6 from optparse
import OptionParser
, OptionValueError
9 from git_stats
import branch
10 from git_stats
import commit
11 from git_stats
import config
12 from git_stats
import diff
13 from git_stats
import parse
17 "branch_filter_rating",
29 """Contains information about a specific commit
32 def __init__(self
, commit
, options
):
33 """Initializes with the specified commit
36 self
.options
= options
41 self
.diff_matches
= ""
44 """Checks if this commit is a bugfix
46 Returns: Whether branches, reverts, msg_matches or diff_matches is set.
54 def bugfixRating(self
):
55 """Calculates the bugfix rating for this commit
60 if self
.branches
and getattr(self
.options
, "branch_filter_rating", None):
61 score
+= self
.options
.branch_filter_rating
63 if self
.reverts
and getattr(self
.options
, "reverts_rating", None):
64 score
+= self
.options
.reverts_rating
66 if self
.msg_matches
and getattr(self
.options
, "msg_filter_rating", None):
67 score
+= self
.options
.msg_filter_rating
69 if self
.diff_matches
and getattr(self
.options
, "diff_filter_rating", None):
70 score
+= self
.options
.diff_filter_rating
75 """Returns a string representation of this object
80 for branch
in self
.branches
:
81 result
.append("In branch: '%s'." % branch
)
83 for commit
in self
.reverts
:
84 result
.append("Reverts commit: '%s'." % commit
)
87 result
.append("Message matches: '%s'." % self
.msg_matches
)
90 result
.append("Diff matches: '%s'." % self
.diff_matches
)
93 result
.append("Bugfix rating: %d." % self
.bugfixRating())
95 res
= self
.commit
+ ":"
98 res
= "%s\n%s" % (res
,line
)
106 branch_map: A dictionary of all refs and their hashes.
107 parent_list: A dictionary with tuples of refs and their rev-lists.
108 raw_diffs: A dictionary with commits and their raw diffs.
109 parsed_diffs: A dictionary with commits and their parsed diffs.
110 touched_files: A dictionary with files and the commits that touched them.
111 pretty_names: A dictionary with commits and their names
115 """Initializes the memory to an empty state
120 self
.pretty_names
= {}
122 self
.parsed_diffs
= {}
123 self
.touched_files
= {}
125 result
= git
.for_each_ref("refs/heads",
129 branches
= result
.split('\n')
130 self
.branch_map
= branch
.getBranchMap(branches
)
131 self
.parent_list
= branch
.getParentList(self
.branch_map
.values())
133 for name
, hash in self
.branch_map
.iteritems():
134 # Skip the leading 'refs/[heads|remotes]' prefix
135 pos
= name
.find('/', 5)
138 self
.pretty_names
[hash] = name
140 def aggregateType(options
):
141 """Uses the specified options to get type information for many commits
145 result
= git
.rev_list(options
.start_from
)
146 commits
= result
.split('\n')
148 options
= config
.extractOptions(options
, options_list
)
150 # Unless overridden by the user ignore parents for large repositories
151 if len(commits
) > 1000 and options
.ignore_parents
== None:
152 options
.ignore_parents
= True
159 commits
= commits
[:options
.limit
]
161 for commit
in commits
:
165 stats
= determineType(commit
, memory
, options
)
170 def determineType(target
, memory
, options
):
171 """Determines the type of the specified commit
174 commit: The commit to determine the type of.
175 memory: A class containing various storage containers.
176 options: A dictionary containing options.
179 branches
= branch
.belongsTo(target
,
182 pretty_names
=memory
.pretty_names
,
183 ignore_parents
=options
.ignore_parents
)
185 reverts
= diff
.findReverts( target
,
186 raw_diffs
=memory
.raw_diffs
,
187 parsed_diffs
=memory
.parsed_diffs
,
188 touched_files
=memory
.touched_files
)
190 msg_matches
= (options
.msg_filter
and
191 commit
.commitmsgMatches(target
, options
.msg_filter
))
193 diff_matches
= (options
.diff_filter
and
194 commit
.commitdiffMatches( target
,
196 raw_diffs
=memory
.raw_diffs
))
198 result
= CommitInfo(target
, options
)
200 if not options
.branch_filter
:
203 filter_branches
= options
.branch_filter
.split(':')
204 match
= set(filter_branches
).intersection(set(branches
))
207 result
.branches
= match
210 result
.reverts
= reverts
213 result
.msg_matches
= options
.msg_filter
216 result
.diff_matches
= options
.diff_filter
220 def _checkOptions(parser
, options
):
221 """Checks the specified options and uses the parser to indicate errors
224 parser: The parser to use to signal when the options are bad.
225 options: The options to check.
228 opts
= [options
.aggregate
, options
.type]
230 if not parse
.isUnique(opts
, at_least_one
=True):
231 parser
.error("Please choose exactly one mode")
234 """Dispatches index related commands
237 progname
= os
.path
.basename(sys
.argv
[0]) + " bug"
239 parser
= OptionParser(option_class
=parse
.GitOption
, prog
=progname
)
244 help="show debug information")
249 help="aggregate bug information of all commits")
254 help="shows which type the specified commit is")
257 "-s", "--start-from",
260 help="the commit to start from")
263 "-m", "--message-filter",
264 help="mark the commit as a fix if it's log matches this filter")
267 "-f", "--diff-filter",
268 help="mark the commit as a fix if it's diff matches this filter")
271 "-b", "--branch-filter",
272 help="mark the commit as a fix if it belongs to this branch")
278 help="limit the commits checked to the most recent N ones")
281 "-i", "--ignore-parents",
283 help="whether to enable parentage ignoring")
285 # Default to True for now, until there is another option
286 parser
.set_default("debug", False)
287 parser
.set_default("aggregate", False)
288 parser
.set_default("start_from", "HEAD")
289 parser
.set_default("limit", 10)
290 parser
.set_default("ignore_parents", None)
292 (options
, args
) = parser
.parse_args(list(args
))
294 _checkOptions(parser
, options
)
296 if options
.aggregate
:
297 result
= aggregateType(options
)
299 kwargs
= config
.extractOptions(options
, options_list
)
301 stats
= determineType(options
.type, memory
, kwargs
)
302 result
= [str(stats
)]