3 # Create a graph of patch flow into the mainline.
5 # This code is part of the LWN git data miner.
7 # Copyright 2007-11 Eklektix, Inc.
8 # Copyright 2007-11 Jonathan Corbet <corbet@lwn.net>
10 # This file may be distributed under the terms of the GNU General
11 # Public License, version 2.
14 from patterns
import patterns
17 # The various types of commit we understand.
20 def __init__(self
, id, parent
):
29 def __init__(self
, id, parent
):
30 Commit
.__init
__(self
, id, parent
)
32 self
.internal
= 1 # Two branches within a repo?
33 self
.parents
= [ parent
]
35 def addparent(self
, parentid
):
36 self
.parents
.append(parentid
)
38 def addtree(self
, tree
):
43 # Trees: where the commits come from.
46 def __init__(self
, name
, url
):
52 def addcommit(self
, id):
53 self
.commits
.append(id)
55 def addinput(self
, tree
):
56 if tree
not in self
.inputs
:
57 self
.inputs
.append(tree
)
58 # print '%s -> %s' % (tree.name, self.name)
60 Mainline
= Tree('Mainline',
61 'git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git')
62 KnownTrees
= { Mainline
.url
: Mainline
}
64 def NormalizeURL(url
):
67 if url
== '../net-2.6/':
68 url
= 'git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-2.6'
69 url
= url
.replace('master.kernel.org:', 'git://git.kernel.org')
70 if url
[-18:] == 'torvalds/linux-2.6':
72 if url
[:8] == '/pub/scm':
73 url
= 'git://git.kernel.org' + url
77 url
= NormalizeURL(url
)
79 return KnownTrees
[url
]
82 KnownTrees
[url
] = tree
86 # We track which tree every commit belongs to.
90 def __init__ (self
, tree
, priority
, path
):
92 self
.priority
= priority
95 def AddCommitTree(id, entry
):
96 # print 'add: ', id, '[',
97 # for tree in entry.path:
101 oldentry
= CommitTrees
[id]
102 if entry
.priority
< oldentry
.priority
:
103 CommitTrees
[id] = entry
105 CommitTrees
[id] = entry
108 def LookupCommitTree(id):
110 return CommitTrees
[id]
112 print 'Unfound commit %s' % (id)
113 return CTEntry (Mainline
, 0, [])
116 # Input handling with one-line pushback.
127 return Input
.readline()
134 # Pull in a commit and see what it is.
138 # Skip junk up to the next commit.
144 m
= patterns
['commit'].match(line
)
149 # Look at the commit and see how many parents we have.
151 ids
= m
.group(1).split()
153 if len(CommitTrees
.values()) > 0:
154 print 'No-Parent commit:', ids
[0]
156 print 'Did you run git with --parents?'
159 if len(ids
) == 2: # Simple commit
160 return Commit(ids
[0], ids
[1])
162 # OK, we have a merge.
164 merge
= Merge(ids
[0], ids
[1])
168 # We need to figure out what kind of merge it is, so read through the
169 # descriptive text to the merge line.
174 print 'EOF looking for merge line'
177 # Maybe it's an external merge?
179 m
= patterns
['ExtMerge'].match(line
)
181 merge
.addtree(LookupTree(m
.group(3)))
184 # OK, maybe it's internal
186 if patterns
['IntMerge'].match(line
) or patterns
['IntMerge2'].match(line
):
187 #print 'Internal:', line[:-1]
190 m
= patterns
['commit'].match(line
)
192 print 'Hit next commit (%s) looking for merge line' % (m
.group(1))
197 # Print out a tree and its inputs
199 def PrintTree(tree
, indent
= ''):
200 print '%s%4d %s' % (indent
, len(tree
.commits
), tree
.name
)
201 for input in tree
.inputs
:
202 PrintTree(input, indent
+ ' ')
205 # Let's try to build a data structure giving the patch flows.
208 def __init__(self
, tree
):
214 rootnode
= FlowNode(Mainline
)
215 notree
= Tree('[No tree]', '')
216 for centry
in CommitTrees
.values():
220 FillFlowPath(path
, rootnode
)
223 def FillFlowPath(path
, node
):
227 next
, rest
= path
[0], path
[1:]
229 nextnode
= node
.inputs
[next
.name
]
231 nextnode
= node
.inputs
[next
.name
] = FlowNode(next
)
232 return FillFlowPath(rest
, nextnode
)
234 def PrintFlowTree(ftree
, indent
= ''):
235 print '%s%3d %s' % (indent
, ftree
.commits
, ftree
.tree
.name
)
236 inputs
= ftree
.inputs
.values()
239 PrintFlowTree(input, indent
+ ' ')
242 # Something for graphviz
244 GVHeader
= '''digraph "runtree" {
245 graph [ label = "Patch flow into the mainline",
249 node [shape = polygon,
259 global MainlineCommits
260 MainlineCommits
= ftree
.commits
261 gvf
= open('runtree.gv', 'w')
263 inputs
= ftree
.inputs
.values()
266 GVPrintNode(gvf
, input, 'Mainline')
269 def GVNodeName(treename
):
270 sname
= treename
.split('/')
271 if treename
.find('kernel.org') >= 0:
272 return '%s/%s' % (sname
[-2], sname
[-1])
273 sep
= treename
.find ('://')
275 return treename
[sep
+3:]
279 return n2
.commits
- n1
.commits
281 def GVPrintNode(gvf
, node
, parent
):
282 name
= GVNodeName(node
.tree
.name
)
283 gvf
.write ('"%s" -> "%s" [taillabel = "%d", labelfontsize = 8' % (name
, parent
, node
.commits
))
284 gvf
.write (', arrowsize = 0.5')
285 if MainlineCommits
/node
.commits
< 20:
286 gvf
.write(', color = red')
287 elif MainlineCommits
/node
.commits
< 100:
288 gvf
.write(', color = orange');
290 inputs
= node
.inputs
.values()
294 GVPrintNode(gvf
, input, name
)
303 entry
= LookupCommitTree(commit
.id)
305 priority
= entry
.priority
306 tree
.addcommit(commit
.id)
308 # For regular commits, just remember the tree involved
310 if not commit
.ismerge
:
311 AddCommitTree(commit
.parent
, entry
)
313 # For merges we have to deal with all the parents.
316 AddCommitTree(commit
.parents
[0], CTEntry (tree
, priority
, entry
.path
))
318 for p
in commit
.parents
[1:]:
319 path
= entry
.path
+ [tree
]
320 AddCommitTree(p
, CTEntry (tree
, priority
, entry
.path
))
322 for p
in commit
.parents
[1:]:
323 path
= entry
.path
+ [commit
.tree
]
324 AddCommitTree(p
, CTEntry (commit
.tree
, priority
+ 1, path
))
325 if commit
.tree
is not Mainline
:
326 tree
.addinput(commit
.tree
)
330 ftree
= BuildFlowTree()
333 print '%d commits total, %d trees' % (MainlineCommits
, len (KnownTrees
.keys()))