1
#!/usr/bin/env python
2
"""
3
Figure out where trees converge in branches when there may be no similar
4
ancestry.
5
6
Copyright (c) 2008  Dustin Sallings <dustin@spy.net>
7
"""
8
9
from __future__ import with_statement
10
11
import sys
12
import difflib
13
import subprocess
14
import collections
15
16
trees={}
17
commit_map={}
18
commits={}
19
20
def load_list(branch):
21
    t=[]
22
    trees[branch] = t
23
    cm = collections.defaultdict(list)
24
    commit_map[branch] = cm
25
26
    args = ['git', 'log', '--pretty=format:%T %h', branch]
27
28
    sub=subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True)
29
30
    for l in sub.stdout:
31
        a=l.strip().split()
32
        commit = a[-1]
33
        treeHash = a[0]
34
        t.append(treeHash)
35
        cm[treeHash].append(commit)
36
37
def load_commits():
38
    args = ['git', 'log', '--all',
39
        '--pretty=format:%h%x00%an%x00%ae%x00%cn%x00%ce%x00%s']
40
    sub=subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True)
41
42
    for l in sub.stdout:
43
        hash, an, ae, cn, ce, desc=l.strip().split("\0")
44
        commits[hash] = (an, ae, cn, ce, desc)
45
46
def commit_info(h):
47
    branch_a, ae, cn, ce, desc = commits[h]
48
    if branch_a == cn:
49
        ai = branch_a
50
    else:
51
        ai = "%s (committed by %s)" % (branch_a, cn)
52
    cl = ('<a href="http://github.com/dustin/memcached/commit/%s">%s</a>'
53
        % (h, h))
54
    return "<div>%s: %s<br/>%s</div>" % (cl, ai, desc)
55
56
def htmlify_col_list(col):
57
    if col:
58
        return "\n".join((commit_info(c) for c in col))
59
    else:
60
        return "&nbsp;"
61
62
def mk_row(cls, l, r):
63
    return ("<tr class='%s'><td class='left'>%s</td><td class='right'>%s</td></tr>" % (cls, l, r))
64
65
def emit_differing_lists(op, left_col, right_col):
66
    print mk_row(op, htmlify_col_list(left_col), htmlify_col_list(right_col))
67
68
def emit_identical_lists(op, left_col, right_col):
69
    for l,r in zip(left_col, right_col):
70
        print mk_row(op, commit_info(l), commit_info(r))
71
72
if __name__ == '__main__':
73
    branch_a, branch_b = sys.argv[1:]
74
75
    load_commits()
76
    load_list(branch_a)
77
    load_list(branch_b)
78
79
    print """<html>
80
        <head>
81
            <title>Tree Comparison from %(branch_a)s to %(branch_b)s</title>
82
            <style type="text/css">
83
                html {
84
                    font-family: verdana;
85
                }
86
                table tr, table td {
87
			border: solid 1px;
88
			vertical-align: top
89
                }
90
                .delete .left, .replace .left {
91
			background: #faa;
92
			color black;
93
                }
94
                .insert .right, .replace .right {
95
			background: #afa;
96
			color black;
97
                }
98
                table tr td div {
99
			border: solid 1px;
100
			padding: 0;
101
			margin: 0;
102
                }
103
            </style>
104
        </head>
105
        <body>
106
        <table>
107
        <thead>
108
            <tr>
109
                <th>%(branch_a)s</th>
110
                <th>%(branch_b)s</th>
111
            </tr>
112
        </thead>
113
        <tbody>
114
""" % {'branch_a': branch_a, 'branch_b': branch_b}
115
    a=trees[branch_a]
116
    b=trees[branch_b]
117
    sm = difflib.SequenceMatcher(a=a, b=b)
118
    for op, i1, i2, j1, j2 in sm.get_opcodes():
119
        left=a[i1:i2]
120
        right=b[j1:j2]
121
122
        left_col=[commit_map[branch_a][tree].pop() for tree in left]
123
        right_col=[commit_map[branch_b][tree].pop() for tree in right]
124
125
        if op == 'equal':
126
            emit_identical_lists(op, left_col, right_col)
127
        else:
128
            emit_differing_lists(op, left_col, right_col)
129
130
    print "</tbody></table></body></html>"