Don't fail if DST exists.
[gitstats.git] / statgit
blobed3fe67b718aec5ffdf139bda7e99ee60fd5b075
1 #!/usr/bin/python
2 # Copyright (c) 2007 Heikki Hokkanen <hoxu@users.sf.net>
3 # GPLv2
4 import commands
5 import datetime
6 import glob
7 import os
8 import re
9 import sys
10 import time
12 GNUPLOT_COMMON = 'set terminal png transparent\nset size 0.5,0.5\n'
14 def getoutput(cmd, quiet = False):
15 if not quiet:
16 print '>> %s' % cmd
17 output = commands.getoutput(cmd)
18 return output
20 def getkeyssortedbyvalues(dict):
21 return map(lambda el : el[1], sorted(map(lambda el : (el[1], el[0]), dict.items())))
23 # TODO getdictkeyssortedbyvaluekey(dict, key) - eg. dict['author'] = { 'commits' : 512 } - ...key(dict, 'commits')
25 class DataCollector:
26 def __init__(self):
27 self.stamp_created = time.time()
28 pass
31 # This should be the main function to extract data from the repository.
32 def collect(self, dir):
33 self.dir = dir
36 # : get a dictionary of author
37 def getAuthorInfo(self, author):
38 return None
40 def getActivityByDayOfWeek(self):
41 return {}
43 def getActivityByHourOfDay(self):
44 return {}
47 # Get a list of authors
48 def getAuthors(self):
49 return []
51 def getFirstCommitDate(self):
52 return datetime.datetime.now()
54 def getLastCommitDate(self):
55 return datetime.datetime.now()
57 def getStampCreated(self):
58 return self.stamp_created
60 def getTags(self):
61 return []
63 def getTotalAuthors(self):
64 return -1
66 def getTotalCommits(self):
67 return -1
69 def getTotalFiles(self):
70 return -1
72 def getTotalLOC(self):
73 return -1
75 class GitDataCollector(DataCollector):
76 def collect(self, dir):
77 DataCollector.collect(self, dir)
79 self.total_authors = int(getoutput('git-log |git-shortlog -s |wc -l'))
80 self.total_commits = int(getoutput('git-rev-list HEAD |wc -l'))
81 self.total_files = int(getoutput('git-ls-files |wc -l'))
82 self.total_lines = int(getoutput('git-ls-files -z |xargs -0 cat |wc -l'))
84 self.activity_by_hour_of_day = {} # hour -> commits
85 self.activity_by_day_of_week = {} # day -> commits
86 self.activity_by_month_of_year = {} # month [1-12] -> commits
87 self.activity_by_hour_of_week = {} # weekday -> hour -> commits
89 self.authors = {} # name -> {commits, first_commit_stamp, last_commit_stamp}
91 # author of the month
92 self.author_of_month = {} # month -> author -> commits
93 self.author_of_year = {} # year -> author -> commits
94 self.commits_by_month = {} # month -> commits
95 self.commits_by_year = {} # year -> commits
96 self.first_commit_stamp = 0
97 self.last_commit_stamp = 0
99 # tags
100 self.tags = {}
101 lines = getoutput('git-show-ref --tags').split('\n')
102 for line in lines:
103 if len(line) == 0:
104 continue
105 (hash, tag) = line.split(' ')
106 tag = tag.replace('refs/tags/', '')
107 output = getoutput('git-log "%s" --pretty=format:"%%at %%an" -n 1' % hash)
108 if len(output) > 0:
109 parts = output.split(' ')
110 stamp = 0
111 try:
112 stamp = int(parts[0])
113 except ValueError:
114 stamp = 0
115 self.tags[tag] = { 'stamp': stamp, 'hash' : hash, 'date' : datetime.datetime.fromtimestamp(stamp).strftime('%Y-%m-%d') }
116 pass
118 # Collect revision statistics
119 # Outputs "<stamp> <author>"
120 lines = getoutput('git-rev-list --pretty=format:"%at %an" HEAD |grep -v ^commit').split('\n')
121 for line in lines:
122 # linux-2.6 says "<unknown>" for one line O_o
123 parts = line.split(' ')
124 author = ''
125 try:
126 stamp = int(parts[0])
127 except ValueError:
128 stamp = 0
129 if len(parts) > 1:
130 author = ' '.join(parts[1:])
131 date = datetime.datetime.fromtimestamp(float(stamp))
133 # First and last commit stamp
134 if self.last_commit_stamp == 0:
135 self.last_commit_stamp = stamp
136 self.first_commit_stamp = stamp
138 # activity
139 # hour
140 hour = date.hour
141 if hour in self.activity_by_hour_of_day:
142 self.activity_by_hour_of_day[hour] += 1
143 else:
144 self.activity_by_hour_of_day[hour] = 1
146 # day of week
147 day = date.weekday()
148 if day in self.activity_by_day_of_week:
149 self.activity_by_day_of_week[day] += 1
150 else:
151 self.activity_by_day_of_week[day] = 1
153 # hour of week
154 if day not in self.activity_by_hour_of_week:
155 self.activity_by_hour_of_week[day] = {}
156 if hour not in self.activity_by_hour_of_week[day]:
157 self.activity_by_hour_of_week[day][hour] = 1
158 else:
159 self.activity_by_hour_of_week[day][hour] += 1
161 # month of year
162 month = date.month
163 if month in self.activity_by_month_of_year:
164 self.activity_by_month_of_year[month] += 1
165 else:
166 self.activity_by_month_of_year[month] = 1
168 # author stats
169 if author not in self.authors:
170 self.authors[author] = {}
171 # TODO commits
172 if 'last_commit_stamp' not in self.authors[author]:
173 self.authors[author]['last_commit_stamp'] = stamp
174 self.authors[author]['first_commit_stamp'] = stamp
175 if 'commits' in self.authors[author]:
176 self.authors[author]['commits'] += 1
177 else:
178 self.authors[author]['commits'] = 1
180 # author of the month/year
181 yymm = datetime.datetime.fromtimestamp(stamp).strftime('%Y-%m')
182 if yymm in self.author_of_month:
183 if author in self.author_of_month[yymm]:
184 self.author_of_month[yymm][author] += 1
185 else:
186 self.author_of_month[yymm][author] = 1
187 else:
188 self.author_of_month[yymm] = {}
189 self.author_of_month[yymm][author] = 1
190 if yymm in self.commits_by_month:
191 self.commits_by_month[yymm] += 1
192 else:
193 self.commits_by_month[yymm] = 1
195 yy = datetime.datetime.fromtimestamp(stamp).year
196 if yy in self.author_of_year:
197 if author in self.author_of_year[yy]:
198 self.author_of_year[yy][author] += 1
199 else:
200 self.author_of_year[yy][author] = 1
201 else:
202 self.author_of_year[yy] = {}
203 self.author_of_year[yy][author] = 1
204 if yy in self.commits_by_year:
205 self.commits_by_year[yy] += 1
206 else:
207 self.commits_by_year[yy] = 1
209 # outputs "<stamp> <files>" for each revision
210 self.files_by_stamp = {} # stamp -> files
211 lines = getoutput('git-rev-list --pretty=format:"%at %H" HEAD |grep -v ^commit |while read line; do set $line; echo "$1 $(git-ls-tree -r "$2" |wc -l)"; done').split('\n')
212 for line in lines:
213 parts = line.split(' ')
214 if len(parts) != 2:
215 continue
216 (stamp, files) = parts[0:2]
217 self.files_by_stamp[int(stamp)] = int(files)
219 # extensions
220 self.extensions = {} # extension -> files, lines
221 lines = getoutput('git-ls-files').split('\n')
222 for line in lines:
223 base = os.path.basename(line)
224 if base.find('.') == -1:
225 ext = ''
226 else:
227 ext = base[(base.rfind('.') + 1):]
229 if ext not in self.extensions:
230 self.extensions[ext] = {'files': 0, 'lines': 0}
232 self.extensions[ext]['files'] += 1
233 self.extensions[ext]['lines'] += int(getoutput('wc -l < %s' % line, quiet = True))
235 def getActivityByDayOfWeek(self):
236 return self.activity_by_day_of_week
238 def getActivityByHourOfDay(self):
239 return self.activity_by_hour_of_day
241 def getAuthorInfo(self, author):
242 a = self.authors[author]
244 commits = a['commits']
245 commits_frac = (100 * float(commits)) / self.getTotalCommits()
246 date_first = datetime.datetime.fromtimestamp(a['first_commit_stamp'])
247 date_last = datetime.datetime.fromtimestamp(a['last_commit_stamp'])
248 delta = date_last - date_first
250 res = { 'commits': commits, 'commits_frac': commits_frac, 'date_first': date_first.strftime('%Y-%m-%d'), 'date_last': date_last.strftime('%Y-%m-%d'), 'timedelta' : delta }
251 return res
253 def getAuthors(self):
254 return self.authors.keys()
256 def getFirstCommitDate(self):
257 return datetime.datetime.fromtimestamp(self.first_commit_stamp)
259 def getLastCommitDate(self):
260 return datetime.datetime.fromtimestamp(self.last_commit_stamp)
262 def getTags(self):
263 lines = getoutput('git-show-ref --tags |cut -d/ -f3')
264 return lines.split('\n')
266 def getTagDate(self, tag):
267 return self.revToDate('tags/' + tag)
269 def getTotalAuthors(self):
270 return self.total_authors
272 def getTotalCommits(self):
273 return self.total_commits
275 def getTotalFiles(self):
276 return self.total_files
278 def getTotalLOC(self):
279 return self.total_lines
281 def revToDate(self, rev):
282 stamp = int(getoutput('git-log --pretty=format:%%at "%s" -n 1' % rev))
283 return datetime.datetime.fromtimestamp(stamp).strftime('%Y-%m-%d')
285 class ReportCreator:
286 def __init__(self):
287 pass
289 def create(self, data, path):
290 self.data = data
291 self.path = path
293 class HTMLReportCreator(ReportCreator):
294 def create(self, data, path):
295 ReportCreator.create(self, data, path)
297 f = open(path + "/index.html", 'w')
298 format = '%Y-%m-%d %H:%m:%S'
299 self.printHeader(f)
301 f.write('<h1>StatGit</h1>')
303 self.printNav(f)
305 f.write('<dl>');
306 f.write('<dt>Generated</dt><dd>%s (in %d seconds)</dd>' % (datetime.datetime.now().strftime(format), time.time() - data.getStampCreated()));
307 f.write('<dt>Report Period</dt><dd>%s to %s</dd>' % (data.getFirstCommitDate().strftime(format), data.getLastCommitDate().strftime(format)))
308 f.write('<dt>Total Files</dt><dd>%s</dd>' % data.getTotalFiles())
309 f.write('<dt>Total Lines of Code</dt><dd>%s</dd>' % data.getTotalLOC())
310 f.write('<dt>Total Commits</dt><dd>%s</dd>' % data.getTotalCommits())
311 f.write('<dt>Authors</dt><dd>%s</dd>' % data.getTotalAuthors())
312 f.write('</dl>');
314 f.write('</body>\n</html>');
315 f.close()
318 # Activity
319 f = open(path + '/activity.html', 'w')
320 self.printHeader(f)
321 f.write('<h1>Activity</h1>')
322 self.printNav(f)
324 f.write('<h2>Last 30 days</h2>')
326 f.write('<h2>Last 12 months</h2>')
328 # Hour of Day
329 f.write('\n<h2>Hour of Day</h2>\n\n')
330 hour_of_day = data.getActivityByHourOfDay()
331 f.write('<table><tr><th>Hour</th>')
332 for i in range(1, 25):
333 f.write('<th>%d</th>' % i)
334 f.write('</tr>\n<tr><th>Commits</th>')
335 fp = open(path + '/hour_of_day.dat', 'w')
336 for i in range(0, 24):
337 if i in hour_of_day:
338 f.write('<td>%d</td>' % hour_of_day[i])
339 fp.write('%d %d\n' % (i, hour_of_day[i]))
340 else:
341 f.write('<td>0</td>')
342 fp.write('%d 0\n' % i)
343 fp.close()
344 f.write('</tr>\n<tr><th>%</th>')
345 totalcommits = data.getTotalCommits()
346 for i in range(0, 24):
347 if i in hour_of_day:
348 f.write('<td>%.2f</td>' % ((100.0 * hour_of_day[i]) / totalcommits))
349 else:
350 f.write('<td>0.00</td>')
351 f.write('</tr></table>')
352 f.write('<img src="hour_of_day.png" alt="Hour of Day" />')
353 fg = open(path + '/hour_of_day.dat', 'w')
354 for i in range(0, 24):
355 if i in hour_of_day:
356 fg.write('%d %d\n' % (i + 1, hour_of_day[i]))
357 else:
358 fg.write('%d 0\n' % (i + 1))
359 fg.close()
361 # Day of Week
362 f.write('\n<h2>Day of Week</h2>\n\n')
363 day_of_week = data.getActivityByDayOfWeek()
364 f.write('<div class="vtable"><table>')
365 f.write('<tr><th>Day</th><th>Total (%)</th></tr>')
366 fp = open(path + '/day_of_week.dat', 'w')
367 for d in range(0, 7):
368 fp.write('%d %d\n' % (d + 1, day_of_week[d]))
369 f.write('<tr>')
370 f.write('<th>%d</th>' % (d + 1))
371 if d in day_of_week:
372 f.write('<td>%d (%.2f%%)</td>' % (day_of_week[d], (100.0 * day_of_week[d]) / totalcommits))
373 else:
374 f.write('<td>0</td>')
375 f.write('</tr>')
376 f.write('</table></div>')
377 f.write('<img src="day_of_week.png" alt="Day of Week" />')
378 fp.close()
380 # Hour of Week
381 f.write('\n<h2>Hour of Week</h2>\n\n')
382 f.write('<table>')
384 f.write('<tr><th>Weekday</th>')
385 for hour in range(0, 24):
386 f.write('<th>%d</th>' % (hour + 1))
387 f.write('</tr>')
389 for weekday in range(0, 7):
390 f.write('<tr><th>%d</th>' % (weekday + 1))
391 for hour in range(0, 24):
392 try:
393 commits = data.activity_by_hour_of_week[weekday][hour]
394 except KeyError:
395 commits = 0
396 if commits != 0:
397 f.write('<td>%d</td>' % commits)
398 else:
399 f.write('<td></td>')
400 f.write('</tr>')
402 f.write('</table>')
404 # Month of Year
405 f.write('\n<h2>Month of Year</h2>\n\n')
406 f.write('<div class="vtable"><table>')
407 f.write('<tr><th>Month</th><th>Commits (%)</th></tr>')
408 fp = open (path + '/month_of_year.dat', 'w')
409 for mm in range(1, 13):
410 commits = 0
411 if mm in data.activity_by_month_of_year:
412 commits = data.activity_by_month_of_year[mm]
413 f.write('<tr><td>%d</td><td>%d (%.2f %%)</td></tr>' % (mm, commits, (100.0 * commits) / data.getTotalCommits()))
414 fp.write('%d %d\n' % (mm, commits))
415 fp.close()
416 f.write('</table></div>')
417 f.write('<img src="month_of_year.png" alt="Month of Year" />')
419 # Commits by year/month
420 f.write('<h2>Commits by year/month</h2>')
421 f.write('<div class="vtable"><table><tr><th>Month</th><th>Commits</th></tr>')
422 for yymm in reversed(sorted(data.commits_by_month.keys())):
423 f.write('<tr><td>%s</td><td>%d</td></tr>' % (yymm, data.commits_by_month[yymm]))
424 f.write('</table></div>')
425 f.write('<img src="commits_by_year_month.png" alt="Commits by year/month" />')
426 fg = open(path + '/commits_by_year_month.dat', 'w')
427 for yymm in sorted(data.commits_by_month.keys()):
428 fg.write('%s %s\n' % (yymm, data.commits_by_month[yymm]))
429 fg.close()
431 # Commits by year
432 f.write('<h2>Commits by year</h2>')
433 f.write('<div class="vtable"><table><tr><th>Year</th><th>Commits (% of all)</th></tr>')
434 for yy in reversed(sorted(data.commits_by_year.keys())):
435 f.write('<tr><td>%s</td><td>%d (%.2f%%)</td></tr>' % (yy, data.commits_by_year[yy], (100.0 * data.commits_by_year[yy]) / data.getTotalCommits()))
436 f.write('</table></div>')
437 f.write('<img src="commits_by_year.png" alt="Commits by Year" />')
438 fg = open(path + '/commits_by_year.dat', 'w')
439 for yy in sorted(data.commits_by_year.keys()):
440 fg.write('%d %d\n' % (yy, data.commits_by_year[yy]))
441 fg.close()
443 f.write('</body></html>')
444 f.close()
447 # Authors
448 f = open(path + '/authors.html', 'w')
449 self.printHeader(f)
451 f.write('<h1>Authors</h1>')
452 self.printNav(f)
454 # Authors :: List of authors
455 f.write('\n<h2>List of authors</h2>\n\n')
457 f.write('<table class="authors">')
458 f.write('<tr><th>Author</th><th>Commits (%)</th><th>First commit</th><th>Last commit</th><th>Age</th></tr>')
459 for author in sorted(data.getAuthors()):
460 info = data.getAuthorInfo(author)
461 f.write('<tr><td>%s</td><td>%d (%.2f%%)</td><td>%s</td><td>%s</td><td>%s</td></tr>' % (author, info['commits'], info['commits_frac'], info['date_first'], info['date_last'], info['timedelta']))
462 f.write('</table>')
464 # Authors :: Author of Month
465 f.write('\n<h2>Author of Month</h2>\n\n')
466 f.write('<table>')
467 f.write('<tr><th>Month</th><th>Author</th><th>Commits (%)</th></tr>')
468 for yymm in reversed(sorted(data.author_of_month.keys())):
469 authordict = data.author_of_month[yymm]
470 authors = getkeyssortedbyvalues(authordict)
471 authors.reverse()
472 commits = data.author_of_month[yymm][authors[0]]
473 f.write('<tr><td>%s</td><td>%s</td><td>%d (%.2f%% of %d)</td></tr>' % (yymm, authors[0], commits, (100 * commits) / data.commits_by_month[yymm], data.commits_by_month[yymm]))
475 f.write('</table>')
477 f.write('\n<h2>Author of Year</h2>\n\n')
478 f.write('<table><tr><th>Year</th><th>Author</th><th>Commits (%)</th></tr>')
479 for yy in reversed(sorted(data.author_of_year.keys())):
480 authordict = data.author_of_year[yy]
481 authors = getkeyssortedbyvalues(authordict)
482 authors.reverse()
483 commits = data.author_of_year[yy][authors[0]]
484 f.write('<tr><td>%s</td><td>%s</td><td>%d (%.2f%% of %d)</td></tr>' % (yy, authors[0], commits, (100 * commits) / data.commits_by_year[yy], data.commits_by_year[yy]))
485 f.write('</table>')
487 f.write('</body></html>')
488 f.close()
491 # Files
492 f = open(path + '/files.html', 'w')
493 self.printHeader(f)
494 f.write('<h1>Files</h1>')
495 self.printNav(f)
497 f.write('<dl>\n')
498 f.write('<dt>Total files</dt><dd>%d</dd>' % data.getTotalFiles())
499 f.write('<dt>Total lines</dt><dd>%d</dd>' % data.getTotalLOC())
500 f.write('<dt>Average file size</dt><dd>%.2f bytes</dd>' % ((100.0 * data.getTotalLOC()) / data.getTotalFiles()))
501 f.write('</dl>\n')
503 # Files :: File count by date
504 f.write('<h2>File count by date</h2>')
506 fg = open(path + '/files_by_date.dat', 'w')
507 for stamp in sorted(data.files_by_stamp.keys()):
508 fg.write('%s %d\n' % (datetime.datetime.fromtimestamp(stamp).strftime('%Y-%m-%d'), data.files_by_stamp[stamp]))
509 fg.close()
511 f.write('<img src="files_by_date.png" alt="Files by Date" />')
513 #f.write('<h2>Average file size by date</h2>')
515 # Files :: Extensions
516 f.write('\n<h2>Extensions</h2>\n\n')
517 f.write('<table><tr><th>Extension</th><th>Files (%)</th><th>Lines (%)</th><th>Lines/file</th></tr>')
518 for ext in sorted(data.extensions.keys()):
519 files = data.extensions[ext]['files']
520 lines = data.extensions[ext]['lines']
521 f.write('<tr><td>%s</td><td>%d (%.2f%%)</td><td>%d (%.2f%%)</td><td>%d</td></tr>' % (ext, files, (100.0 * files) / data.getTotalFiles(), lines, (100.0 * lines) / data.getTotalLOC(), lines / files))
522 f.write('</table>')
524 f.write('</body></html>')
525 f.close()
528 # Lines
529 f = open(path + '/lines.html', 'w')
530 self.printHeader(f)
531 f.write('<h1>Lines</h1>')
532 self.printNav(f)
534 f.write('<dl>\n')
535 f.write('<dt>Total lines</dt><dd>%d</dd>' % data.getTotalLOC())
536 f.write('</dl>\n')
538 f.write('</body></html>')
539 f.close()
542 # tags.html
543 f = open(path + '/tags.html', 'w')
544 self.printHeader(f)
545 f.write('<h1>Tags</h1>')
546 self.printNav(f)
548 f.write('<dl>')
549 f.write('<dt>Total tags</dt><dd>%d</dd>' % len(data.tags))
550 if len(data.tags) > 0:
551 f.write('<dt>Average commits per tag</dt><dd>%.2f</dd>' % (data.getTotalCommits() / len(data.tags)))
552 f.write('</dl>')
554 f.write('<table>')
555 f.write('<tr><th>Name</th><th>Date</th></tr>')
556 # sort the tags by date desc
557 tags_sorted_by_date_desc = map(lambda el : el[1], reversed(sorted(map(lambda el : (el[1]['date'], el[0]), data.tags.items()))))
558 for tag in tags_sorted_by_date_desc:
559 f.write('<tr><td>%s</td><td>%s</td></tr>' % (tag, data.tags[tag]['date']))
560 f.write('</table>')
562 f.write('</body></html>')
563 f.close()
565 self.createGraphs(path)
566 pass
568 def createGraphs(self, path):
569 print 'Generating graphs...'
571 # hour of day
572 f = open(path + '/hour_of_day.plot', 'w')
573 f.write(GNUPLOT_COMMON)
574 f.write(
576 set output 'hour_of_day.png'
577 unset key
578 set xrange [0.5:24.5]
579 set xtics 4
580 set ylabel "Commits"
581 plot 'hour_of_day.dat' using 1:2:(0.5) w boxes fs solid
582 """)
583 f.close()
585 # day of week
586 f = open(path + '/day_of_week.plot', 'w')
587 f.write(GNUPLOT_COMMON)
588 f.write(
590 set output 'day_of_week.png'
591 unset key
592 set xrange [0.5:7.5]
593 set xtics 1
594 set ylabel "Commits"
595 plot 'day_of_week.dat' using 1:2:(0.5) w boxes fs solid
596 """)
597 f.close()
599 # Month of Year
600 f = open(path + '/month_of_year.plot', 'w')
601 f.write(GNUPLOT_COMMON)
602 f.write(
604 set output 'month_of_year.png'
605 unset key
606 set xrange [0.5:12.5]
607 set xtics 1
608 set ylabel "Commits"
609 plot 'month_of_year.dat' using 1:2:(0.5) w boxes fs solid
610 """)
611 f.close()
613 # commits_by_year_month
614 f = open(path + '/commits_by_year_month.plot', 'w')
615 f.write(GNUPLOT_COMMON)
616 f.write(
618 set output 'commits_by_year_month.png'
619 unset key
620 set xdata time
621 set timefmt "%Y-%m"
622 set format x "%Y-%m"
623 set xtics rotate by 90 15768000
624 set ylabel "Commits"
625 plot 'commits_by_year_month.dat' using 1:2:(0.5) w boxes fs solid
626 """)
627 f.close()
629 # commits_by_year
630 f = open(path + '/commits_by_year.plot', 'w')
631 f.write(GNUPLOT_COMMON)
632 f.write(
634 set output 'commits_by_year.png'
635 unset key
636 set xtics 1
637 set ylabel "Commits"
638 plot 'commits_by_year.dat' using 1:2:(0.5) w boxes fs solid
639 """)
640 f.close()
642 # Files by date
643 f = open(path + '/files_by_date.plot', 'w')
644 f.write(GNUPLOT_COMMON)
645 f.write(
647 set output 'files_by_date.png'
648 unset key
649 set xdata time
650 set timefmt "%Y-%m-%d"
651 set format x "%Y-%m-%d"
652 set ylabel "Files"
653 set xtics rotate by 90
654 plot 'files_by_date.dat' using 1:2 smooth csplines
655 """)
656 f.close()
658 os.chdir(path)
659 files = glob.glob(path + '/*.plot')
660 for f in files:
661 print '>> gnuplot %s' % os.path.basename(f)
662 os.system('gnuplot %s' % f)
664 def printHeader(self, f):
665 f.write(
666 """<?xml version="1.0" encoding="UTF-8"?>
667 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
668 <html xmlns="http://www.w3.org/1999/xhtml">
669 <head>
670 <title>StatGit</title>
671 <link rel="stylesheet" href="statgit.css" type="text/css" />
672 <meta name="generator" content="statgit" />
673 </head>
674 <body>
675 """)
677 def printNav(self, f):
678 f.write("""
679 <div class="nav">
680 <ul>
681 <li><a href="index.html">General</a></li>
682 <li><a href="activity.html">Activity</a></li>
683 <li><a href="authors.html">Authors</a></li>
684 <li><a href="files.html">Files</a></li>
685 <li><a href="lines.html">Lines</a></li>
686 <li><a href="tags.html">Tags</a></li>
687 </ul>
688 </div>
689 """)
692 usage = """
693 Usage: statgit [options] <gitpath> <outputpath>
695 Options:
698 if len(sys.argv) < 3:
699 print usage
700 sys.exit(0)
702 gitpath = sys.argv[1]
703 outputpath = os.path.abspath(sys.argv[2])
705 try:
706 os.makedirs(outputpath)
707 except OSError:
708 pass
709 if not os.path.isdir(outputpath):
710 print 'FATAL: Output path is not a directory or does not exist'
711 sys.exit(1)
713 print 'Git path: %s' % gitpath
714 print 'Output path: %s' % outputpath
716 os.chdir(gitpath)
718 print 'Collecting data...'
719 data = GitDataCollector()
720 data.collect(gitpath)
722 print 'Generating report...'
723 report = HTMLReportCreator()
724 report.create(data, outputpath)