gitstats: Two more memories were added and some refactoring
[git-stats.git] / src / git_stats / bug.py
blobdf4602549e0917447bc1df00d445b70f43fa8cf2
1 #!/usr/bin/env python
3 import os
4 import sys
6 from optparse import OptionParser, OptionValueError
7 from git import Repo
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
15 class CommitInfo:
16 """Contains information about a specific commit
17 """
19 def __init__(self, commit):
20 """Initializes with the specified commit
21 """
23 self.commit = commit
24 self.branches = []
25 self.reverts = []
26 self.msg_matches = ""
27 self.diff_matches = ""
29 def isBugfix():
30 """Checks if this commit is a bugfix
32 Returns: Whether branches, reverts, msg_matches or diff_matches is set.
33 """
35 return self.branches \
36 or self.reverts \
37 or self.msg_matches \
38 or self.diff_matches
40 def __str__(self):
41 """Returns a string representation of this object
42 """
44 result = []
46 for branch in self.branches:
47 result.append("In branch: " + branch)
49 for commit in self.reverts:
50 result.append("Reverts commit: " + commit)
52 if self.msg_matches:
53 result.append("Message matches: '" + self.msg_matches + "'.")
55 if self.diff_matches:
56 result.append("Diff matches: '" + self.diff_matches + "'.")
58 res = self.commit + ":"
60 for line in result:
61 res += "\n" + line
63 return res
65 def aggregateType(options):
66 """
67 """
69 git = Repo(".").git
70 result = git.rev_list(options.start_from)
71 commits = result.split('\n')
73 kwargs = extractKwargs(options)
75 result = git.for_each_ref("refs/heads",
76 "refs/remotes",
77 format="%(objectname)")
79 branches = result.split('\n')
80 branch_map = branch.getBranchMap(branches, cut_off_prefix=False)
81 parent_list = branch.getParentList(branch_map.values())
83 pretty_names = {}
84 raw_diffs = {}
85 parsed_diffs = {}
86 touched_files = {}
88 result = []
90 for commit in commits:
91 stats = determineType(commit,
92 branch_map,
93 parent_list,
94 pretty_names=pretty_names,
95 raw_diffs=raw_diffs,
96 parsed_diffs=parsed_diffs,
97 touched_files=touched_files,
98 **kwargs)
99 result.append(stats)
101 return result
103 def determineType(target,
104 branch_map,
105 parent_list,
106 debug=False,
107 branchFilter=None,
108 msgFilter=None,
109 diffFilter=None,
110 pretty_names={},
111 raw_diffs={},
112 parsed_diffs={},
113 touched_files={}):
114 """Determines the type of the specified commit
116 Args:
117 commit: The commit to determine the type of.
118 debug: Whether to include debug information.
119 branchFilter: Type=fix if a commit belongs to any of these branches.
120 msgFilter: Type=fix if the commit log msg matches this filter.
121 diffFilter: Type=fix if the commit diff matches this filter.
122 raw_diffs: A dictionary with commits and their raw diffs.
123 parsed_diffs: A dictionary with commits and their parsed diffs.
124 touched_files: A dictionary with files and the commits that touched them.
127 branches = branch.belongsTo(target,
128 branch_map,
129 parent_list,
130 pretty_names=pretty_names)
132 reverts = diff.findReverts( target,
133 raw_diffs=raw_diffs,
134 parsed_diffs=parsed_diffs,
135 touched_files=touched_files)
137 msg_matches = msgFilter and commit.commitmsgMatches(target, msgFilter)
138 diff_matches = diffFilter and commit.commitdiffMatches( target,
139 diffFilter,
140 raw_diffs=raw_diffs)
142 result = CommitInfo(target)
144 if branchFilter in branches:
145 match = set(branchFilter).intersection(set(branches))
146 result.branches = match
148 if reverts:
149 result.reverts = reverts
151 if msg_matches:
152 result.msg_matches = msgFilter
154 if diff_matches:
155 result.diff_matches = diffFilter
157 return result
159 def extractKwargs(options):
160 """Extracts kwargs and returns the result
162 The kwargs are extracted from the specified options and
163 from the 'config' file.
166 opts = config.read()
168 kwargs = {}
170 kwargs["debug"] = options.debug or opts.get("debug", False)
171 kwargs["msgFilter"] = options.message_filter or opts.get("msgFilter", None)
172 kwargs["diffFilter"] = options.diff_filter or opts.get("diffFilter", None)
173 kwargs["branchFilter"] = options.branch_filter or opts.get("branchFilter", None)
175 return kwargs
177 def _checkOptions(parser, options):
178 """Checks the specified options and uses the parser to indicate errors
180 Args:
181 parser: The parser to use to signal when the options are bad.
182 options: The options to check.
185 opts = [options.aggregate, options.type]
187 if not parse.isUnique(opts):
188 parser.error("Please choose exactly one mode")
190 def dispatch(*args):
191 """Dispatches index related commands
194 progname = os.path.basename(sys.argv[0]) + " bug"
196 parser = OptionParser(option_class=parse.GitOption, prog=progname)
198 parser.add_option(
199 "-d", "--debug",
200 action="store_true",
201 help="show debug information")
203 parser.add_option(
204 "-a", "--aggregate",
205 action="store_true",
206 help="aggregate bug information of all commits")
208 parser.add_option(
209 "-t", "--type",
210 type="commit",
211 help="shows which type the specified commit is")
213 parser.add_option(
214 "-s", "--start-from",
215 type="commit",
216 metavar="COMMIT",
217 help="the commit to start from")
219 parser.add_option(
220 "-m", "--message-filter",
221 help="mark the commit as a fix if it's log matches this filter")
223 parser.add_option(
224 "-f", "--diff-filter",
225 help="mark the commit as a fix if it's diff matches this filter")
227 parser.add_option(
228 "-b", "--branch-filter",
229 help="mark the commit as a fix if it belongs to this branch")
231 # Default to True for now, until there is another option
232 parser.set_default("debug", False)
233 parser.set_default("aggregate", False)
234 parser.set_default("start_from", "HEAD")
236 (options, args) = parser.parse_args(list(args))
238 _checkOptions(parser, options)
240 if options.aggregate:
241 result = aggregateType(options)
242 elif options.type:
243 kwargs = extractKwargs(options)
244 stats = determineType(options.type, **kwargs)
245 result = [str(stats)]
247 for line in result:
248 print(line)