-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgitinfo.rb
More file actions
396 lines (332 loc) · 11.4 KB
/
Copy pathgitinfo.rb
File metadata and controls
396 lines (332 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
#!/usr/bin/env ruby
require 'sqlite3'
require 'csv'
require 'semver'
def create_commits_table db
db.execute <<-SQL1
CREATE TABLE IF NOT EXISTS commits (
id TEXT UNIQUE,
tag TEXT,
summary TEXT,
author_name TEXT,
author_email TEXT,
author_when DATETIME
);
SQL1
db.execute <<-SQL2
CREATE TABLE IF NOT EXISTS commit_files (
id TEXT,
name TEXT,
added INT,
deleted INT
);
SQL2
end
def create_tags_table db
db.execute <<-SQL3
CREATE TABLE IF NOT EXISTS tags (
name TEXT UNIQUE,
sha TEXT,
object TEXT,
author_email TEXT,
major_version INTEGER,
minor_version INTEGER,
patch_version INTEGER,
rc_version INTEGER,
created_at DATETIME
);
SQL3
end
def create_authors_table db
db.execute <<-SQL4
CREATE TABLE IF NOT EXISTS authors (
email text unique,
name text,
total_commits integer,
total_tags integer default 0,
earliest DATETIME,
latest DATETIME,
timezone_offset text
);
SQL4
end
def create_file_stats_table db
db.execute <<-SQL5
CREATE TABLE IF NOT EXISTS file_stats_summary (
sha text,
tag text,
author_name text,
author_email text,
author_when text,
code_lines integer,
comment_lines integer,
blank_lines integer
);
SQL5
db.execute <<-SQL6
CREATE TABLE IF NOT EXISTS file_stats (
sha text,
filename text,
language text,
code_lines integer,
comment_lines integer,
blank_lines integer
);
SQL6
end
def create_timeline_table db
db.execute <<-SQL7
CREATE TABLE IF NOT EXISTS timeline (
event_at DATETIME,
event_verb TEXT,
event_author TEXT,
event_subject TEXT,
concurrent_contributors INTEGER,
commits INTEGER,
entities_changed INTEGER
);
SQL7
db.execute( "DELETE FROM timeline;" );
end
repo_dir = ENV['REPO_DIR'] || "."
database = File.join( ENV['OUTPUT_DIR'] || ".", "repository.sqlite" )
puts "Repodir: #{repo_dir}"
if !File.exists?( File.join( repo_dir, ".git" ) )
if !ENV['REPO_URL']
puts "Couldn't find a repo at #{repo_dir} and REPO_URL is unset"
exit 1
end
puts "Cloning repo #{ENV['REPO_URL']} into #{repo_dir}"
value = system( "git clone #{ENV['REPO_URL']} #{repo_dir}" )
else
system( "cd #{repo_dir};git pull origin $(git branch --show-current) --ff-only" )
end
head = `(cd #{repo_dir};git rev-parse HEAD)`.chomp
db = SQLite3::Database.new database
create_commits_table db
create_tags_table db
create_authors_table db
create_file_stats_table db
create_timeline_table db
def add_commit db, id, email, name, date, summary
ret = db.execute("INSERT INTO commits (id, summary, author_name, author_email, author_when)
VALUES (?, ?, ?, ?, ?)", [id, summary, name, email, date ] )
end
def add_file_commit db, id, file, added, deleted
ret = db.execute("INSERT INTO commit_files (id, name, added, deleted)
VALUES (?, ?, ?, ?)", [id, file, added, deleted] )
end
def load_commits db, repo_dir
commit = nil
`(cd #{repo_dir};git log --pretty=format:'|%H|%ae|%an|%aI|%s' --numstat)`.each_line do |line|
line.chomp!
if line[0] == '|'
md = /\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*)/.match( line )
commit = md[1]
puts line
begin
add_commit db, md[1], md[2], md[3], md[4], md[5]
rescue SQLite3::ConstraintException
puts "Found existing commit, exiting"
return
end
elsif line.length != 0
md = /([\d|-]*)\s*([\d|-]*)\s*(.*)/.match( line )
add_file_commit db, commit, md[3], md[1], md[2]
end
end
end
puts "Finding commits"
db.transaction
load_commits db, repo_dir
db.commit
# figure out the major/minor
def guess_version tag_name
version = SemVer.parse_rubygems tag_name
return { major_version: version.major,
minor_version: version.minor,
patch_version: version.patch,
rc_version: version.special }
end
# Insert into the database
def add_tag db, name, sha, object, created_at, author_email
version = guess_version name
ret = db.execute("INSERT INTO tags
(name, sha, object, created_at, author_email,
major_version, minor_version, patch_version, rc_version)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
[name, sha, object, created_at, author_email,
version[:major_version], version[:minor_version],
version[:patch_version], version[:rc_version]])
end
# Parse the output of git tag
def load_tags db, repo_dir
`(cd #{repo_dir};git tag --sort=-v:refname --format='%(refname:short):%(objectname):%(*objectname):%(creatordate:iso8601-strict)')`.each_line do |line|
line.chomp!
md = line.match( /(.*?):(.*?):(.*?):(.*)/ )
if( !md )
puts "Unrecognized line #{line}"
else
tag_name = md[1]
sha = md[2]
sha_object = md[3] unless md[3] == "" # have annotated tags point to the main commit
created_at = md[4]
tagger = db.execute( "select author_email from commits where id = (?) or id = (?)", [sha, sha_object == "" ? sha : sha_object] ).first
begin
add_tag db, tag_name, sha, sha_object, created_at, tagger
rescue SQLite3::ConstraintException
puts "Found existing tag, exiting"
return
end
end
end
end
# Match up the commits to the tags for easier querying
def tag_commits db
last_date = nil
db.execute( "select name, created_at from tags where created_at is not null order by created_at asc" ).each do |row|
if last_date.nil?
db.execute( "update commits set tag = (?) where author_when <= (?)", [row[0], row[1]] )
else
db.execute( "update commits set tag = (?) where author_when <= (?) and author_when > (?)", [row[0], row[1], last_date] )
end
last_date = row[1]
end
db.execute( "update commits set tag = 'HEAD' where author_when > (?)", [last_date] )
end
puts "Finding tags"
db.transaction
db.execute( "delete from tags" );
load_tags db, repo_dir
tag_commits db
db.commit
db.transaction
puts "Summarizing authors"
db.execute( "delete from authors" );
db.execute( " select
author_name,
author_email,
count(commits.id) as commit_count,
count(distinct(tag)) as tag_count,
min( author_when ) as first_contrib,
max( author_when ) as last_contrib
from commits
group by author_email" ).each do |row|
name, email, cnt, tag_count, earliest, latest = row
ending = earliest[-6..-1] || earliest
timezone_offset = ""
if( ending =~ /[-+]\d\d:\d\d/ )
timezone_offset = ending
end
db.execute( "INSERT INTO authors
(email,name,total_commits,total_tags,earliest,latest,timezone_offset)
values
(?,?,?,?,?,?,?)", [email, name, cnt, tag_count, earliest, latest, timezone_offset] );
end
db.commit
def import_cloc_output repo_dir, db, sha
puts "Finding stats for #{sha}"
output = `(cd #{repo_dir};cloc --skip-uniqueness --quiet --by-file --csv --git #{sha})`
CSV.parse(output).each do |row|
next if row == []
next if row[0] == 'language'
if row[0] == 'SUM'
return { code_lines: row[4], comment_lines: row[3], blank_lines: row[2] }
else
ret = db.execute(
"INSERT INTO file_stats (sha, filename, language, code_lines, comment_lines, blank_lines)
VALUES (?, ?, ?, ?, ?, ?)",
sha, row[1], row[0], row[4], row[3], row[2] )
end
end
end
def find_commit db, sha, obj
row = db.execute( "select author_name, author_email, author_when from commits where id=(?) or id=(?)", [sha, obj] ).first
return nil if row.nil?
return {author_name: row[0], author_email: row[1], author_when: row[2]}
end
def add_summary db, summary, name, sha, commit
commit ||= {}
author_name = commit[:author_name]
author_email = commit[:author_email]
author_when = commit[:author_when];
ret = db.execute(
"INSERT INTO file_stats_summary (sha, tag, author_name, author_email, author_when, code_lines, comment_lines, blank_lines)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
[sha,
name,
author_name,
author_email,
author_when,
summary[:code_lines],
summary[:comment_lines],
summary[:blank_lines]
])
end
db.transaction
summary = import_cloc_output repo_dir, db, head
add_summary db, summary, "HEAD", head, find_commit( db, head, head )
db.execute( "select sha, name, object from tags" ) do |row|
sha = row[0]
name = row[1]
object = row[2]
summary = import_cloc_output repo_dir, db, sha
add_summary db, summary, name, sha, find_commit( db, sha, object );
end
db.commit
def concurrent_contribs db, at
return db.execute( "select count(*) from authors where earliest <= (?) and date(latest,'+90 days') >= (?)", [at, at]).first
end
def add_event db, at, verb, author, subject
concurrent = concurrent_contribs db, at
commits = db.execute( "select count(*) from commits where author_email = (?)", [author] ).first
entities_changed = db.execute( "select count(distinct(name)) from commits, commit_files where commits.id = commit_files.id and author_email = (?)", [author]).first
db.execute( "INSERT INTO timeline (event_at, event_verb, event_author, event_subject, concurrent_contributors, commits, entities_changed)
VALUES (?, ?, ?, ?, ?, ?, ?)",
[at, verb, author, subject, concurrent, commits, entities_changed] );
end
def project_start_stop db
row = db.execute( "select author_name, author_email, author_when from commits order by author_when asc limit 1" ).first
add_event db, row[2], "project.start", row[1], "#{row[0]} made first commit"
row = db.execute( "select author_name, author_email, author_when from commits order by author_when desc limit 1" ).first
add_event db, row[2], "project.mostrecent", row[1], "#{row[0]} made most recent"
end
def contributors db
# Look through all the authors to add when the started and stopped.
rows= db.execute( "select email, name, earliest, latest from authors" )
rows.each do |row|
if row[2] != row[3]
add_event db, row[2], "contrib.start", row[0], row[1]
# only add latest if it was 45 at least 45 days ago
add_event db, row[3], "contrib.latest", row[0], row[1]
end
end
end
def releases db
db.execute( "
select tag,
count(distinct(commits.author_email)) as contributors,
count(*) as commits,
count(distinct(commit_files.name)) as entities,
tags.name,
tags.author_email,
max(author_when)
from commits, commit_files
left join tags on tags.name = commits.tag
where commits.id = commit_files.id
group by tag
order by max(author_when)
" ).each do |row|
tag, contributors, commits, entities, name, author_email, author_when = row
db.execute( "INSERT INTO timeline
(event_at, event_verb, event_author, event_subject, concurrent_contributors, entities_changed, commits)
VALUES
(?, ?, ?, ?, ?, ?, ?)",
[author_when, 'project.release', author_email, tag, contributors, entities, commits] )
end
end
db.transaction
project_start_stop db
contributors db
releases db
db.commit