statgit 20KB

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