parent
adc71512cd
commit
e44ff7edf3
@ -1,278 +0,0 @@ |
||||
#!/usr/bin/python |
||||
#emacs: -*- mode: python-mode; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: t -*- |
||||
#ex: set sts=4 ts=4 sw=4 noet: |
||||
#------------------------- =+- Python script -+= ------------------------- |
||||
""" |
||||
@file graphviz_plot.py |
||||
@date Mon Aug 11 11:54:34 2008 |
||||
@brief |
||||
|
||||
|
||||
Yaroslav Halchenko CS@UNM, CS@NJIT |
||||
web: http://www.onerussian.com & PSYCH@RUTGERS |
||||
e-mail: yoh@onerussian.com ICQ#: 60653192 |
||||
|
||||
DESCRIPTION (NOTES): Prototype code to draw using graphviz a git |
||||
history of merges with collapses, where plain 1-parent 1-child |
||||
commits occured |
||||
|
||||
TODO: |
||||
use pydot internally to create a graph |
||||
proper filenames specification |
||||
.... |
||||
|
||||
EXAMPLE USAGE: |
||||
|
||||
../graphviz_plot.py --all # will generate graph.dot |
||||
dot -Tsvg graph.dot >| graph.svg |
||||
inkscape graph.svg |
||||
|
||||
COPYRIGHT: Yaroslav Halchenko 2008 |
||||
|
||||
LICENSE: |
||||
|
||||
This program is free software; you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation; either version 2 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program; if not, write to the |
||||
Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, |
||||
MA 02110-1301, USA. |
||||
|
||||
On Debian system see /usr/share/common-licenses/GPL for the full license. |
||||
""" |
||||
#-----------------\____________________________________/------------------ |
||||
|
||||
__author__ = 'Yaroslav Halhenko' |
||||
__revision__ = '$Revision: $' |
||||
__date__ = '$Date: $' |
||||
__copyright__ = 'Copyright (c) 2008 Yaroslav Halchenko' |
||||
__license__ = 'GPL' |
||||
|
||||
|
||||
import sys, re, os |
||||
|
||||
gitargs = ' '.join(sys.argv[1:]) |
||||
|
||||
# lets read all commits and place into list |
||||
commits = [] |
||||
|
||||
split_strip = lambda f, split_symbol: [[x.strip() for x in l.split(split_symbol)] |
||||
for l in f.readlines()] |
||||
|
||||
print "I: Reading the commits" |
||||
f = os.popen('git rev-list --pretty=format:"%%H|%%h|%%an|%%s" "%s" | grep -v "^commit"' |
||||
% gitargs) |
||||
commits = split_strip(f, '|') |
||||
|
||||
print "I: Reading the tree" |
||||
f = os.popen('git rev-list --parents "%s"' % gitargs) |
||||
tree_down = split_strip(f, ' ') |
||||
|
||||
|
||||
print "I: Reading heads" |
||||
f = os.popen("git branch --no-color -v --no-abbrev | sed -e 's,\*,,g' | awk '{print $1, $2;}'") |
||||
heads_list = split_strip(f, ' ') |
||||
heads_dict = dict( [ (y,x) for x,y in heads_list ] ) |
||||
|
||||
|
||||
# need to traverse the tree and create double linked nodes instead of |
||||
# just parent since we would need to remove some |
||||
tree = dict([(n[0], [n[1:], [], [], [], []]) for n in tree_down]) |
||||
|
||||
print "I: Rebuilding childrens" |
||||
# lets rebuild children nodes -- may be there is cleaner way? :-) |
||||
for id_, (parents, children, indirect_parents, cherried_into, cherried_from) in tree.iteritems(): |
||||
for parent in parents: |
||||
tree[parent][1].append(id_) |
||||
|
||||
print "I: Figuring out cherry picks" |
||||
# following evil bash line gives what is needed |
||||
# git rev-list --no-merges --pretty=format:"|%H|%h|%ci|%an|%ae|%s|%b|" --all | grep -v '^commit' | tr '&' '%' | tr '\n' '&' | sed -e 's/|&/|\n/g' -e 's/&|/|/g' | sort -t\| -k 5,8 | uniq -d -s75 -D | while read commit; do sha=$(echo $commit | awk -F\| '{print $2;}'); diffsha=$(git diff ${sha}^..${sha} | grep '^[+-]' | sha1sum | awk '{print $1;}'); echo "$commit|$diffsha"; done | sort -t\| -k 5,10 | uniq -d -s75 -D |
||||
# |
||||
# in summary it does |
||||
# 1. list all non merge commits and looks for similar according |
||||
# to author/commit_msg |
||||
# |
||||
# 2. since some times commit_msg's are bogus -- we also add sha1sum for |
||||
# changed context by that commit -- lines which start with + or - |
||||
# |
||||
# 3. and then once again - sort and uniq ;-) |
||||
# |
||||
# result looks like |
||||
# |072c0f7a0449130e7363be9ef618668147ece87e|072c0f7|2008-07-30 18:26:26 +0200|Julian_chu|julian_chu@openmoko.com|[splinter] Bump up the reversion of splinter to 507|For some UI tunning stuff||66e879ca796685319d4602487e0e52c629217191 |
||||
# |23ed84fc72097e7abe1dac21453a33ce9440991d|23ed84f|2008-07-30 23:01:44 +0800|Julian_chu|julian_chu@openmoko.com|[splinter] Bump up the reversion of splinter to 507|For some UI tunning stuff||66e879ca796685319d4602487e0e52c629217191 |
||||
# |8c1e9ae76bf39c141d2f5bdc759c5fb628574463|8c1e9ae|2008-05-11 23:32:35 +0000||julian_chu@openmoko.com|Add Ninja Theme as default|||8f42ba37c6fef619bf922a689d560e0ffc979b5f |
||||
# |ac9d16ad220468ba1f359355e155e2dc2a232272|ac9d16a|2008-05-25 22:27:39 +0800||julian_chu@openmoko.com|Add Ninja Theme as default|||8f42ba37c6fef619bf922a689d560e0ffc979b5f |
||||
# |
||||
# so we just need to compare the times to derive from where it was cherry picked |
||||
cmd = 'git rev-list --no-merges --pretty=format:"|%%H|%%h|%%ci|%%an|%%ae|%%s|%%b|" %s | grep -v "^commit" | tr "&" "%%" | tr "\\n" "&" | sed -e "s/|&/|\\n/g" -e "s/&|/|/g" | sort -t\| -k 5,8 | uniq -d -s75 -D | while read commit; do sha=$(echo $commit | awk -F\| "{print \$2;}"); diffsha=$(git diff ${sha}^..${sha} | grep "^[+-]" | sha1sum | awk "{print \$1;}"); echo "$commit|$diffsha"; done | sort -t\| -k 5,10 | uniq -d -s75 -D' % gitargs |
||||
f = os.popen(cmd) |
||||
cherry_picks_raw = split_strip(f, '|') |
||||
#print cherry_picks_raw |
||||
|
||||
print "I: Analyzing cherry picks" |
||||
# 3 - date |
||||
# >=4 -- use for id |
||||
cp_uid_last = None |
||||
cp_date_min = 'X' |
||||
cherry_picks = {} |
||||
cp_ids = [] |
||||
for cp in cherry_picks_raw + [ [' ']*12 ]: # trailer for easier logic |
||||
id_ = cp[1] |
||||
cp_date = cp[3] |
||||
cp_uid = ' '.join(cp[4:]) |
||||
if cp_uid_last != cp_uid and cp_uid_last is not None: |
||||
# process it since we got to a new one |
||||
#print "D: Process cherry pick '%s' created in %s" % (cp_uid_last, id_min) |
||||
try: |
||||
cp_ids.pop(cp_ids.index(id_min)) |
||||
except: |
||||
# XXX should not happen but does -- figure out later |
||||
pass |
||||
tree[id_min][3] = cp_ids |
||||
for cp_ in cp_ids: |
||||
tree[cp_][4] = [id_min] |
||||
cp_ids = [] |
||||
pass |
||||
if cp_date < cp_date_min: |
||||
cp_date_min = cp_date |
||||
id_min = id_ |
||||
cp_uid_last = cp_uid |
||||
cp_ids.append(id_) |
||||
assert(id_ == ' ') |
||||
|
||||
|
||||
print "I: Figuring out branches ownership from merge messages" |
||||
# for now each node belongs just to 1 branch although sure thing it |
||||
# could be in few branches at that moment |
||||
regexp = re.compile("Merge (?P<type>branch|commit) '(?P<branch0>.*)'(?: into (?P<branch1>[^. ,|]*))?") |
||||
branches = {} |
||||
for commit in commits: |
||||
fid = commit[0] |
||||
# only those which are left |
||||
if not fid in tree: |
||||
continue |
||||
comment = commit[3] |
||||
regres = regexp.search(comment) |
||||
if regres is None: |
||||
# print "I: don't know branch for %s" % fid |
||||
continue |
||||
|
||||
regdict = regres.groupdict() |
||||
|
||||
if regdict['branch1'] == None: |
||||
regdict['branch1'] = 'master' |
||||
|
||||
branches[fid] = regdict['branch1'] |
||||
# TODO check what is message with octopus |
||||
if regdict['type'] == 'branch': |
||||
#import pydb |
||||
#pydb.debugger() |
||||
branches[ tree[fid][0][1] ] = regdict['branch0'] |
||||
branches[ tree[fid][0][0] ] = regdict['branch1'] |
||||
|
||||
from sets import Set |
||||
branches_names = list(Set(branches.values() + heads_dict.values())) |
||||
print "D: Detected branches from merges: ", branches_names |
||||
branches_colors = dict([[x, i+2] for i,x in enumerate(branches_names)]) |
||||
#print branches_colors |
||||
|
||||
#for k, p in tree.iteritems(): |
||||
# print k, p |
||||
|
||||
print "I: Pruning" |
||||
# lets go through all nodes and if there is only 1 parent and 1 child -- KILL!!! |
||||
for id_ in tree.keys(): |
||||
parents, children, indirect_parents, cherried_into, cherried_from = tree[id_] |
||||
if len(children) == 1 and len(parents) + len(indirect_parents) == 1 \ |
||||
and len(cherried_into) + len(cherried_from) == 0 \ |
||||
and (not id_ in heads_dict):# and False: |
||||
# print "D: %s with %s" % (id_, tree[id_]) |
||||
tree.pop(id_) |
||||
|
||||
# update children |
||||
for child in children: |
||||
tchild = tree[child] |
||||
tchild[2] += parents + indirect_parents |
||||
for i in [0,2]: |
||||
try: |
||||
tchild[i].pop(tchild[i].index(id_)) |
||||
#print "removed %s from %s's parent %d" % (id_, child, i) |
||||
except: |
||||
pass |
||||
|
||||
# update the parent |
||||
for parent in parents + indirect_parents: |
||||
tparent = tree[parent] |
||||
tparent[1] += children |
||||
try: |
||||
tparent[1].pop(tparent[1].index(id_)) |
||||
#print "removed %s from %s's children" % (id_, parent) |
||||
except: |
||||
pass |
||||
|
||||
|
||||
#print tree |
||||
print "I: Storing graph" |
||||
# plot the damn graph ;-) |
||||
fout = open('graph.dot', 'w') |
||||
fout.write("digraph lattice {\n") |
||||
|
||||
# print nodes |
||||
for commit in commits: |
||||
fid = commit[0] |
||||
# only those which are left |
||||
if not fid in tree: |
||||
continue |
||||
|
||||
sid = commit[1] |
||||
if len(commit)>2: |
||||
comment = ' '.join(commit[3:]) |
||||
else: |
||||
comment = '' |
||||
|
||||
# condition comment |
||||
comment = comment.replace('"', "'")[:60] |
||||
|
||||
try: |
||||
color = ", fillcolor=\"/brbg11/%s\", style=\"filled\"" % branches_colors[branches[fid]] |
||||
except: |
||||
color = "" |
||||
|
||||
fout.write('n%s [ label="%s\\n%s"%s]\n' % (fid, sid, comment, color)) |
||||
|
||||
# print heads |
||||
for head_id, head_name in heads_dict.iteritems(): |
||||
try: |
||||
color = ", color=\"/brbg11/%s\", style=\"filled\"" % branches_colors[head_name] |
||||
except: |
||||
color = "" |
||||
fout.write('h%s [ shape=box, label="%s"%s]\n' % (head_id, head_name, color)) |
||||
|
||||
# print edges |
||||
for id_, (parents, children, indirect_parents, cherried_into, cherried_from) in tree.iteritems(): |
||||
c = ("", " [ color=\"red\"]")[int(len(parents)>1)] |
||||
|
||||
for p in parents: |
||||
fout.write("n%s -> n%s%s\n" % (id_, p, c)) |
||||
|
||||
for p in indirect_parents: |
||||
fout.write("n%s -> n%s [style=dashed, color=\"red\"]\n" % (id_, p)) |
||||
|
||||
if len(cherried_from)>0: |
||||
fout.write("n%s -> n%s [style=dashed]\n" % (id_, cherried_from[0])) |
||||
|
||||
# print links from heads |
||||
for head_id, head_name in heads_dict.iteritems(): |
||||
fout.write("h%s -> n%s\n" % (head_id, head_id)) |
||||
|
||||
fout.write("}\n") |
||||
fout.close() |
Loading…
Reference in new issue