git_ownership_insights 1.1.1 → 2.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
  SHA256:
3
- metadata.gz: 7a07de43c9627bdeceeedc7e06d1028645a3b374f26203063e1bc23ed3024611
4
- data.tar.gz: b7ea15dc22b92a8742278ff835612f180125240af9531e2ec764c7b19a80e5c9
3
+ metadata.gz: e323781f21d5b045b9fe23e6790f53739fe6bd99e917a9ad2b88c544a9aa3428
4
+ data.tar.gz: 0c23bd13ad86a4edc00f38a77e5219d18ebd81af84dc8e76764762611780fb1e
5
5
  SHA512:
6
- metadata.gz: f828c59098fb64d0017ac93a0fe15894ea8c92a08a5f6a2145d84ff7e88f38c089311cb5d177257169a9d88e8d0948c592f85c12f8a1209135e4442c223b0795
7
- data.tar.gz: eb8302ef0f5a93679411c77c243dfece43e0d902a129325d4319d739f103fbf5c2ed29f225d9c3c705466525aaef7238964fd557176fbb87036e97355119967e
6
+ metadata.gz: 7302774074f86ca8f5ebd3288f47150a7cc9adf642b2dfe3bc60f0f0ff087a38f9645789b256e923fc578b5d445293be72dcc143d07906a86c6a678e8742a1c4
7
+ data.tar.gz: 97f58091878d741eac46b1c0a0eb8263cbe7cc7abdba5f8db2c70bf2d12fe4dc0f089496a8e8b7b87531c9bfa4bdb4577820b75afa50576eec9e30184bbdd723
@@ -0,0 +1,2 @@
1
+ spec/fixtures/file1.swift @FIOS
2
+ spec/fixtures/file2.kt @FAND
data/.rubocop.yml ADDED
@@ -0,0 +1,14 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ # The behavior of RuboCop can be controlled via the .rubocop.yml
4
+ # configuration file. It makes it possible to enable/disable
5
+ # certain cops (checks) and to alter their behavior if they accept
6
+ # any parameters. The file can be placed either in your home
7
+ # directory or in some project directory.
8
+ #
9
+ # RuboCop will start looking for the configuration file in the directory
10
+ # where the inspected file is and continue its way up to the root directory.
11
+ #
12
+ # See https://docs.rubocop.org/rubocop/configuration
13
+ AllCops:
14
+ TargetRubyVersion: 3.2.1
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,58 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2024-01-30 07:43:06 UTC using RuboCop version 1.60.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 4
10
+ # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
11
+ Metrics/AbcSize:
12
+ Max: 187
13
+
14
+ # Offense count: 1
15
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
16
+ # AllowedMethods: refine
17
+ Metrics/BlockLength:
18
+ Max: 69
19
+
20
+ # Offense count: 1
21
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
22
+ Metrics/CyclomaticComplexity:
23
+ Max: 31
24
+
25
+ # Offense count: 4
26
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
27
+ Metrics/MethodLength:
28
+ Max: 115
29
+
30
+ # Offense count: 1
31
+ # Configuration parameters: CountComments, CountAsOne.
32
+ Metrics/ModuleLength:
33
+ Max: 192
34
+
35
+ # Offense count: 1
36
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
37
+ Metrics/PerceivedComplexity:
38
+ Max: 32
39
+
40
+ # Offense count: 1
41
+ # Configuration parameters: AllowedConstants.
42
+ Style/Documentation:
43
+ Exclude:
44
+ - 'spec/**/*'
45
+ - 'test/**/*'
46
+ - 'lib/git_ownership_insights/git_ownership_insight.rb'
47
+
48
+ # Offense count: 1
49
+ Style/MultilineBlockChain:
50
+ Exclude:
51
+ - 'lib/git_ownership_insights/git_ownership_insight.rb'
52
+
53
+ # Offense count: 7
54
+ # This cop supports safe autocorrection (--autocorrect).
55
+ # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
56
+ # URISchemes: http, https
57
+ Layout/LineLength:
58
+ Max: 150
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- git_ownership_insights (1.0.6)
4
+ git_ownership_insights (2.0.0)
5
5
  date
6
6
  pry
7
7
 
@@ -33,7 +33,7 @@ OptionParser.new do |opts|
33
33
  end
34
34
 
35
35
  opts.on('--excluded-files STRING',
36
- 'Comma-delimited list of excluded files [example: ViewController,AppDelegate.swift]') do |excluded_files|
36
+ 'Comma-delimited list of excluded keywords [example: ViewController,AppDelegate.swift]') do |excluded_files|
37
37
  options[:excluded_files] = excluded_files
38
38
  end
39
39
 
@@ -121,7 +121,7 @@ unless CI
121
121
  puts "Code extensions: #{CODE_EXTENSIONS}"
122
122
  puts "Regex to detect the teams identifiers: #{TEAM_REGEX}"
123
123
  puts "Excluded contributors: #{EXCLUSIONS}\n" if EXCLUSIONS
124
- puts "Excluded file patterns: #{EXCLUDED_FILES.split(',')}\n" if EXCLUDED_FILES
124
+ puts "Excluded file keywords: #{EXCLUDED_FILES.split(',')}\n" if EXCLUDED_FILES
125
125
  puts "Lines of code limit (big files) for the hotspot calculation: #{BIG_FILE_SIZE}"
126
126
  puts "Hotspot detailed output is: #{options[:hotspot_files] ? 'on' : 'off'}\n"
127
127
  puts "CODEOWNERS output is: #{options[:codeowners] ? 'on' : 'off'}\n"
@@ -134,5 +134,5 @@ end
134
134
  system("git checkout #{DEFAULT_BRANCH}", [:out] => File::NULL)
135
135
  system('git pull', %i[out err] => File::NULL)
136
136
 
137
- GitOwnershipInsights.contribution_message(duration_in_days: options[:duration_in_days] || 30, directory_path: REPO_PATH,
138
- begin_time: DateTime.now, steps: options[:steps].to_i, debug: options[:debug])
137
+ GitOwnershipInsights.new(duration_in_days: options[:duration_in_days] || 30, directory_path: REPO_PATH,
138
+ begin_time: DateTime.now, steps: options[:steps].to_i, debug: options[:debug]).contribution_message
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.summary = 'This gem prints git ownership insights'
13
13
  spec.homepage = 'https://rubygems.org'
14
14
  spec.license = 'MIT'
15
- spec.required_ruby_version = '>= 2.6.0'
15
+ spec.required_ruby_version = '>= 3.2'
16
16
 
17
17
  spec.metadata['homepage_uri'] = spec.homepage
18
18
  spec.metadata['source_code_uri'] = 'https://rubygems.org'
@@ -1,11 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module GitOwnershipInsights
4
- def self.true?(obj)
3
+ require 'pry'
4
+ require 'date'
5
+
6
+ class GitOwnershipInsights
7
+ def initialize(directory_path:, duration_in_days:, begin_time:, debug: nil, steps: 1)
8
+ @directory_path = directory_path
9
+ @duration_in_days = duration_in_days
10
+ @begin_time = begin_time
11
+ @debug = debug
12
+ @steps = steps
13
+ end
14
+
15
+ def true?(obj)
5
16
  obj.to_s.downcase == 'true'
6
17
  end
7
18
 
8
- def self.read_codeowners_file
19
+ def read_codeowners_file
9
20
  raise "CODEOWNERS file does not exist under #{CODEOWNERS_PATH}" unless File.exist?(CODEOWNERS_PATH)
10
21
 
11
22
  codeowners = {}
@@ -20,7 +31,7 @@ module GitOwnershipInsights
20
31
  codeowners
21
32
  end
22
33
 
23
- def self.find_owners(file_path, codeowners)
34
+ def find_owners(file_path, codeowners)
24
35
  matching_patterns = codeowners.keys.select do |pattern|
25
36
  pattern_regex = Regexp.new("^#{Regexp.escape(pattern.sub(%r{^/+}, '').chomp('/')).gsub('\*', '.*').gsub('**',
26
37
  '.*?')}")
@@ -42,7 +53,7 @@ module GitOwnershipInsights
42
53
  codeowners[best_match].split(' ')
43
54
  end
44
55
 
45
- def self.count_big_files(directory_path, size: BIG_FILE_SIZE)
56
+ def count_big_files(directory_path, size: BIG_FILE_SIZE)
46
57
  size = size.to_i
47
58
  # Get a list of all files in the specified directory
48
59
  files = Dir.glob(File.join(directory_path, '**', '*')).select { |file| File.file?(file) }
@@ -62,10 +73,10 @@ module GitOwnershipInsights
62
73
  count += 1 if lines_count > size
63
74
  end
64
75
 
65
- puts " *Total number of code files longer than #{size} lines:* #{count}"
76
+ puts " *Current(*) total number of code files longer than #{size} lines:* #{count}"
66
77
  end
67
78
 
68
- def self.count_hotspot_lines(files)
79
+ def count_hotspot_lines(files)
69
80
  code_files = files.select do |f|
70
81
  extension = File.extname(f)
71
82
  valid_extensions = CODE_EXTENSIONS
@@ -83,43 +94,57 @@ module GitOwnershipInsights
83
94
  puts " *Total lines of hotspot code:* #{count}"
84
95
  end
85
96
 
86
- def self.filter_code_files(files)
97
+ def filter_existing_code_files(files)
87
98
  files.select do |f|
99
+ next unless File.exist?(f)
100
+
101
+ if EXCLUDED_FILES
102
+ excluded_patterns = EXCLUDED_FILES.split(',')
103
+ next if excluded_patterns.any? { |pattern| f.include?(pattern) }
104
+ end
105
+
88
106
  extension = File.extname(f)
89
107
  valid_extensions = CODE_EXTENSIONS
90
108
  valid_extensions.include?(extension)
91
109
  end
92
110
  end
93
111
 
94
- def self.contribution_message(directory_path:, duration_in_days:, begin_time:, debug: nil, steps: nil)
95
- duration_in_days = duration_in_days.to_i
112
+ def git_files(directory_path:)
113
+ `git ls-tree -r --name-only $(git rev-list -1 HEAD) -- "#{directory_path}"`
114
+ end
115
+
116
+ def files_with_changes(directory_path:, start_date:, end_date:)
117
+ `git log --name-only --pretty=format:"" --since="#{start_date}" --until="#{end_date}" "#{directory_path}"`
118
+ end
119
+
120
+ def git_commit_count(file:, start_date:, end_date:)
121
+ `git log --since="#{start_date}" --until="#{end_date}" --follow -- "#{file}" | grep -c '^commit'`
122
+ end
123
+
124
+ def git_commit_info(file:, start_date:, end_date:)
125
+ `git log --pretty=format:"%s" --since="#{start_date}" --until="#{end_date}" --follow -- "#{file}"`
126
+ end
127
+
128
+ def contribution_message
129
+ duration_in_days = @duration_in_days.to_i
96
130
  all_teams = []
97
131
  cross_teams_count = 0
98
132
  single_ownership_teams_count = 0
99
133
  files_changed_by_many_teams = 0
100
134
  total_changes = 0
101
- start_date = begin_time.to_time.to_i - duration_in_days * 86_400
102
- end_date = begin_time.to_time.to_i
103
- file_count = `git ls-tree -r --name-only $(git rev-list -1 --since="#{start_date}" --until="#{end_date}" HEAD) -- "#{directory_path}" | wc -l`.to_i
104
- all_files_with_changes = `git log --name-only --pretty=format:"" --since="#{start_date}" --until="#{end_date}" "#{directory_path}"`.split.sort
105
- excluded_patterns = EXCLUDED_FILES.split(',') if EXCLUDED_FILES
106
-
107
- code_files_with_changes = filter_code_files(all_files_with_changes)
108
-
109
- if EXCLUDED_FILES
110
- code_files_with_changes = code_files_with_changes.reject do |file|
111
- excluded_patterns.any? { |pattern| file.include?(pattern) }
112
- end
113
- end
114
-
135
+ start_date = @begin_time.to_time.to_i - duration_in_days * 86_400 - 30 * 86_400
136
+ end_date = @begin_time.to_time.to_i - 30 * 86_400
137
+ git_ls = git_files(directory_path: @directory_path)
138
+ file_count = filter_existing_code_files(git_ls.split).count
139
+ all_files_with_changes = files_with_changes(directory_path: @directory_path, start_date:, end_date:).split.sort
140
+ code_files_with_changes = filter_existing_code_files(all_files_with_changes)
115
141
  uniq_code_files_with_changes = code_files_with_changes.uniq
116
142
 
117
143
  file_team_map = {}
118
144
  uniq_code_files_with_changes.each do |file|
119
145
  filename = File.basename(file)
120
- commit_count = `git log --since="#{start_date}" --until="#{end_date}" --follow -- "#{file}" | grep -c '^commit'`.to_i
121
- # Get the log of the file in the given duration
122
- git_log = `git log --pretty=format:"%s" --since="#{start_date}" --until="#{end_date}" --follow -- "#{file}"`.split("\n")
146
+ commit_count = git_commit_count(file:, start_date:, end_date:).to_i
147
+ git_log = git_commit_info(file:, start_date:, end_date:).split("\n")
123
148
  teams = git_log.map do |team|
124
149
  team.match(/#{TEAM_REGEX}/)[0].upcase
125
150
  end.reject { |e| EXCLUSIONS&.include?(e) }
@@ -131,12 +156,12 @@ module GitOwnershipInsights
131
156
  if teams.count > 1
132
157
  files_changed_by_many_teams += 1
133
158
  file_team_map.merge!(file.to_s => [teams, commit_count])
134
- cross_teams_count += teams.count if File.exist?(file)
159
+ cross_teams_count += teams.count
135
160
  else
136
- single_ownership_teams_count += 1 if File.exist?(file)
161
+ single_ownership_teams_count += 1
137
162
  end
138
163
 
139
- puts "\n#{filename} [#{commit_count}]:#{teams}\n" if debug
164
+ puts "\n#{filename} [#{commit_count}]:#{teams}\n" if @debug
140
165
  end
141
166
 
142
167
  occurrences = all_teams.flatten.compact.tally
@@ -156,26 +181,32 @@ module GitOwnershipInsights
156
181
 
157
182
  filtered_top_touched_files = filtered_files.sort_by { |element, count| [-count.last, element] }
158
183
 
159
- puts ""
160
- puts "*Timeframe:* #{(begin_time - duration_in_days).strftime('%Y-%m-%d')} to #{begin_time.strftime('%Y-%m-%d')}"
161
- puts " *Hotspot Code Changes:* #{churn_count} (#{hotspot_changes_percentage.round(2)}%)"
162
- puts " *Cross-Squad Dependency:*"
184
+ puts ''
185
+ puts "*Timeframe:* #{(@begin_time - duration_in_days).strftime('%Y-%m-%d')} to #{@begin_time.strftime('%Y-%m-%d')}"
186
+ puts " *Code files with a single contributor:* #{(100 - ((files_changed_by_many_teams.to_f / file_count) * 100)).round(2)}%"
187
+ puts " *Existing files changed by many teams:* #{files_changed_by_many_teams}"
188
+ puts " *Current existing #{CODE_EXTENSIONS} files:* #{file_count}"
189
+ puts ' *Cross-Squad Dependency:*'
163
190
  puts " *Contributions by multiple squads to the same files:* #{cross_teams_count}"
164
191
  puts " *Contributions by single squads contributing to single files:* #{single_ownership_teams_count}"
165
- puts " *Files exceeding #{BIG_FILE_SIZE} lines with multiple contributors:* #{filtered_top_touched_files.count}"
192
+ puts " *Hotspot Code Changes:* #{hotspot_changes_percentage.round(2)}%"
193
+ puts " *Churn count(commits to files by multiple teams):* #{churn_count}"
194
+ puts " *Total amount of commits:* #{total_changes}"
166
195
  count_hotspot_lines(filtered_files.keys)
167
- count_big_files(directory_path)
168
- puts " *Code files with a single contributor:* #{(100 - ((files_changed_by_many_teams.to_f / code_files_with_changes.count) * 100)).round(2)}%"
169
- puts " *Total amount of code changes:* #{total_changes}"
170
- puts " *Total files changed:* #{code_files_with_changes.count}"
171
- puts " *Total files in the folder:* #{file_count}"
196
+ puts " *#{CODE_EXTENSIONS} files with multiple contributors:* #{file_team_map.count}"
197
+ puts " *#{CODE_EXTENSIONS} files exceeding #{BIG_FILE_SIZE} lines with multiple contributors:* #{filtered_top_touched_files.count}"
198
+ puts " *Total amount of commits to #{CODE_EXTENSIONS} files:* #{total_changes}"
199
+ puts " *Total #{CODE_EXTENSIONS} files changed:* #{uniq_code_files_with_changes.count}"
200
+ count_big_files(@directory_path)
201
+ puts " *Current(*) total of #{CODE_EXTENSIONS} files in the folder:* #{file_count}"
172
202
  puts " *Contributors:* #{contributors}"
203
+ puts "* means that it the current(instant) repository value, all the other metrics are done over #{duration_in_days} days period"
173
204
 
174
205
  if HOTSPOT
175
206
  puts "\n"
176
207
  puts ' Hotspot changes:'
177
208
  filtered_top_touched_files.each do |line|
178
- puts " #{line.first.gsub(directory_path, '')} Contributors: #{line.last.first} Commits: #{line.last.last}"
209
+ puts " #{line.first.gsub(@directory_path, '')} Contributors: #{line.last.first} Commits: #{line.last.last}"
179
210
  end
180
211
  end
181
212
 
@@ -196,7 +227,7 @@ module GitOwnershipInsights
196
227
  owners_data[owner][:churn_count] += count.last
197
228
 
198
229
  dir_path = File.dirname(file)
199
- owners_data[owner][:directories][dir_path][:files] << { name: File.basename(file), count: count }
230
+ owners_data[owner][:directories][dir_path][:files] << { name: File.basename(file), count: }
200
231
  end
201
232
  end
202
233
 
@@ -222,13 +253,13 @@ module GitOwnershipInsights
222
253
  end
223
254
  end
224
255
  end
225
- steps -= 1
256
+ @steps -= 1
226
257
 
227
- return unless steps.positive?
258
+ return unless @steps.positive?
228
259
 
229
- system("git checkout `git rev-list -1 --before='#{(begin_time - duration_in_days).strftime('%B %d %Y')}' HEAD`",
260
+ system("git checkout `git rev-list -1 --before='#{(@begin_time - duration_in_days).strftime('%B %d %Y')}' HEAD`",
230
261
  %i[out err] => File::NULL)
231
- contribution_message(duration_in_days: duration_in_days, directory_path: directory_path,
232
- begin_time: begin_time - duration_in_days, steps: steps, debug: debug)
262
+ @begin_time -= duration_in_days
263
+ contribution_message
233
264
  end
234
265
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module GitOwnershipInsights
4
- VERSION = '1.1.1'
3
+ class GitOwnershipInsights
4
+ VERSION = '2.0.0'
5
5
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative 'git_ownership_insights/version'
4
4
 
5
- module GitOwnershipInsights
5
+ class GitOwnershipInsights
6
6
  class Error < StandardError; end
7
7
  # Your code goes here...
8
8
  end
@@ -1,4 +1,4 @@
1
- module GitOwnershipInsights
1
+ class GitOwnershipInsights
2
2
  VERSION: String
3
3
  # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
4
  end
File without changes