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-" % (self
.count
, self
.added
, self
.deleted
)
23 def activityInArea(log
):
24 """Parses the specified file containing commit logs.
25 The output is expected to be in the format described below:
28 [<lines added>\t<lines deleted>\t<path>]+
33 log: The log formatted as described above.
36 A dictionary containing activity per author is returned.
37 Each author contains a dictionary with activity per path.
38 Each path contains one Activity object with the aggregated results.
41 # Create a dict to store the activity stats per path
44 # Create a place to store the result in
50 # Parse all the lines in the file
54 # Split the line at the tab and store the data
55 splitline
= line
.split('\t')
57 length
= len(line
.lstrip())
59 # There is something on this line, but it contains no separator
60 if size
== 1 and length
> 0:
61 # Get the id address minus the newline
68 addpart
= splitline
[0]
69 deletepart
= splitline
[1]
77 activity
.added
= int(addpart
)
78 activity
.deleted
= int(deletepart
)
80 print("On line '" + str(i
) + "', could not convert number: " + str(e
))
82 activity
.path
= splitline
[2][:-1]
83 activities
.append(activity
)
85 for activity
in activities
:
86 for author
in activity
.id:
87 if not activityByAuthor
.has_key(author
):
88 activityByAuthor
[author
] = {}
90 activityByPath
= activityByAuthor
[author
]
92 # If we have not encountered this path, create an entry
93 if not activityByPath
.has_key(activity
.path
):
95 activityByPath
[activity
.path
] = addme
97 known
= activityByPath
[activity
.path
]
100 result
.added
= known
.added
+ activity
.added
101 result
.deleted
= known
.deleted
+ activity
.deleted
102 result
.count
= known
.count
+ 1
104 # Store it under it's path
105 activityByPath
[activity
.path
] = result
107 # Store it under it's author
108 activityByAuthor
[author
] = activityByPath
110 # Create a fresh activity to store the next round in
114 print("Cannot parse line " + str(i
) + ".")
117 return activityByAuthor
119 def activityInFile(path
, id, start_from
, relative
):
120 """Shows the activity for the file in the current repo.
123 path: The path to filter on.
124 id: The id of the developer to show in the result.
125 startfrom: The commit to start logging from.
126 relative: Treat path as relative to the current working directory.
131 result
= git
.log(start_from
, "--", path
, pretty
="format:%" + id, with_keep_cwd
=relative
)
132 activity
= result
.split('\n')
136 for line
in activity
:
137 # Create an entry if there was none for this author yet
138 if not result
.has_key(line
):
145 def activity(id, field
, startFrom
):
146 """Shows the activity for the specified developer in the current repo.
149 id: The id of the developer, as specified by field.
150 field: The field to filter on.
151 startfrom: The commit to start logging from.
155 result
= git
.log("--numstat", startFrom
, "--", pretty
="format:%" + field
)
157 log
= result
.splitlines(True)
158 allActivity
= activityInArea(log
)
163 keys
= allActivity
.keys()
167 activity
= allActivity
[author
]
168 result
.append(author
+ ":")
169 keys
= activity
.keys()
173 value
= activity
[key
]
174 result
.append("\t" + str(value
) + " = " + str(key
))
180 if not allActivity
.has_key(id):
181 result
.append("Unknown author " + id)
182 result
.append("Known authors:")
183 result
.extend(allActivity
.keys())
187 activity
= allActivity
[id]
189 keys
= activity
.keys()
193 value
= activity
[key
]
194 result
.append(str(value
) + " = " + str(key
))
198 def aggregateActivity(idFilter
, field
, startFrom
):
199 """Aggregates the activity for all developers
202 idFilter: The id to filter on, if None all developers will be shown.
203 field: The field to filter on.
207 result
= git
.log("--numstat", startFrom
, "--", pretty
="format:%" + field
)
209 log
= result
.splitlines(True)
210 allActivity
= activityInArea(log
)
212 aggregatedActivity
= {}
214 for _
, activityByPath
in allActivity
.iteritems():
215 for path
, activity
in activityByPath
.iteritems():
216 if not aggregatedActivity
.has_key(path
):
217 aggregatedActivity
[path
] = Activity()
219 known
= aggregatedActivity
[path
]
222 result
.added
= known
.added
+ activity
.added
223 result
.deleted
= known
.deleted
+ activity
.deleted
224 result
.count
= known
.count
+ activity
.count
226 aggregatedActivity
[path
] = result
230 keys
= aggregatedActivity
.keys()
234 value
= aggregatedActivity
[key
]
235 result
.append(str(value
) + " = " + str(key
))
239 def _checkOptions(parser
, options
):
240 """Checks the specified options and uses the parser to indicate errors
243 parser: The parser to use to signal when the options are bad.
244 options: The options to check.
247 opts
= [options
.aggregate
, options
.developer
, options
.file, options
.everyone
]
249 if not parse
.isUnique(opts
, atLeastOne
=True):
250 parser
.error("Please choose exactly one mode")
254 parse
.check_file(value
=options
.file, relative
=options
.relative
)
255 except OptionValueError
, e
:
260 """Dispatches author related commands
263 progname
= os
.path
.basename(sys
.argv
[0]) + " author"
265 parser
= OptionParser(option_class
=parse
.GitOption
, prog
=progname
)
270 help="aggregate the results")
275 help="show the activity of all developers")
279 help="the id to filter on")
283 help="the file to filter on")
287 help="the one/two letter identifier specifying which field to use as id")
290 "-s", "--start-from",
293 help="the commit to start logging from")
298 help="paths are relative to the current directory")
300 parser
.set_default("id", "ae")
301 parser
.set_default("start_from", "HEAD")
302 parser
.set_default("relative", False)
304 (options
, args
) = parser
.parse_args(list(args
))
306 _checkOptions(parser
, options
)
308 if options
.aggregate
:
309 result
= aggregateActivity(options
.developer
, options
.id, options
.start_from
)
310 elif options
.developer
or options
.everyone
:
311 result
= activity(options
.developer
, options
.id, options
.start_from
)
313 activity_for_file
= activityInFile( options
.file,
320 for key
, value
in activity_for_file
.iteritems():
321 result
.append(key
+ ": " + str(value
))