123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  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
  11. GNUPLOT_COMMON = 'set terminal png transparent\nset size 0.5,0.5\n'
  12. def getoutput(cmd):
  13. print '>> %s' % cmd
  14. output = commands.getoutput(cmd)
  15. return output
  16. def getkeyssortedbyvalues(dict):
  17. return map(lambda el : el[1], sorted(map(lambda el : (el[1], el[0]), dict.items())))
  18. # TODO getdictkeyssortedbyvaluekey(dict, key) - eg. dict['author'] = { 'commits' : 512 } - ...key(dict, 'commits')
  19. class DataCollector:
  20. def __init__(self):
  21. self.stamp_created = time.time()
  22. pass
  23. ##
  24. # This should be the main function to extract data from the repository.
  25. def collect(self, dir):
  26. self.dir = dir
  27. ##
  28. # : get a dictionary of author
  29. def getAuthorInfo(self, author):
  30. return None
  31. def getActivityByDayOfWeek(self):
  32. return {}
  33. def getActivityByHourOfDay(self):
  34. return {}
  35. ##
  36. # Get a list of authors
  37. def getAuthors(self):
  38. return []
  39. def getFirstCommitDate(self):
  40. return datetime.datetime.now()
  41. def getLastCommitDate(self):
  42. return datetime.datetime.now()
  43. def getStampCreated(self):
  44. return self.stamp_created
  45. def getTags(self):
  46. return []
  47. def getTotalAuthors(self):
  48. return -1
  49. def getTotalCommits(self):
  50. return -1
  51. def getTotalFiles(self):
  52. return -1
  53. def getTotalLOC(self):
  54. return -1
  55. class GitDataCollector(DataCollector):
  56. def collect(self, dir):
  57. DataCollector.collect(self, dir)
  58. self.total_authors = int(getoutput('git-log |git-shortlog -s |wc -l'))
  59. self.total_commits = int(getoutput('git-rev-list HEAD |wc -l'))
  60. self.total_files = int(getoutput('git-ls-files |wc -l'))
  61. self.total_lines = int(getoutput('git-ls-files -z |xargs -0 cat |wc -l'))
  62. self.activity_by_hour_of_day = {} # hour -> commits
  63. self.activity_by_day_of_week = {} # day -> commits
  64. self.activity_by_month_of_year = {} # month [1-12] -> commits
  65. self.authors = {} # name -> {commits, first_commit_stamp, last_commit_stamp}
  66. # author of the month
  67. self.author_of_month = {} # month -> author -> commits
  68. self.author_of_year = {} # year -> author -> commits
  69. self.commits_by_month = {} # month -> commits
  70. self.commits_by_year = {} # year -> commits
  71. self.first_commit_stamp = 0
  72. self.last_commit_stamp = 0
  73. # tags
  74. self.tags = {}
  75. lines = getoutput('git-show-ref --tags').split('\n')
  76. for line in lines:
  77. if len(line) == 0:
  78. continue
  79. (hash, tag) = line.split(' ')
  80. tag = tag.replace('refs/tags/', '')
  81. output = getoutput('git-log "%s" --pretty=format:"%%at %%an" -n 1' % hash)
  82. if len(output) > 0:
  83. parts = output.split(' ')
  84. stamp = 0
  85. try:
  86. stamp = int(parts[0])
  87. except ValueError:
  88. stamp = 0
  89. self.tags[tag] = { 'stamp': stamp, 'hash' : hash, 'date' : datetime.datetime.fromtimestamp(stamp).strftime('%Y-%m-%d') }
  90. pass
  91. # TODO also collect statistics for "last 30 days"/"last 12 months"
  92. lines = getoutput('git-rev-list --pretty=format:"%at %an" HEAD |grep -v ^commit').split('\n')
  93. for line in lines:
  94. # linux-2.6 says "<unknown>" for one line O_o
  95. parts = line.split(' ')
  96. author = ''
  97. try:
  98. stamp = int(parts[0])
  99. except ValueError:
  100. stamp = 0
  101. if len(parts) > 1:
  102. author = ' '.join(parts[1:])
  103. date = datetime.datetime.fromtimestamp(float(stamp))
  104. # First and last commit stamp
  105. if self.last_commit_stamp == 0:
  106. self.last_commit_stamp = stamp
  107. self.first_commit_stamp = stamp
  108. # activity
  109. # hour
  110. hour = date.hour
  111. if hour in self.activity_by_hour_of_day:
  112. self.activity_by_hour_of_day[hour] += 1
  113. else:
  114. self.activity_by_hour_of_day[hour] = 1
  115. # day
  116. day = date.weekday()
  117. if day in self.activity_by_day_of_week:
  118. self.activity_by_day_of_week[day] += 1
  119. else:
  120. self.activity_by_day_of_week[day] = 1
  121. # month of year
  122. month = date.month
  123. if month in self.activity_by_month_of_year:
  124. self.activity_by_month_of_year[month] += 1
  125. else:
  126. self.activity_by_month_of_year[month] = 1
  127. # author stats
  128. if author not in self.authors:
  129. self.authors[author] = {}
  130. # TODO commits
  131. if 'last_commit_stamp' not in self.authors[author]:
  132. self.authors[author]['last_commit_stamp'] = stamp
  133. self.authors[author]['first_commit_stamp'] = stamp
  134. if 'commits' in self.authors[author]:
  135. self.authors[author]['commits'] += 1
  136. else:
  137. self.authors[author]['commits'] = 1
  138. # author of the month/year
  139. yymm = datetime.datetime.fromtimestamp(stamp).strftime('%Y-%m')
  140. if yymm in self.author_of_month:
  141. if author in self.author_of_month[yymm]:
  142. self.author_of_month[yymm][author] += 1
  143. else:
  144. self.author_of_month[yymm][author] = 1
  145. else:
  146. self.author_of_month[yymm] = {}
  147. self.author_of_month[yymm][author] = 1
  148. if yymm in self.commits_by_month:
  149. self.commits_by_month[yymm] += 1
  150. else:
  151. self.commits_by_month[yymm] = 1
  152. yy = datetime.datetime.fromtimestamp(stamp).year
  153. if yy in self.author_of_year:
  154. if author in self.author_of_year[yy]:
  155. self.author_of_year[yy][author] += 1
  156. else:
  157. self.author_of_year[yy][author] = 1
  158. else:
  159. self.author_of_year[yy] = {}
  160. self.author_of_year[yy][author] = 1
  161. if yy in self.commits_by_year:
  162. self.commits_by_year[yy] += 1
  163. else:
  164. self.commits_by_year[yy] = 1
  165. # outputs "<stamp> <files>" for each revision
  166. self.files_by_stamp = {} # stamp -> files
  167. 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')
  168. for line in lines:
  169. parts = line.split(' ')
  170. if len(parts) != 2:
  171. continue
  172. (stamp, files) = parts[0:2]
  173. self.files_by_stamp[int(stamp)] = int(files)
  174. def getActivityByDayOfWeek(self):
  175. return self.activity_by_day_of_week
  176. def getActivityByHourOfDay(self):
  177. return self.activity_by_hour_of_day
  178. def getAuthorInfo(self, author):
  179. a = self.authors[author]
  180. commits = a['commits']
  181. commits_frac = (100 * float(commits)) / self.getTotalCommits()
  182. date_first = datetime.datetime.fromtimestamp(a['first_commit_stamp']).strftime('%Y-%m-%d')
  183. date_last = datetime.datetime.fromtimestamp(a['last_commit_stamp']).strftime('%Y-%m-%d')
  184. res = { 'commits': commits, 'commits_frac': commits_frac, 'date_first': date_first, 'date_last': date_last }
  185. return res
  186. def getAuthors(self):
  187. return self.authors.keys()
  188. def getFirstCommitDate(self):
  189. return datetime.datetime.fromtimestamp(self.first_commit_stamp)
  190. def getLastCommitDate(self):
  191. return datetime.datetime.fromtimestamp(self.last_commit_stamp)
  192. def getTags(self):
  193. lines = getoutput('git-show-ref --tags |cut -d/ -f3')
  194. return lines.split('\n')
  195. def getTagDate(self, tag):
  196. return self.revToDate('tags/' + tag)
  197. def getTotalAuthors(self):
  198. return self.total_authors
  199. def getTotalCommits(self):
  200. return self.total_commits
  201. def getTotalFiles(self):
  202. return self.total_files
  203. def getTotalLOC(self):
  204. return self.total_lines
  205. def revToDate(self, rev):
  206. stamp = int(getoutput('git-log --pretty=format:%%at "%s" -n 1' % rev))
  207. return datetime.datetime.fromtimestamp(stamp).strftime('%Y-%m-%d')
  208. class ReportCreator:
  209. def __init__(self):
  210. pass
  211. def create(self, data, path):
  212. self.data = data
  213. self.path = path
  214. class HTMLReportCreator(ReportCreator):
  215. def create(self, data, path):
  216. ReportCreator.create(self, data, path)
  217. f = open(path + "/index.html", 'w')
  218. format = '%Y-%m-%d %H:%m:%S'
  219. self.printHeader(f)
  220. f.write('<h1>StatGit</h1>')
  221. self.printNav(f)
  222. f.write('<dl>');
  223. f.write('<dt>Generated</dt><dd>%s (in %d seconds)</dd>' % (datetime.datetime.now().strftime(format), time.time() - data.getStampCreated()));
  224. f.write('<dt>Report Period</dt><dd>%s to %s</dd>' % (data.getFirstCommitDate().strftime(format), data.getLastCommitDate().strftime(format)))
  225. f.write('<dt>Total Files</dt><dd>%s</dd>' % data.getTotalFiles())
  226. f.write('<dt>Total Lines of Code</dt><dd>%s</dd>' % data.getTotalLOC())
  227. f.write('<dt>Total Commits</dt><dd>%s</dd>' % data.getTotalCommits())
  228. f.write('<dt>Authors</dt><dd>%s</dd>' % data.getTotalAuthors())
  229. f.write('</dl>');
  230. f.write('</body>\n</html>');
  231. f.close()
  232. ###
  233. # Activity
  234. f = open(path + '/activity.html', 'w')
  235. self.printHeader(f)
  236. f.write('<h1>Activity</h1>')
  237. self.printNav(f)
  238. f.write('<h2>Last 30 days</h2>')
  239. f.write('<h2>Last 12 months</h2>')
  240. # Hour of Day
  241. f.write('\n<h2>Hour of Day</h2>\n\n')
  242. hour_of_day = data.getActivityByHourOfDay()
  243. f.write('<table><tr><th>Hour</th>')
  244. for i in range(1, 25):
  245. f.write('<th>%d</th>' % i)
  246. f.write('</tr>\n<tr><th>Commits</th>')
  247. fp = open(path + '/hour_of_day.dat', 'w')
  248. for i in range(0, 24):
  249. if i in hour_of_day:
  250. f.write('<td>%d</td>' % hour_of_day[i])
  251. fp.write('%d %d\n' % (i, hour_of_day[i]))
  252. else:
  253. f.write('<td>0</td>')
  254. fp.write('%d 0\n' % i)
  255. fp.close()
  256. f.write('</tr>\n<tr><th>%</th>')
  257. totalcommits = data.getTotalCommits()
  258. for i in range(0, 24):
  259. if i in hour_of_day:
  260. f.write('<td>%.2f</td>' % ((100.0 * hour_of_day[i]) / totalcommits))
  261. else:
  262. f.write('<td>0.00</td>')
  263. f.write('</tr></table>')
  264. f.write('<img src="hour_of_day.png" />')
  265. fg = open(path + '/hour_of_day.dat', 'w')
  266. for i in range(0, 24):
  267. if i in hour_of_day:
  268. fg.write('%d %d\n' % (i + 1, hour_of_day[i]))
  269. else:
  270. fg.write('%d 0\n' % (i + 1))
  271. fg.close()
  272. # Day of Week
  273. # TODO show also by hour of weekday?
  274. f.write('\n<h2>Day of Week</h2>\n\n')
  275. day_of_week = data.getActivityByDayOfWeek()
  276. f.write('<div class="vtable"><table>')
  277. f.write('<tr><th>Day</th><th>Total (%)</th></tr>')
  278. fp = open(path + '/day_of_week.dat', 'w')
  279. for d in range(0, 7):
  280. fp.write('%d %d\n' % (d + 1, day_of_week[d]))
  281. f.write('<tr>')
  282. f.write('<th>%d</th>' % (d + 1))
  283. if d in day_of_week:
  284. f.write('<td>%d (%.2f%%)</td>' % (day_of_week[d], (100.0 * day_of_week[d]) / totalcommits))
  285. else:
  286. f.write('<td>0</td>')
  287. f.write('</tr>')
  288. f.write('</table></div>')
  289. f.write('<img src="day_of_week.png" />')
  290. fp.close()
  291. # Month of Year
  292. f.write('\n<h2>Month of Year</h2>\n\n')
  293. f.write('<div class="vtable"><table>')
  294. f.write('<tr><th>Month</th><th>Commits (%)</th></tr>')
  295. fp = open (path + '/month_of_year.dat', 'w')
  296. for mm in range(1, 13):
  297. commits = 0
  298. if mm in data.activity_by_month_of_year:
  299. commits = data.activity_by_month_of_year[mm]
  300. f.write('<tr><td>%d</td><td>%d (%.2f %%)</td></tr>' % (mm, commits, (100.0 * commits) / data.getTotalCommits()))
  301. fp.write('%d %d\n' % (mm, commits))
  302. fp.close()
  303. f.write('</table></div>')
  304. f.write('<img src="month_of_year.png" />')
  305. # Commits by year/month
  306. f.write('<h2>Commits by year/month</h2>')
  307. f.write('<div class="vtable"><table><tr><th>Month</th><th>Commits</th></tr>')
  308. for yymm in reversed(sorted(data.commits_by_month.keys())):
  309. f.write('<tr><td>%s</td><td>%d</td></tr>' % (yymm, data.commits_by_month[yymm]))
  310. f.write('</table></div>')
  311. f.write('<img src="commits_by_year_month.png" />')
  312. fg = open(path + '/commits_by_year_month.dat', 'w')
  313. for yymm in sorted(data.commits_by_month.keys()):
  314. fg.write('%s %s\n' % (yymm, data.commits_by_month[yymm]))
  315. fg.close()
  316. # Commits by year
  317. f.write('<h2>Commits by year</h2>')
  318. f.write('<div class="vtable"><table><tr><th>Year</th><th>Commits (% of all)</th></tr>')
  319. for yy in reversed(sorted(data.commits_by_year.keys())):
  320. 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()))
  321. f.write('</table></div>')
  322. f.write('<img src="commits_by_year.png" />')
  323. fg = open(path + '/commits_by_year.dat', 'w')
  324. for yy in sorted(data.commits_by_year.keys()):
  325. fg.write('%d %d\n' % (yy, data.commits_by_year[yy]))
  326. fg.close()
  327. f.write('</body></html>')
  328. f.close()
  329. ###
  330. # Authors
  331. f = open(path + '/authors.html', 'w')
  332. self.printHeader(f)
  333. f.write('<h1>Authors</h1>')
  334. self.printNav(f)
  335. f.write('\n<h2>List of authors</h2>\n\n')
  336. f.write('<table class="authors">')
  337. f.write('<tr><th>Author</th><th>Commits (%)</th><th>First commit</th><th>Last commit</th></tr>')
  338. for author in sorted(data.getAuthors()):
  339. info = data.getAuthorInfo(author)
  340. f.write('<tr><td>%s</td><td>%d (%.2f%%)</td><td>%s</td><td>%s</td></tr>' % (author, info['commits'], info['commits_frac'], info['date_first'], info['date_last']))
  341. f.write('</table>')
  342. f.write('\n<h2>Author of Month</h2>\n\n')
  343. f.write('<table>')
  344. f.write('<tr><th>Month</th><th>Author</th><th>Commits (%)</th></tr>')
  345. for yymm in reversed(sorted(data.author_of_month.keys())):
  346. authordict = data.author_of_month[yymm]
  347. authors = getkeyssortedbyvalues(authordict)
  348. authors.reverse()
  349. commits = data.author_of_month[yymm][authors[0]]
  350. 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]))
  351. f.write('</table>')
  352. f.write('\n<h2>Author of Year</h2>\n\n')
  353. f.write('<table><tr><th>Year</th><th>Author</th><th>Commits (%)</th></tr>')
  354. for yy in reversed(sorted(data.author_of_year.keys())):
  355. authordict = data.author_of_year[yy]
  356. authors = getkeyssortedbyvalues(authordict)
  357. authors.reverse()
  358. commits = data.author_of_year[yy][authors[0]]
  359. 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]))
  360. f.write('</table>')
  361. f.write('</body></html>')
  362. f.close()
  363. ###
  364. # Files
  365. f = open(path + '/files.html', 'w')
  366. self.printHeader(f)
  367. f.write('<h1>Files</h1>')
  368. self.printNav(f)
  369. f.write('<dl>\n')
  370. f.write('<dt>Total files</dt><dd>%d</dd>' % data.getTotalFiles())
  371. f.write('<dt>Total lines</dt><dd>%d</dd>' % data.getTotalLOC())
  372. f.write('<dt>Average file size</dt><dd>%.2f bytes</dd>' % ((100.0 * data.getTotalLOC()) / data.getTotalFiles()))
  373. f.write('</dl>\n')
  374. f.write('<h2>File count by date</h2>')
  375. fg = open(path + '/files_by_date.dat', 'w')
  376. for stamp in sorted(data.files_by_stamp.keys()):
  377. fg.write('%s %d\n' % (datetime.datetime.fromtimestamp(stamp).strftime('%Y-%m-%d'), data.files_by_stamp[stamp]))
  378. fg.close()
  379. f.write('<img src="files_by_date.png" />')
  380. f.write('<h2>Average file size by date</h2>')
  381. f.write('</body></html>')
  382. f.close()
  383. ###
  384. # tags.html
  385. f = open(path + '/tags.html', 'w')
  386. self.printHeader(f)
  387. f.write('<h1>Tags</h1>')
  388. self.printNav(f)
  389. f.write('<dl>')
  390. f.write('<dt>Total tags</dt><dd>%d</dd>' % len(data.tags))
  391. if len(data.tags) > 0:
  392. f.write('<dt>Average commits per tag</dt><dd>%.2f</dd>' % (data.getTotalCommits() / len(data.tags)))
  393. f.write('</dl>')
  394. f.write('<table>')
  395. f.write('<tr><th>Name</th><th>Date</th></tr>')
  396. # sort the tags by date desc
  397. tags_sorted_by_date_desc = map(lambda el : el[1], reversed(sorted(map(lambda el : (el[1]['date'], el[0]), data.tags.items()))))
  398. for tag in tags_sorted_by_date_desc:
  399. f.write('<tr><td>%s</td><td>%s</td></tr>' % (tag, data.tags[tag]['date']))
  400. f.write('</table>')
  401. f.write('</body></html>')
  402. f.close()
  403. self.createGraphs(path)
  404. pass
  405. def createGraphs(self, path):
  406. print 'Generating graphs...'
  407. # hour of day
  408. f = open(path + '/hour_of_day.plot', 'w')
  409. f.write(GNUPLOT_COMMON)
  410. f.write(
  411. """
  412. set output 'hour_of_day.png'
  413. unset key
  414. set xrange [0.5:24.5]
  415. set xtics 4
  416. set ylabel "Commits"
  417. plot 'hour_of_day.dat' using 1:2:(0.5) w boxes fs solid
  418. """)
  419. f.close()
  420. # day of week
  421. f = open(path + '/day_of_week.plot', 'w')
  422. f.write(GNUPLOT_COMMON)
  423. f.write(
  424. """
  425. set output 'day_of_week.png'
  426. unset key
  427. set xrange [0.5:7.5]
  428. set xtics 1
  429. set ylabel "Commits"
  430. plot 'day_of_week.dat' using 1:2:(0.5) w boxes fs solid
  431. """)
  432. f.close()
  433. # Month of Year
  434. f = open(path + '/month_of_year.plot', 'w')
  435. f.write(GNUPLOT_COMMON)
  436. f.write(
  437. """
  438. set output 'month_of_year.png'
  439. unset key
  440. set xrange [0.5:12.5]
  441. set xtics 1
  442. set ylabel "Commits"
  443. plot 'month_of_year.dat' using 1:2:(0.5) w boxes fs solid
  444. """)
  445. f.close()
  446. # commits_by_year_month
  447. f = open(path + '/commits_by_year_month.plot', 'w')
  448. f.write(GNUPLOT_COMMON)
  449. f.write(
  450. # TODO rotate xtic labels by 90 degrees
  451. """
  452. set output 'commits_by_year_month.png'
  453. unset key
  454. set xdata time
  455. set timefmt "%Y-%m"
  456. set format x "%Y-%m"
  457. set xtics 15768000
  458. set ylabel "Commits"
  459. plot 'commits_by_year_month.dat' using 1:2:(0.5) w boxes fs solid
  460. """)
  461. f.close()
  462. # commits_by_year
  463. f = open(path + '/commits_by_year.plot', 'w')
  464. f.write(GNUPLOT_COMMON)
  465. f.write(
  466. """
  467. set output 'commits_by_year.png'
  468. unset key
  469. set xtics 1
  470. set ylabel "Commits"
  471. plot 'commits_by_year.dat' using 1:2:(0.5) w boxes fs solid
  472. """)
  473. f.close()
  474. # Files by date
  475. f = open(path + '/files_by_date.plot', 'w')
  476. f.write(GNUPLOT_COMMON)
  477. f.write(
  478. """
  479. set output 'files_by_date.png'
  480. unset key
  481. set xdata time
  482. set timefmt "%Y-%m-%d"
  483. set format x "%Y-%m-%d"
  484. set ylabel "Files"
  485. set xtics rotate by 90
  486. plot 'files_by_date.dat' using 1:2 smooth csplines
  487. """)
  488. f.close()
  489. os.chdir(path)
  490. files = glob.glob(path + '/*.plot')
  491. for f in files:
  492. print '>> gnuplot %s' % os.path.basename(f)
  493. os.system('gnuplot %s' % f)
  494. def printHeader(self, f):
  495. f.write("""<html>
  496. <head>
  497. <title>StatGit</title>
  498. <link rel="stylesheet" href="statgit.css" type="text/css" />
  499. </head>
  500. <body>
  501. """)
  502. def printNav(self, f):
  503. f.write("""
  504. <div class="nav">
  505. <li><a href="index.html">General</a></li>
  506. <li><a href="activity.html">Activity</a></li>
  507. <li><a href="authors.html">Authors</a></li>
  508. <li><a href="files.html">Files</a></li>
  509. <li><a href="lines.html">Lines</a></li>
  510. <li><a href="tags.html">Tags</a></li>
  511. </ul>
  512. </div>
  513. """)
  514. usage = """
  515. Usage: statgit [options] <gitpath> <outputpath>
  516. Options:
  517. -o html
  518. """
  519. if len(sys.argv) < 3:
  520. print usage
  521. sys.exit(0)
  522. gitpath = sys.argv[1]
  523. outputpath = os.path.abspath(sys.argv[2])
  524. print 'Git path: %s' % gitpath
  525. print 'Output path: %s' % outputpath
  526. os.chdir(gitpath)
  527. print 'Collecting data...'
  528. data = GitDataCollector()
  529. data.collect(gitpath)
  530. print 'Generating report...'
  531. report = HTMLReportCreator()
  532. report.create(data, outputpath)