git-commits-analyzer 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e1c430c515c28a8a1a8f6dea0098f0590101095f
4
- data.tar.gz: 01df65d27d647d9dff0207927d9d550a93111b0f
3
+ metadata.gz: 3235fa842b69838ddc164316ad8394c08f71b6a5
4
+ data.tar.gz: 6bb686c2baf86c75abc8d31f752c4968fd1931ed
5
5
  SHA512:
6
- metadata.gz: 21b04677942df99acdef4d4d44cfc907fdeee7907a121c695e616c093d98e99af87ab242aa036009be2644fb629b05d61ebaaaf3c024c4f6fc8cdd5925ffa614
7
- data.tar.gz: a647cccacdec435bd1d73f444b76157f363f671e6da468917fb7e637c8dc090e3c9c9fa7c7e4c5612afbc48e0d985119c5b5f609e2269a786a2f564446be07bd
6
+ metadata.gz: d7f5f16a6b4b26bcae93456c26d7df31de2fb3b6a35c74b5b268184f529c5303a81b6262a60a6da8d3075a9d901d8c13ef930285bb705978ee836de3ffb296a5
7
+ data.tar.gz: 82f66448cfe0081c0881cb302dee08c080938e7778a6762fb8119e39143cec4145161412de4c511a5489ca6e1b3f1b1baf64c53aa44eddf368b71fc5c0ab7939
data/bin/analyze_commits CHANGED
@@ -23,29 +23,36 @@ options = Utils.parse_command_line_options()
23
23
 
24
24
  # Find git repos to inspect.
25
25
  repos = Utils.get_git_repos(path: options[:path])
26
- puts "Found " + repos.length.to_s + " repos to inspect."
27
- puts ""
26
+ printf("Found %s repos to inspect.\n", repos.length.to_s)
27
+ puts ''
28
28
 
29
29
  # Inspect git repos.
30
- puts "===== Inspecting repos ====="
31
- puts ""
32
- git_commits_analyzer = GitCommitsAnalyzer.new(logger: logger, author: options[:authors])
30
+ puts '===== Inspecting repos ====='
31
+ puts ''
32
+ git_commits_analyzer = GitCommitsAnalyzer.new(
33
+ logger: logger,
34
+ author: options[:authors]
35
+ )
33
36
  repos.sort.each do |repo|
34
- puts "Inspecting repo " + repo
37
+ printf("Inspecting repo %s.\n", repo)
35
38
  git_commits_analyzer.parse_repo(repo: repo)
36
- #break
39
+ # break
37
40
  end
38
- puts ""
41
+ puts ''
39
42
 
40
43
  # Display sanity check.
41
- puts "Found #{git_commits_analyzer.total_commits} commits for author(s) " + options[:authors].join(', ')
42
- puts ""
43
- exit if git_commits_analyzer.monthly_commits.keys.length == 0
44
+ printf(
45
+ "Found %s commits for author(s) %s.\n",
46
+ git_commits_analyzer.commits_total,
47
+ options[:authors].join(', ')
48
+ )
49
+ puts ''
50
+ exit if git_commits_analyzer.commits_by_month.keys.empty?
44
51
 
45
52
  # Save data.
46
- puts "===== Save data ====="
47
- puts ""
48
- output_file = options[:output];
53
+ puts '===== Save data ====='
54
+ puts ''
55
+ output_file = options[:output]
49
56
  File.open(output_file, 'w') { |file| file.write(git_commits_analyzer.to_json) }
50
57
  puts "Re-generated #{output_file}."
51
- puts ""
58
+ puts ''
@@ -1,36 +1,44 @@
1
1
  require 'optparse'
2
2
 
3
+ # Public: supporting functions for the command-line utility.
4
+ #
5
+ # Examples:
6
+ #
7
+ # require 'git-commits-analyzer/utils'
8
+ # options = Utils.parse_command_line_options()
9
+ # repos = Utils.get_git_repos(path)
10
+ #
3
11
  class Utils
4
12
  def self.parse_command_line_options()
5
13
  options = {}
6
14
  OptionParser.new do |opts|
7
- opts.banner = "Usage: inspect_contributions.rb [options]"
15
+ opts.banner = 'Usage: inspect_contributions.rb [options]'
8
16
  options[:authors] = []
9
17
 
10
18
  # Parse path.
11
- opts.on("-p", "--path PATH", "Specify a path to search for git repositories under") do |path|
19
+ opts.on('-p', '--path PATH', 'Specify a path to search for git repositories under') do |path|
12
20
  options[:path] = path
13
21
  end
14
22
 
15
23
  # Parse authors.
16
- opts.on("-a", "--author EMAIL", "Include this author in statistics") do |email|
24
+ opts.on('-a', '--author EMAIL', 'Include this author in statistics') do |email|
17
25
  options[:authors] << email
18
26
  end
19
27
 
20
28
  # Parse output directory.
21
- opts.on("-p", "--output PATH", "Specify a path to output files with collected data") do |output|
29
+ opts.on('-p', '--output PATH', 'Specify a path to output files with collected data') do |output|
22
30
  options[:output] = output
23
31
  end
24
32
 
25
33
  # Show usage
26
- opts.on_tail("-h", "--help", "Show this message") do
34
+ opts.on_tail('-h', '--help', 'Show this message') do
27
35
  puts opts
28
36
  exit
29
37
  end
30
38
  end.parse!
31
39
 
32
40
  # Check mandatory options.
33
- raise OptionParser::MissingArgument, '--author' if options[:authors].length == 0
41
+ raise OptionParser::MissingArgument, '--author' if options[:authors].empty?
34
42
  raise OptionParser::MissingArgument, '--output' if options[:output].nil?
35
43
  raise OptionParser::MissingArgument, '--path' if options[:path].nil?
36
44
 
@@ -11,15 +11,24 @@ require 'json'
11
11
  #
12
12
  class GitCommitsAnalyzer
13
13
  # Public: Returns a hash of commit numbers broken down by month.
14
- attr_reader :monthly_commits
14
+ attr_reader :commits_by_month
15
15
 
16
16
  # Public: Returns the total number of commits belonging to the author
17
17
  # specified.
18
- attr_reader :total_commits
18
+ attr_reader :commits_total
19
19
 
20
20
  # Public: Returns the number of lines added/removed broken down by language.
21
21
  attr_reader :lines_by_language
22
22
 
23
+ # Public: Returns the tally of commits broken down by hour of the day.
24
+ attr_reader :commit_hours
25
+
26
+ # Public: Returns the tally of commits broken down by day.
27
+ attr_reader :commit_days
28
+
29
+ # Public: Returns the tally of commits broken down by weekday and hour.
30
+ attr_reader :commit_weekdays_hours
31
+
23
32
  # Public: Initialize new GitParser object.
24
33
  #
25
34
  # logger - A logger object to display git errors/warnings.
@@ -28,10 +37,20 @@ class GitCommitsAnalyzer
28
37
  def initialize(logger:, author:)
29
38
  @logger = logger
30
39
  @author = author
31
- @monthly_commits = {}
32
- @monthly_commits.default = 0
33
- @total_commits = 0
40
+ @commits_by_month = {}
41
+ @commits_by_month.default = 0
42
+ @commits_total = 0
34
43
  @lines_by_language = {}
44
+ @commit_hours = 0.upto(23).map{ |x| [x, 0] }.to_h
45
+ @commit_days = {}
46
+ @commit_days.default = 0
47
+ @commit_weekdays_hours = {}
48
+ ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].each do |weekday|
49
+ @commit_weekdays_hours[weekday] = {}
50
+ 0.upto(23).each do |hour|
51
+ @commit_weekdays_hours[weekday][hour] = 0
52
+ end
53
+ end
35
54
  end
36
55
 
37
56
  # Public: Determine the type of a file at the given revision of a repo.
@@ -49,7 +68,9 @@ class GitCommitsAnalyzer
49
68
  case filename
50
69
  when /\.(pl|pm|t|cgi|pod|run)$/i
51
70
  return 'Perl'
52
- when /\.rb$/
71
+ when /\.(?:rb|gemspec)$/
72
+ return 'Ruby'
73
+ when /(?:\/|^)Rakefile$/
53
74
  return 'Ruby'
54
75
  when /\.md$/
55
76
  return 'Markdown'
@@ -75,9 +96,15 @@ class GitCommitsAnalyzer
75
96
  return 'bash'
76
97
  when /(bash|bash_\w+)$/
77
98
  return 'bash'
78
- when /\.?(SKIP|gitignore|txt|csv|vim|gitmodules|gitattributes|jshintrc|gperf|vimrc|psqlrc|inputrc|screenrc)$/
99
+ when /\.?(SKIP|gitignore|txt|csv|vim|gitmodules|gitattributes|jshintrc|gperf|vimrc|psqlrc|inputrc|screenrc|curlrc|wgetrc|selected_editor|dmrc|netrc)$/
79
100
  return 'Text'
80
- when /^(README|MANIFEST|Changes|Gemfile|Gemfile.lock)$/
101
+ when /(?:\/|^)(?:LICENSE|LICENSE-\w+)$/
102
+ return nil
103
+ when /\.(?:0|1|VimballRecord)$/
104
+ return nil
105
+ when /^vim\/doc\/tags$/
106
+ return nil
107
+ when /(?:\/|^)(?:README|MANIFEST|Changes|Gemfile|Gemfile.lock|CHANGELOG)$/
81
108
  return 'Text'
82
109
  end
83
110
 
@@ -93,6 +120,10 @@ class GitCommitsAnalyzer
93
120
  case first_line
94
121
  when /perl$/
95
122
  return 'Perl'
123
+ when /ruby$/
124
+ return 'Ruby'
125
+ when /^\#!\/usr\/bin\/bash$/
126
+ return 'Ruby'
96
127
  end
97
128
 
98
129
  # Fall back on the extension in last resort.
@@ -110,23 +141,42 @@ class GitCommitsAnalyzer
110
141
  # variables collecting commit metadata.
111
142
  #
112
143
  def parse_repo(repo:)
113
- git_repo = Git.open(repo, :log => @logger)
144
+ git_repo = Git.open(repo, log: @logger)
114
145
 
146
+ #return if repo != '/git_backups/github-guillaumeaubert/dot-files'
115
147
  # Note: override the default of 30 for count(), nil gives the whole git log
116
148
  # history.
117
149
  git_repo.log(count = nil).each do |commit|
118
150
  # Only include the authors specified on the command line.
119
151
  next if !@author.include?(commit.author.email)
120
152
 
153
+ # Parse commit date and update the corresponding stats.
154
+ commit_datetime = DateTime.parse(commit.author.date.to_s)
155
+ commit_hour = commit_datetime.hour
156
+ @commit_hours[commit_hour] += 1
157
+ commit_day = commit_datetime.strftime('%Y-%m-%d')
158
+ @commit_days[commit_day] += 1
159
+ commit_weekday = commit_datetime.strftime('%a')
160
+ @commit_weekdays_hours[commit_weekday][commit_hour] += 1
161
+
121
162
  # Parse diff and analyze patches to detect language.
122
- diff = commit.diff_parent.to_s
163
+ diff = git_repo.show(commit.sha)
123
164
  diff.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
124
165
 
166
+ file_properties = git_repo.ls_tree(['-r', commit.sha])
167
+
125
168
  patches = GitDiffParser.parse(diff)
126
169
  patches.each do |patch|
170
+ # Skip submodules.
171
+ next if file_properties['commit'].has_key?(patch.file);
172
+
173
+ # Skip symlinks.
174
+ next if file_properties['blob'].has_key?(patch.file) &&
175
+ (file_properties['blob'][patch.file][:mode] == '120000')
176
+
127
177
  body = patch.instance_variable_get :@body
128
178
  language = self.class.determine_language(filename: patch.file, sha: commit.sha, git_repo: git_repo)
129
- next if language == nil
179
+ next if language.nil?
130
180
  @lines_by_language[language] ||=
131
181
  {
132
182
  'added' => 0,
@@ -147,10 +197,10 @@ class GitCommitsAnalyzer
147
197
  # Add to stats for monthly commit count.
148
198
  # Note: months are zero-padded to allow easy sorting, even if it's more
149
199
  # work for formatting later on.
150
- @monthly_commits[commit.date.strftime("%Y-%m")] += 1
200
+ @commits_by_month[commit.date.strftime("%Y-%m")] += 1
151
201
 
152
202
  # Add to stats for total commits count.
153
- @total_commits += 1
203
+ @commits_total += 1
154
204
  end
155
205
  end
156
206
 
@@ -160,8 +210,8 @@ class GitCommitsAnalyzer
160
210
  #
161
211
  def get_month_scale()
162
212
  month_scale = []
163
- commits_start = @monthly_commits.keys.sort.first.split('-').map { |x| x.to_i }
164
- commits_end = @monthly_commits.keys.sort.last.split('-').map { |x| x.to_i }
213
+ commits_start = @commits_by_month.keys.sort.first.split('-').map { |x| x.to_i }
214
+ commits_end = @commits_by_month.keys.sort.last.split('-').map { |x| x.to_i }
165
215
  commits_start[0].upto(commits_end[0]) do |year|
166
216
  1.upto(12) do |month|
167
217
  next if month < commits_start[1] && year == commits_start[0]
@@ -178,20 +228,23 @@ class GitCommitsAnalyzer
178
228
  # Returns: a JSON string.
179
229
  #
180
230
  def to_json()
181
- formatted_monthly_commits = []
231
+ formatted_commits_by_month = []
182
232
  month_names = Date::ABBR_MONTHNAMES
183
233
  self.get_month_scale.each do |frame|
184
234
  display_key = month_names[frame[1]] + '-' + frame[0].to_s
185
235
  data_key = sprintf('%s-%02d', frame[0], frame[1])
186
- count = @monthly_commits[data_key].to_s
187
- formatted_monthly_commits << { :month => display_key, :commits => count.to_s }
236
+ count = @commits_by_month[data_key].to_s
237
+ formatted_commits_by_month << { month: display_key, commits: count.to_s }
188
238
  end
189
239
 
190
240
  return JSON.pretty_generate(
191
241
  {
192
- :monthly_commits => formatted_monthly_commits,
193
- :total_commits => @total_commits,
194
- :lines_by_language => @lines_by_language,
242
+ commits_total: @commits_total,
243
+ commits_by_month: formatted_commits_by_month,
244
+ commits_by_hour: @commit_hours,
245
+ commits_by_day: @commit_days,
246
+ commit_by_weekday_hour: @commit_weekdays_hours,
247
+ lines_by_language: @lines_by_language
195
248
  }
196
249
  )
197
250
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-commits-analyzer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Guillaume Aubert
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-12 00:00:00.000000000 Z
11
+ date: 2016-03-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Parse git repos and collect commit statistics/data for a given author.
14
14
  email: aubertg@cpan.org