|
|
@@ -1,7 +1,7 @@
|
|
1
|
1
|
#!/usr/bin/env python
|
|
2
|
2
|
# Copyright (c) 2007 Heikki Hokkanen <hoxu@users.sf.net>
|
|
3
|
3
|
# GPLv2
|
|
4
|
|
-import commands
|
|
|
4
|
+import subprocess
|
|
5
|
5
|
import datetime
|
|
6
|
6
|
import glob
|
|
7
|
7
|
import os
|
|
|
@@ -10,24 +10,36 @@ import shutil
|
|
10
|
10
|
import sys
|
|
11
|
11
|
import time
|
|
12
|
12
|
|
|
|
13
|
+GNUPLOT = 'c:/tools/gnuplot/bin/wgnuplot_pipes.exe'
|
|
13
|
14
|
GNUPLOT_COMMON = 'set terminal png transparent\nset size 0.5,0.5\n'
|
|
14
|
15
|
|
|
15
|
16
|
exectime_internal = 0.0
|
|
16
|
17
|
exectime_external = 0.0
|
|
17
|
18
|
time_start = time.time()
|
|
18
|
19
|
|
|
19
|
|
-def getoutput(cmd, quiet = False):
|
|
|
20
|
+def getpipeoutput(cmds, quiet = False):
|
|
|
21
|
+ def beautify_name(cmds):
|
|
|
22
|
+ ret = cmds[0]
|
|
|
23
|
+ for x in cmds[1:]:
|
|
|
24
|
+ ret += ' | ' + x
|
|
|
25
|
+ return ret
|
|
|
26
|
+
|
|
20
|
27
|
global exectime_external
|
|
21
|
28
|
start = time.time()
|
|
22
|
29
|
if not quiet:
|
|
23
|
|
- print '>> %s' % cmd,
|
|
|
30
|
+ print '>> ', beautify_name(cmds)
|
|
24
|
31
|
sys.stdout.flush()
|
|
25
|
|
- output = commands.getoutput(cmd)
|
|
|
32
|
+ p0 = subprocess.Popen(cmds[0], stdout = subprocess.PIPE)
|
|
|
33
|
+ p = p0
|
|
|
34
|
+ for x in cmds[1:]:
|
|
|
35
|
+ p = subprocess.Popen(x, stdin = p0.stdout, stdout = subprocess.PIPE)
|
|
|
36
|
+ p0 = p
|
|
|
37
|
+ output = p.communicate()[0]
|
|
26
|
38
|
end = time.time()
|
|
27
|
39
|
if not quiet:
|
|
28
|
|
- print '\r[%.5f] >> %s' % (end - start, cmd)
|
|
|
40
|
+ print '\r[%.5f] >> %s' % (end - start, beautify_name(cmds))
|
|
29
|
41
|
exectime_external += (end - start)
|
|
30
|
|
- return output
|
|
|
42
|
+ return output.rstrip('\n')
|
|
31
|
43
|
|
|
32
|
44
|
def getkeyssortedbyvalues(dict):
|
|
33
|
45
|
return map(lambda el : el[1], sorted(map(lambda el : (el[1], el[0]), dict.items())))
|
|
|
@@ -97,7 +109,10 @@ class GitDataCollector(DataCollector):
|
|
97
|
109
|
def collect(self, dir):
|
|
98
|
110
|
DataCollector.collect(self, dir)
|
|
99
|
111
|
|
|
100
|
|
- self.total_authors = int(getoutput('git-log |git-shortlog -s |wc -l'))
|
|
|
112
|
+ try:
|
|
|
113
|
+ self.total_authors = int(getpipeoutput(('git-log', 'git-shortlog -s', 'wc -l')))
|
|
|
114
|
+ except:
|
|
|
115
|
+ self.total_authors = 0
|
|
101
|
116
|
#self.total_lines = int(getoutput('git-ls-files -z |xargs -0 cat |wc -l'))
|
|
102
|
117
|
|
|
103
|
118
|
self.activity_by_hour_of_day = {} # hour -> commits
|
|
|
@@ -117,13 +132,17 @@ class GitDataCollector(DataCollector):
|
|
117
|
132
|
|
|
118
|
133
|
# tags
|
|
119
|
134
|
self.tags = {}
|
|
120
|
|
- lines = getoutput('git-show-ref --tags').split('\n')
|
|
|
135
|
+ lines = getpipeoutput((('git-show-ref', '--tags'),)).split('\n')
|
|
121
|
136
|
for line in lines:
|
|
122
|
137
|
if len(line) == 0:
|
|
123
|
138
|
continue
|
|
124
|
|
- (hash, tag) = line.split(' ')
|
|
|
139
|
+ print "line = ", line
|
|
|
140
|
+ splitted_str = line.split(' ')
|
|
|
141
|
+ print "splitted_str = ", splitted_str
|
|
|
142
|
+ (hash, tag) = splitted_str
|
|
|
143
|
+
|
|
125
|
144
|
tag = tag.replace('refs/tags/', '')
|
|
126
|
|
- output = getoutput('git-log "%s" --pretty=format:"%%at %%an" -n 1' % hash)
|
|
|
145
|
+ output = getpipeoutput((('git-log', '"%s" --pretty=format:"%%at %%an" -n 1' % hash),))
|
|
127
|
146
|
if len(output) > 0:
|
|
128
|
147
|
parts = output.split(' ')
|
|
129
|
148
|
stamp = 0
|
|
|
@@ -136,7 +155,7 @@ class GitDataCollector(DataCollector):
|
|
136
|
155
|
|
|
137
|
156
|
# Collect revision statistics
|
|
138
|
157
|
# Outputs "<stamp> <author>"
|
|
139
|
|
- lines = getoutput('git-rev-list --pretty=format:"%at %an" HEAD |grep -v ^commit').split('\n')
|
|
|
158
|
+ lines = getpipeoutput(('git-rev-list --pretty=format:"%at %an" HEAD', 'grep -v ^commit')).split('\n')
|
|
140
|
159
|
for line in lines:
|
|
141
|
160
|
# linux-2.6 says "<unknown>" for one line O_o
|
|
142
|
161
|
parts = line.split(' ')
|
|
|
@@ -144,6 +163,9 @@ class GitDataCollector(DataCollector):
|
|
144
|
163
|
try:
|
|
145
|
164
|
stamp = int(parts[0])
|
|
146
|
165
|
except ValueError:
|
|
|
166
|
+ print "lines = ", lines
|
|
|
167
|
+ print "line = ", line
|
|
|
168
|
+ raise
|
|
147
|
169
|
stamp = 0
|
|
148
|
170
|
if len(parts) > 1:
|
|
149
|
171
|
author = ' '.join(parts[1:])
|
|
|
@@ -228,7 +250,15 @@ class GitDataCollector(DataCollector):
|
|
228
|
250
|
# TODO Optimize this, it's the worst bottleneck
|
|
229
|
251
|
# outputs "<stamp> <files>" for each revision
|
|
230
|
252
|
self.files_by_stamp = {} # stamp -> files
|
|
231
|
|
- 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')
|
|
|
253
|
+ lines = getpipeoutput(('git-rev-list --pretty=format:"%at %H" HEAD',
|
|
|
254
|
+ 'grep -v ^commit')).strip().split('\n')
|
|
|
255
|
+ #'sh while read line; do set $line; echo "$1 $(git-ls-tree -r "$2" |wc -l)"; done')).split('\n')
|
|
|
256
|
+ tmp = [None] * len(lines)
|
|
|
257
|
+ for idx in xrange(len(lines)):
|
|
|
258
|
+ (a, b) = lines[idx].split(" ")
|
|
|
259
|
+ tmp[idx] = a + getpipeoutput(('git-ls-tree -r ' + b, 'wc -l')).strip('\n')
|
|
|
260
|
+ lines = tmp
|
|
|
261
|
+
|
|
232
|
262
|
self.total_commits = len(lines)
|
|
233
|
263
|
for line in lines:
|
|
234
|
264
|
parts = line.split(' ')
|
|
|
@@ -242,7 +272,7 @@ class GitDataCollector(DataCollector):
|
|
242
|
272
|
|
|
243
|
273
|
# extensions
|
|
244
|
274
|
self.extensions = {} # extension -> files, lines
|
|
245
|
|
- lines = getoutput('git-ls-files').split('\n')
|
|
|
275
|
+ lines = getpipeoutput(('git-ls-files',)).split('\n')
|
|
246
|
276
|
self.total_files = len(lines)
|
|
247
|
277
|
for line in lines:
|
|
248
|
278
|
base = os.path.basename(line)
|
|
|
@@ -257,7 +287,7 @@ class GitDataCollector(DataCollector):
|
|
257
|
287
|
self.extensions[ext]['files'] += 1
|
|
258
|
288
|
try:
|
|
259
|
289
|
# Escaping could probably be improved here
|
|
260
|
|
- self.extensions[ext]['lines'] += int(getoutput('wc -l < %s' % re.sub(r'(\W)', r'\\\1', line), quiet = True))
|
|
|
290
|
+ self.extensions[ext]['lines'] += int(getpipeoutput(('wc -l "%s"' % line,)).split()[0])
|
|
261
|
291
|
except:
|
|
262
|
292
|
print 'Warning: Could not count lines for file "%s"' % line
|
|
263
|
293
|
|
|
|
@@ -266,7 +296,7 @@ class GitDataCollector(DataCollector):
|
|
266
|
296
|
# N files changed, N insertions (+), N deletions(-)
|
|
267
|
297
|
# <stamp> <author>
|
|
268
|
298
|
self.changes_by_date = {} # stamp -> { files, ins, del }
|
|
269
|
|
- lines = getoutput('git-log --shortstat --pretty=format:"%at %an"').split('\n')
|
|
|
299
|
+ lines = getpipeoutput(('git-log --shortstat --pretty=format:"%at %an"',)).split('\n')
|
|
270
|
300
|
lines.reverse()
|
|
271
|
301
|
files = 0; inserted = 0; deleted = 0; total_lines = 0
|
|
272
|
302
|
for line in lines:
|
|
|
@@ -330,7 +360,8 @@ class GitDataCollector(DataCollector):
|
|
330
|
360
|
return datetime.datetime.fromtimestamp(self.last_commit_stamp)
|
|
331
|
361
|
|
|
332
|
362
|
def getTags(self):
|
|
333
|
|
- lines = getoutput('git-show-ref --tags |cut -d/ -f3')
|
|
|
363
|
+ lines = getpipeoutput(('git-show-ref --tags',
|
|
|
364
|
+ 'cut -d/ -f3'))
|
|
334
|
365
|
return lines.split('\n')
|
|
335
|
366
|
|
|
336
|
367
|
def getTagDate(self, tag):
|
|
|
@@ -349,7 +380,7 @@ class GitDataCollector(DataCollector):
|
|
349
|
380
|
return self.total_lines
|
|
350
|
381
|
|
|
351
|
382
|
def revToDate(self, rev):
|
|
352
|
|
- stamp = int(getoutput('git-log --pretty=format:%%at "%s" -n 1' % rev))
|
|
|
383
|
+ stamp = int(getpipeoutput(('git-log --pretty=format:%%at "%s" -n 1' % rev,)))
|
|
353
|
384
|
return datetime.datetime.fromtimestamp(stamp).strftime('%Y-%m-%d')
|
|
354
|
385
|
|
|
355
|
386
|
class ReportCreator:
|
|
|
@@ -375,7 +406,8 @@ class HTMLReportCreator(ReportCreator):
|
|
375
|
406
|
|
|
376
|
407
|
# TODO copy the CSS if it does not exist
|
|
377
|
408
|
if not os.path.exists(path + '/gitstats.css'):
|
|
378
|
|
- shutil.copyfile('gitstats.css', path + '/gitstats.css')
|
|
|
409
|
+ basedir = os.path.dirname(os.path.abspath(__file__))
|
|
|
410
|
+ shutil.copyfile(basedir + '/gitstats.css', path + '/gitstats.css')
|
|
379
|
411
|
pass
|
|
380
|
412
|
|
|
381
|
413
|
f = open(path + "/index.html", 'w')
|
|
|
@@ -775,7 +807,7 @@ plot 'lines_of_code.dat' using 1:2 w lines
|
|
775
|
807
|
os.chdir(path)
|
|
776
|
808
|
files = glob.glob(path + '/*.plot')
|
|
777
|
809
|
for f in files:
|
|
778
|
|
- out = getoutput('gnuplot %s' % f)
|
|
|
810
|
+ out = getpipeoutput((GNUPLOT + ' "%s"' % f,))
|
|
779
|
811
|
if len(out) > 0:
|
|
780
|
812
|
print out
|
|
781
|
813
|
|