| 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 " " |
| 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>" |