git-commits-analyzer 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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