gitstats: Add a 'commits that touch' memory to commit.py
[git-stats.git] / src / git_stats / bug.py
blobbdf0d36905a90101249cf66ff41c710ad1468e83
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 memory = {}
76 tmemory = {}
78 result = []
80 for commit in commits:
81 stats = determineType(commit, memory=memory, tmemory=tmemory, **kwargs)
82 result.append(stats)
84 return result
86 def determineType(target,
87 debug=False,
88 branchFilter=None,
89 msgFilter=None,
90 diffFilter=None,
91 memory={},
92 tmemory={}):
93 """Determines the type of the specified commit
95 Args:
96 commit: The commit to determine the type of.
97 debug: Whether to include debug information.
98 branchFilter: Type=fix if a commit belongs to any of these branches.
99 msgFilter: Type=fix if the commit log msg matches this filter.
100 diffFilter: Type=fix if the commit diff matches this filter.
101 memory: A dictionary of commits and their parsed diffs.
102 tmemory: A dictionary of files and the commits that touched them.
105 branches = branch.belongsTo(target)
106 reverts = diff.findReverts(target, memory, tmemory)
107 msg_matches = msgFilter and commit.commitmsgMatches(target, msgFilter)
108 diff_matches = diffFilter and commit.commitdiffMatches(target, diffFilter)
110 result = CommitInfo(target)
112 if branchFilter in branches:
113 match = set(branchFilter).intersection(set(branches))
114 result.branches = match
116 if reverts:
117 result.reverts = reverts
119 if msg_matches:
120 result.msg_matches = msgFilter
122 if diff_matches:
123 result.diff_matches = diffFilter
125 return result
127 def extractKwargs(options):
128 """Extracts kwargs and returns the result
130 The kwargs are extracted from the specified options and
131 from the 'config' file.
134 opts = config.read()
136 kwargs = {}
138 kwargs["debug"] = options.debug or opts.get("debug", False)
139 kwargs["msgFilter"] = options.message_filter or opts.get("msgFilter", None)
140 kwargs["diffFilter"] = options.diff_filter or opts.get("diffFilter", None)
141 kwargs["branchFilter"] = options.branch_filter or opts.get("branchFilter", None)
143 return kwargs
145 def _checkOptions(parser, options):
146 """Checks the specified options and uses the parser to indicate errors
148 Args:
149 parser: The parser to use to signal when the options are bad.
150 options: The options to check.
153 opts = [options.aggregate, options.type]
155 if not parse.isUnique(opts):
156 parser.error("Please choose exactly one mode")
158 def dispatch(*args):
159 """Dispatches index related commands
162 progname = os.path.basename(sys.argv[0]) + " bug"
164 parser = OptionParser(option_class=parse.GitOption, prog=progname)
166 parser.add_option(
167 "-d", "--debug",
168 action="store_true",
169 help="show debug information")
171 parser.add_option(
172 "-a", "--aggregate",
173 action="store_true",
174 help="aggregate bug information of all commits")
176 parser.add_option(
177 "-t", "--type",
178 type="commit",
179 help="shows which type the specified commit is")
181 parser.add_option(
182 "-s", "--start-from",
183 type="commit",
184 metavar="COMMIT",
185 help="the commit to start from")
187 parser.add_option(
188 "-m", "--message-filter",
189 help="mark the commit as a fix if it's log matches this filter")
191 parser.add_option(
192 "-f", "--diff-filter",
193 help="mark the commit as a fix if it's diff matches this filter")
195 parser.add_option(
196 "-b", "--branch-filter",
197 help="mark the commit as a fix if it belongs to this branch")
199 # Default to True for now, until there is another option
200 parser.set_default("debug", False)
201 parser.set_default("aggregate", False)
202 parser.set_default("start_from", "HEAD")
204 (options, args) = parser.parse_args(list(args))
206 _checkOptions(parser, options)
208 if options.aggregate:
209 result = aggregateType(options)
210 elif options.type:
211 kwargs = extractKwargs(options)
212 stats = determineType(options.type, **kwargs)
213 result = [str(stats)]
215 for line in result:
216 print(line)