6 from optparse
import OptionParser
9 from git_stats
import parse
11 def prettyName(branch
, pretty_names
={}):
12 """Looks up the name of a branch and returns it
14 Returns only the last part of the branchname.
17 branch: The branch to print
18 pretty_names: A dictionary with commits and their names
21 if not branch
in pretty_names
:
23 name
= git
.describe("--all", branch
)
24 splitname
= name
.split('/')
26 pretty_names
[branch
] = name
28 name
= pretty_names
[branch
]
33 """This class represents a metric
35 It is meant to store arbitrary data related to a specific metric.
44 res
= ("Branch %s, dilution: %d." %
45 (prettyName(self
.branch
), self
.dilution
))
49 def _bestMetric(metrics
):
50 """Finds the best metric from a set
52 Iterates the metrics and selects the ones with the lowest dilution.
53 All the returned metrics are guaranteed to have the same dilution.
56 metrics: The metrics to pick the best ones from.
58 Returns: A list with the best metrics.
63 # Find the best metric
64 for metric
in metrics
:
65 # Store the current value for easy access
66 dilution
= metric
.dilution
68 # First element, set the minimum to the current
72 # Worse than our best, not interesting
76 # Clear the old ones if we have a better minimum
85 def _calculateDilution(target
, start
, parent_lists
, global_memory
, ignore
=[]):
86 """Calculates the dilution for the specified commit.
89 target: The commit to calculate the dilution for.
90 start: The initial metric information.
91 parent_list: A dictionary with commits and their parents.
92 global_memory: A dictionary with all commits and their parents.
93 ignore: The parent commits of the target commit which should be ignored.
105 current
= stack
.pop()
107 # We found what we are looking for
108 if target
== current
.commit
:
109 metrics
.append(current
)
112 # Ignore commits that are past the target
113 if current
.commit
in ignore
:
116 # If we already checked this commit, don't add it again
117 if current
.commit
in memory
:
118 dilution
= memory
[current
.commit
]
119 if dilution
<= current
.dilution
:
122 # Nor when already checked in a better branch
123 if current
.commit
in global_memory
:
124 dilution
= global_memory
[current
.commit
]
125 if dilution
< current
.dilution
:
128 # Remember this commit
129 memory
[current
.commit
] = current
.dilution
130 global_memory
[current
.commit
] = current
.dilution
132 # Defaulting to None in the case we only have a partial parent listing
133 parents
= parent_lists
.get(current
.commit
, None)
135 # Root commit, we can't go any further
139 # Check all the parents and give only the first one 0 extra dilution
140 for parent
in parents
:
141 if parent
== parents
[0]:
142 newdilution
= current
.dilution
144 newdilution
= current
.dilution
+ 1
146 # Create the next target
149 next
.branch
= current
.branch
150 next
.dilution
= newdilution
152 # Add it to the stack to be examined
155 # Nothing new was found here
159 # Return only the best metric
160 min, champs
= _bestMetric(metrics
)
163 def debugPrint(debug
, text
):
164 """Only print the specified text if debug is on
166 debug: Whether debug is on.
167 text: The text to print
173 def getBranchMap(branches
):
174 """Creates a branchMap from the specified branches and returns it
176 The hash of each branch is retreived and stored in a
177 dictionary with the condition that all values in the map
178 are unique. As such if multiple branches map to one hash,
179 only one of those branches (the first one) will be in the
183 branches: A list of branches that should be in the map.
185 Returns: A dictionary with branch names and their hashes.
192 for branch
in branches
:
193 # Retreive the hash of each branch
194 branch_hash
= git
.rev_parse(branch
)
196 if branch_hash
in branch_map
.values():
199 branch_map
[branch
] = branch_hash
203 def getParentList(commits
):
204 """Retreives the parents of the specified commits and their parents
206 The parents for all the specified commits, and for the
207 parents of those commits, are retreived and stored in
211 commits: A list of commits for which the parents should be retreived.
213 Returns: A dictionary with commits and their parents.
218 # First mine some data
219 result
= git
.rev_list("--parents", *commits
)
220 data
= result
.split('\n')
225 # Each line contains the commit first, and then all it's parents
226 splitline
= item
.split(' ')
229 # This commit has no parents at all
230 if len(splitline
) == 1:
231 parent_list
[first
] = []
232 # This commits has no parents either
233 elif splitline
[1] == '':
234 parent_list
[first
] = []
235 # Store all the parents
237 parent_list
[first
] = splitline
[1:]
241 def belongsTo(commit
,
246 ignore_parents
=False):
247 """Returns which branches the specified commit belongs to
250 commit: The commit to examine.
251 branch_map: A dictionary of all refs and their hashes.
252 parent_list: A dictionary with tuples of refs and their rev-lists.
253 pretty_names: A dictionary with commits and their names
254 debug: Whether to return debug information as well.
256 Returns: A list of branches that the commit belongs to.
261 if commit
in branch_map
:
262 debugPrint(debug
, "Easy as pie, that's a branch head!")
263 name
= branch_map
[commit
]
268 # Retreive the parents of the target commit and ignore them
270 result
= git
.rev_list(commit
)
271 ignore
= set(result
.split('\n'))
275 debugPrint(debug
, "Checking branches now:")
279 # Collect the metric for all branches
280 for branch_name
, branch_hash
in branch_map
.iteritems():
281 debugPrint(debug
, branch_name
)
283 # Create the first metric
285 metric
.branch
= branch_hash
286 metric
.commit
= branch_hash
289 # Find the dilution for this branch
290 metric
= _calculateDilution(commit
, metric
, parent_list
, memory
, ignore
)
291 metrics
= metrics
+ metric
293 debugPrint(debug
, "Done.\n")
296 print("Listing found metrics:")
297 for metric
in metrics
:
300 min, champs
= _bestMetric(metrics
)
302 debugPrint(debug
, "Done.\n")
307 results
.append("The minimal dilution is: " + str(min))
309 # Loop over all the champs and get their name
310 for metric
in champs
:
311 results
.append(prettyName(metric
.branch
, pretty_names
))
315 def branchcontains(branch
, commit
):
316 """returns whether the specified branch contains the specified commit.
323 whether the branch contains the commit.
327 arg
= branch
+ ".." + commit
328 result
= git
.rev_list(arg
)
331 # if there is a difference between these sets, the commit is not in the branch
334 # there is no difference between the two, thus the branch contains the commit
337 def branchList(commit_filter
, include_remotes
=False):
338 """Returns all branches that contain the specified commit
341 commit_filter: The commit to filter by.
342 include_remotes: Whether to search remote branches as well.
353 args
.append("--contains")
354 args
.append(commit_filter
)
356 result
= git
.branch(*args
)
358 branches
= result
.split('\n')
362 for branch
in branches
:
363 result
.append(branch
[2:])
368 """Dispatches branch related commands
371 progname
= os
.path
.basename(sys
.argv
[0]) + " branch"
373 parser
= OptionParser(option_class
=parse
.GitOption
, prog
=progname
)
377 "-b", "--belongs-to",
380 help="find out which branch the specified commit belongs to")
385 help="print debug information on the found metrics")
390 help="show only branches that contain the specified commit")
395 help="include remotes in the listing")
397 parser
.set_default("debug", False)
398 parser
.set_default("remotes", False)
400 (options
, args
) = parser
.parse_args(list(args
))
402 if options
.belongs_to
:
403 branches
= branchList(options
.belongs_to
, include_remotes
=options
.remotes
)
405 branch_map
= getBranchMap(branches
)
406 parent_list
= getParentList(branch_map
.values())
408 result
= belongsTo( options
.belongs_to
,
413 result
= branchList(commit_filter
=options
.contains
,
414 include_remotes
=options
.remotes
)
416 print("Matching branches:")
418 for branch
in result
: