6 from optparse
import OptionParser
, OptionValueError
9 from git_stats
import parse
12 """Simple storage class containing stats on the activity in one file."""
21 return "%4d: %5d+ %5d- %5d~" % \
22 (self
.count
, self
.added
, self
.deleted
, self
.added
-self
.deleted
)
24 def activityInArea(log
):
25 """Parses the specified file containing commit logs.
26 The output is expected to be in the format described below:
29 [<lines added>\t<lines deleted>\t<path>]+
34 log: The log formatted as described above.
37 A dictionary containing activity per author is returned.
38 Each author contains a dictionary with activity per path.
39 Each path contains one Activity object with the aggregated results.
42 # Create a dict to store the activity stats per path
45 # Create a place to store the result in
51 # Parse all the lines in the file
55 # Split the line at the tab and store the data
56 splitline
= line
.split('\t')
58 length
= len(line
.lstrip())
60 # There is something on this line, but it contains no separator
61 if size
== 1 and length
> 0:
62 # Get the id address minus the newline
69 addpart
= splitline
[0]
70 deletepart
= splitline
[1]
78 activity
.added
= int(addpart
)
79 activity
.deleted
= int(deletepart
)
81 print("On line '" + str(i
) + "', could not convert number: " + str(e
))
83 activity
.path
= splitline
[2][:-1]
84 activities
.append(activity
)
86 for activity
in activities
:
87 for author
in activity
.id:
88 if not activityByAuthor
.has_key(author
):
89 activityByAuthor
[author
] = {}
91 activityByPath
= activityByAuthor
[author
]
93 # If we have not encountered this path, create an entry
94 if not activityByPath
.has_key(activity
.path
):
96 activityByPath
[activity
.path
] = addme
98 known
= activityByPath
[activity
.path
]
101 result
.added
= known
.added
+ activity
.added
102 result
.deleted
= known
.deleted
+ activity
.deleted
103 result
.count
= known
.count
+ 1
105 # Store it under it's path
106 activityByPath
[activity
.path
] = result
108 # Store it under it's author
109 activityByAuthor
[author
] = activityByPath
111 # Create a fresh activity to store the next round in
115 print("Cannot parse line " + str(i
) + ".")
118 return activityByAuthor
120 def activityInFile(path
, id, start_from
, relative
):
121 """Shows the activity for the file in the current repo.
124 path: The path to filter on.
125 id: The id of the developer to show in the result.
126 startfrom: The commit to start logging from.
127 relative: Treat path as relative to the current working directory.
132 result
= git
.log(start_from
, "--", path
, pretty
="format:%" + id, with_keep_cwd
=relative
)
133 activity
= result
.split('\n')
137 for line
in activity
:
138 # Create an entry if there was none for this author yet
139 if not result
.has_key(line
):
146 def activity(id, field
, startFrom
):
147 """Shows the activity for the specified developer in the current repo.
150 id: The id of the developer, as specified by field.
151 field: The field to filter on.
152 startfrom: The commit to start logging from.
156 result
= git
.log("--numstat", startFrom
, "--", pretty
="format:%" + field
)
158 log
= result
.splitlines(True)
159 allActivity
= activityInArea(log
)
164 keys
= allActivity
.keys()
168 activity
= allActivity
[author
]
169 result
.append(author
+ ":")
170 keys
= activity
.keys()
174 value
= activity
[key
]
175 result
.append("\t" + str(value
) + " = " + str(key
))
181 if not allActivity
.has_key(id):
182 result
.append("Unknown author " + id)
183 result
.append("Known authors:")
184 result
.extend(allActivity
.keys())
188 activity
= allActivity
[id]
190 keys
= activity
.keys()
194 value
= activity
[key
]
195 result
.append(str(value
) + " = " + str(key
))
199 def aggregateActivity(idFilter
, field
, startFrom
):
200 """Aggregates the activity for all developers
203 idFilter: The id to filter on, if None all developers will be shown.
204 field: The field to filter on.
208 result
= git
.log("--numstat", startFrom
, "--", pretty
="format:%" + field
)
210 log
= result
.splitlines(True)
211 allActivity
= activityInArea(log
)
213 aggregatedActivity
= {}
215 for _
, activityByPath
in allActivity
.iteritems():
216 for path
, activity
in activityByPath
.iteritems():
217 if not aggregatedActivity
.has_key(path
):
218 aggregatedActivity
[path
] = Activity()
220 known
= aggregatedActivity
[path
]
223 result
.added
= known
.added
+ activity
.added
224 result
.deleted
= known
.deleted
+ activity
.deleted
225 result
.count
= known
.count
+ activity
.count
227 aggregatedActivity
[path
] = result
231 keys
= aggregatedActivity
.keys()
235 value
= aggregatedActivity
[key
]
236 result
.append(str(value
) + " = " + str(key
))
240 def _checkOptions(parser
, options
):
241 """Checks the specified options and uses the parser to indicate errors
244 parser: The parser to use to signal when the options are bad.
245 options: The options to check.
248 opts
= [options
.aggregate
, options
.developer
, options
.file, options
.everyone
]
250 if not parse
.isUnique(opts
, atLeastOne
=True):
251 parser
.error("Please choose exactly one mode")
255 parse
.check_file(value
=options
.file, relative
=options
.relative
)
256 except OptionValueError
, e
:
261 """Dispatches author related commands
264 progname
= os
.path
.basename(sys
.argv
[0]) + " author"
266 parser
= OptionParser(option_class
=parse
.GitOption
, prog
=progname
)
271 help="aggregate the results")
276 help="show the activity of all developers")
280 help="the id to filter on")
284 help="the file to filter on")
288 help="the one/two letter identifier specifying which field to use as id")
291 "-s", "--start-from",
294 help="the commit to start logging from")
299 help="paths are relative to the current directory")
301 parser
.set_default("id", "ae")
302 parser
.set_default("start_from", "HEAD")
303 parser
.set_default("relative", False)
305 (options
, args
) = parser
.parse_args(list(args
))
307 _checkOptions(parser
, options
)
309 if options
.aggregate
:
310 result
= aggregateActivity(options
.developer
, options
.id, options
.start_from
)
311 elif options
.developer
or options
.everyone
:
312 result
= activity(options
.developer
, options
.id, options
.start_from
)
314 activity_for_file
= activityInFile( options
.file,
321 for key
, value
in activity_for_file
.iteritems():
322 result
.append(key
+ ": " + str(value
))