forked from huygn/dotfiles
-
Notifications
You must be signed in to change notification settings - Fork 0
/
count-commits
executable file
·154 lines (128 loc) · 4.56 KB
/
count-commits
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
#!/usr/bin/env ruby
if ARGV.include? '-h' or ARGV.include? '--help' then puts <<-HELP
Count git commits across multiple projects.
http://benalman.com/
Usage: #{File.basename $0} [directories] > counts.txt
If directories aren't specified, all child directories of the current directory
will be searched.
Arbitrary name and email changes that can be accounted for will be reflected
in the output. Counts/names/emails are sent to STDOUT in the following format:
commitcount email1, email2, ... (name1, name2, ...)
Eg:
1843 [email protected] (Ben Alman, cowboy)
1202 [email protected] (Tyler Kellen, tkellen)
695 [email protected], [email protected] (Kyle Robinson Young, shama)
402 [email protected] (vladikoff, Vlad Filippov)
388 [email protected] (Sindre Sorhus, sindresorhus)
All other messages are sent to STDERR.
Copyright (c) 2014 "Cowboy" Ben Alman
Licensed under the MIT license.
http://benalman.com/about/license/
HELP
exit; end
require 'open3'
def warn(msg) $stderr.puts msg end
def fatal(msg) $stderr.puts msg; exit 1 end
def debug(msg) $stderr.puts "> #{msg}" end
# Validate specified dirs.
def get_dirs(dirs)
# Use all child dirs of the current dir if none were specified.
if dirs.empty?
dirs = Dir.glob('*/')
end
# Remove non-dirs
dirs.select! {|d| File.directory? d}
# Remove trailing slashes
dirs.map! {|d| d.sub %r{/$}, ''}
# Fail if no dirs were found.
if dirs.empty?
fatal 'Error: No directories found.'
end
dirs
end
# Get computed total number of commits.
def get_computed_total()
$committers.reduce(0) {|sum, committer| sum + committer[:commit_count]}
end
# Add a commit.
def add_commit(email, name)
# debug "#{email} #{name}"
emails = [email]
names = [name]
commit_count = 1
# Get the corresponding committer object(s) indexed by name and email.
committer_by_email = $committers_by_email[email]
committer_by_name = $committers_by_name[name]
# Two committer objects exist, but they're not the same object. Combine them!
if committer_by_email and committer_by_name and committer_by_email != committer_by_name
emails = committer_by_name[:emails]
names = committer_by_name[:names]
commit_count += committer_by_name[:commit_count]
# We save committer_by_email and kill off committer_by_name.
$committers.reject! {|committer| committer == committer_by_name}
committer = committer_by_email
# One committer object exists, use it.
elsif committer_by_email
committer = committer_by_email
elsif committer_by_name
committer = committer_by_name
# No committer object exists, create one!
else
committer = Hash.new
committer[:emails] = []
committer[:names] = []
committer[:commit_count] = 0
$committers << committer
end
# Update committers_by_name/committers_by_email maps.
names.each {|name| $committers_by_name[name] = committer}
emails.each {|email| $committers_by_email[email] = committer}
# Update committer object with new data.
committer[:names].concat(names).uniq!
committer[:emails].concat(emails).uniq!
committer[:commit_count] += commit_count
end
# Our data store.
$committers = []
$committers_by_email = Hash.new
$committers_by_name = Hash.new
# Raw counters based on lines parsed.
$commit_count = 0
$project_count = 0
# Get commits for each specified dir.
get_dirs(ARGV).each do |dir|
dir.sub! %r{/$}, ''
stdin, stdout, stderr = Open3.popen3('git log --format="%aE%x09%aN"', :chdir => dir)
if !stderr.readlines.empty?
warn %Q{Error running "git log" in "#{dir}" directory.}
else
$project_count += 1
stdout.readlines.each do |line|
$commit_count += 1
email, name = line.chomp.split "\t"
add_commit(email, name)
end
end
end
# Sort committer names and email fields.
$committers.each do |committer|
committer[:names].sort! {|a, b| a.downcase <=> b.downcase}
committer[:emails].sort! {|a, b| a.downcase <=> b.downcase}
end
# Sort committers first by commit count (descending) then by name.
$committers.sort! do |a, b|
comp = b[:commit_count] <=> a[:commit_count]
comp.zero? ? (a[:names][0].downcase <=> b[:names][0].downcase) : comp
end
# Whoops?
$total = get_computed_total
if $total != $commit_count
fatal "Error: Parsed #{$commit_count} commits, but for some reason only #{$total} appear."
end
# Useful output to STDOUT
$committers.each do |c|
puts "%#{$total.to_s.length}d %s (%s)" % [c[:commit_count], c[:emails].join(", "), c[:names].join(", ")]
end
# Totals.
def pl(n, str) "#{n} #{str}#{n == 1 ? '' : ?s}" end
warn "#{pl $commit_count, 'commit'} by #{pl $committers.length, 'author'} in #{pl $project_count, 'project'}."