statgit 19KB

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