codeowners-checker 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.
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fuzzy_match'
4
+
5
+ require_relative '../checker'
6
+ require_relative 'base'
7
+ require_relative 'config'
8
+ require_relative 'filter'
9
+
10
+ module Codeowners
11
+ module Cli
12
+ # Command Line Interface used by bin/codeowners-checker.
13
+ class Main < Base
14
+ LABEL = { missing_ref: 'Missing references', useless_pattern: 'No files matching with the pattern' }.freeze
15
+ option :from, default: 'origin/master'
16
+ option :to, default: 'HEAD'
17
+ option :interactive, default: true, type: :boolean, aliases: '-i'
18
+ desc 'check REPO', 'Checks .github/CODEOWNERS consistency'
19
+ # for pre-commit: --from HEAD --to index
20
+ def check(repo = '.')
21
+ @codeowners_changed = false
22
+ @repo = repo
23
+ setup_checker
24
+ @checker.check!
25
+ write_codeowners if @codeowners_changed
26
+ @checker.commit_changes! if options[:interactive] && yes?('Commit changes?')
27
+ end
28
+
29
+ desc 'filter <by-owner>', 'List owners of changed files'
30
+ subcommand 'filter', Codeowners::Cli::Filter
31
+
32
+ desc 'config', 'Checks config is consistent or configure it'
33
+ subcommand 'config', Codeowners::Cli::Config
34
+
35
+ private
36
+
37
+ def setup_checker
38
+ to = options[:to] != 'index' ? options[:to] : nil
39
+ @checker = Codeowners::Checker.new(@repo, options[:from], to)
40
+ @checker.when_useless_pattern = method(:suggest_fix_for)
41
+ @checker.when_new_file = method(:suggest_add_to_codeowners)
42
+ @checker.transformers << method(:unrecognized_line)
43
+ end
44
+
45
+ def write_codeowners
46
+ @checker.codeowners.persist!
47
+ end
48
+
49
+ def suggest_add_to_codeowners(file)
50
+ return unless yes?("File added: #{file.inspect}. Add owner to the CODEOWNERS file?")
51
+
52
+ owner = ask('File owner(s): ')
53
+ new_line = create_new_pattern(file, owner)
54
+
55
+ unless new_line.pattern?
56
+ puts "#{owner.inspect} is not a valid owner name."
57
+ return
58
+ end
59
+
60
+ subgroups = @checker.main_group.subgroups_owned_by(new_line.owner)
61
+ add_pattern(new_line, subgroups)
62
+
63
+ @codeowners_changed = true
64
+ end
65
+
66
+ def create_new_pattern(file, owner)
67
+ line = "#{file} #{owner}"
68
+ Codeowners::Checker::Group::Line.build(line)
69
+ end
70
+
71
+ def add_pattern(pattern, subgroups)
72
+ unless subgroups.empty?
73
+ return if insert_pattern_into_subgroup(pattern, subgroups) == true
74
+ end
75
+
76
+ @checker.main_group.add(pattern) if yes?('Add to the end of the CODEOWNERS file?')
77
+ end
78
+
79
+ def insert_pattern_into_subgroup(pattern, subgroups)
80
+ subgroup = suggest_subgroups_for_pattern(subgroups).to_i - 1
81
+ return unless subgroup >= 0 && subgroup < subgroups.length
82
+
83
+ subgroups[subgroup].insert(pattern)
84
+ true
85
+ end
86
+
87
+ def suggest_subgroups_for_pattern(subgroups)
88
+ puts 'Possible groups to which the pattern belongs: '
89
+ subgroups.each_with_index { |group, i| puts "#{i + 1} - #{group.title}" }
90
+ ask('Choose group: ')
91
+ end
92
+
93
+ def suggest_fix_for(line)
94
+ search = FuzzyMatch.new(line.suggest_files_for_pattern)
95
+ suggestion = search.find(line.pattern)
96
+
97
+ puts "Pattern #{line.pattern.inspect} doesn't match."
98
+
99
+ # TODO: Handle duplicate patterns.
100
+ if suggestion
101
+ apply_suggestion(line, suggestion)
102
+ else
103
+ pattern_fix(line)
104
+ end
105
+
106
+ @codeowners_changed = true
107
+ end
108
+
109
+ def apply_suggestion(line, suggestion)
110
+ case make_suggestion(suggestion)
111
+ when 'i' then nil
112
+ when 'y'
113
+ line.pattern = suggestion
114
+ when 'e'
115
+ pattern_change(line)
116
+ when 'd'
117
+ line.remove!
118
+ end
119
+ end
120
+
121
+ def make_suggestion(suggestion)
122
+ ask(<<~QUESTION, limited_to: %w[y i e d])
123
+ Replace with: #{suggestion.inspect}?
124
+ (y) yes
125
+ (i) ignore
126
+ (e) edit the pattern
127
+ (d) delete the pattern
128
+ QUESTION
129
+ end
130
+
131
+ def pattern_fix(line)
132
+ case pattern_suggest_fixing
133
+ when 'e' then pattern_change(line)
134
+ when 'i' then nil
135
+ when 'd' then line.remove!
136
+ end
137
+ end
138
+
139
+ def pattern_suggest_fixing
140
+ ask(<<~QUESTION, limited_to: %w[i e d])
141
+ (e) edit the pattern
142
+ (d) delete the pattern
143
+ (i) ignore
144
+ QUESTION
145
+ end
146
+
147
+ def pattern_change(line)
148
+ new_pattern = ask("Replace pattern #{line.pattern.inspect} with: ")
149
+ return if new_pattern.empty?
150
+
151
+ line.pattern = new_pattern
152
+ end
153
+
154
+ def unrecognized_line(line)
155
+ return line unless line.is_a?(Codeowners::Checker::Group::UnrecognizedLine)
156
+
157
+ case unrecognized_line_suggest_fixing(line)
158
+ when 'i' then line
159
+ when 'y' then unrecognized_line_new_line
160
+ when 'd' then nil
161
+ end
162
+ end
163
+
164
+ def unrecognized_line_suggest_fixing(line)
165
+ ask(<<~QUESTION, limited_to: %w[y i d])
166
+ #{line.to_s.inspect} is in unrecognized format. Would you like to edit?
167
+ (y) yes
168
+ (i) ignore
169
+ (d) delete the line
170
+ QUESTION
171
+ end
172
+
173
+ def unrecognized_line_new_line
174
+ line = nil
175
+ begin
176
+ new_line_string = ask('New line: ')
177
+ line = Codeowners::Checker::Group::Line.build(new_line_string)
178
+ end while line.is_a?(Codeowners::Checker::Group::UnrecognizedLine)
179
+ line
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ Git::Lib.class_eval do
4
+ def config_set(name, value)
5
+ command('config', [name, value])
6
+ rescue Git::GitExecuteError
7
+ command('config', ['--add', name, value])
8
+ end
9
+
10
+ def config_get(name)
11
+ do_get = proc do |_path|
12
+ command('config', ['--get', name])
13
+ end
14
+
15
+ if @git_dir
16
+ Dir.chdir(@git_dir, &do_get)
17
+ else
18
+ do_get.call
19
+ end
20
+ end
21
+ end
22
+
23
+ module Codeowners
24
+ class AnonymousGit
25
+ include Git
26
+ end
27
+
28
+ # Connfigure and manage the git config file.
29
+ class Config
30
+ def initialize(git = AnonymousGit.new)
31
+ @git = git
32
+ end
33
+
34
+ def default_owner
35
+ @git.config('user.owner')
36
+ end
37
+
38
+ def default_owner=(name)
39
+ @git.config('user.owner', name)
40
+ end
41
+
42
+ def to_h
43
+ {
44
+ default_owner: default_owner
45
+ }
46
+ end
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,239 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: codeowners-checker
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jônatas Davi Paganini
8
+ - Eva Kadlecová
9
+ - Michal Papis
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2019-03-01 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: fuzzy_match
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '2.1'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '2.1'
29
+ - !ruby/object:Gem::Dependency
30
+ name: git
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '1.5'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '1.5'
43
+ - !ruby/object:Gem::Dependency
44
+ name: thor
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: 0.20.3
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: 0.20.3
57
+ - !ruby/object:Gem::Dependency
58
+ name: bundler
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '1.16'
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: '1.16'
71
+ - !ruby/object:Gem::Dependency
72
+ name: pry
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - "~>"
76
+ - !ruby/object:Gem::Version
77
+ version: 0.12.2
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - "~>"
83
+ - !ruby/object:Gem::Version
84
+ version: 0.12.2
85
+ - !ruby/object:Gem::Dependency
86
+ name: rake
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: '10.0'
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - "~>"
97
+ - !ruby/object:Gem::Version
98
+ version: '10.0'
99
+ - !ruby/object:Gem::Dependency
100
+ name: rb-readline
101
+ requirement: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - "~>"
104
+ - !ruby/object:Gem::Version
105
+ version: 0.5.5
106
+ type: :development
107
+ prerelease: false
108
+ version_requirements: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - "~>"
111
+ - !ruby/object:Gem::Version
112
+ version: 0.5.5
113
+ - !ruby/object:Gem::Dependency
114
+ name: rspec
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - "~>"
118
+ - !ruby/object:Gem::Version
119
+ version: '3.0'
120
+ type: :development
121
+ prerelease: false
122
+ version_requirements: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - "~>"
125
+ - !ruby/object:Gem::Version
126
+ version: '3.0'
127
+ - !ruby/object:Gem::Dependency
128
+ name: rubocop
129
+ requirement: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - "~>"
132
+ - !ruby/object:Gem::Version
133
+ version: 0.61.1
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - "~>"
139
+ - !ruby/object:Gem::Version
140
+ version: 0.61.1
141
+ - !ruby/object:Gem::Dependency
142
+ name: rubocop-rspec
143
+ requirement: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - "~>"
146
+ - !ruby/object:Gem::Version
147
+ version: '1.30'
148
+ type: :development
149
+ prerelease: false
150
+ version_requirements: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - "~>"
153
+ - !ruby/object:Gem::Version
154
+ version: '1.30'
155
+ - !ruby/object:Gem::Dependency
156
+ name: simplecov
157
+ requirement: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - "~>"
160
+ - !ruby/object:Gem::Version
161
+ version: 0.16.1
162
+ type: :development
163
+ prerelease: false
164
+ version_requirements: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - "~>"
167
+ - !ruby/object:Gem::Version
168
+ version: 0.16.1
169
+ description:
170
+ email:
171
+ - open-source@toptal.com
172
+ executables:
173
+ - codeowners-checker
174
+ extensions: []
175
+ extra_rdoc_files: []
176
+ files:
177
+ - ".gitignore"
178
+ - ".projections.json"
179
+ - ".rspec"
180
+ - ".rubocop.yml"
181
+ - ".travis.yml"
182
+ - CODE_OF_CONDUCT.md
183
+ - Gemfile
184
+ - Gemfile.lock
185
+ - Guardfile
186
+ - LICENSE.txt
187
+ - README.md
188
+ - Rakefile
189
+ - bin/codeowners-checker
190
+ - bin/console
191
+ - bin/setup
192
+ - codeowners-checker.gemspec
193
+ - demos/missing_reference.svg
194
+ - demos/useless_pattern.svg
195
+ - lib/codeowners/checker.rb
196
+ - lib/codeowners/checker/array.rb
197
+ - lib/codeowners/checker/code_owners.rb
198
+ - lib/codeowners/checker/file_as_array.rb
199
+ - lib/codeowners/checker/group.rb
200
+ - lib/codeowners/checker/group/comment.rb
201
+ - lib/codeowners/checker/group/empty.rb
202
+ - lib/codeowners/checker/group/group_begin_comment.rb
203
+ - lib/codeowners/checker/group/group_end_comment.rb
204
+ - lib/codeowners/checker/group/line.rb
205
+ - lib/codeowners/checker/group/pattern.rb
206
+ - lib/codeowners/checker/group/unrecognized_line.rb
207
+ - lib/codeowners/checker/line_grouper.rb
208
+ - lib/codeowners/checker/version.rb
209
+ - lib/codeowners/cli/base.rb
210
+ - lib/codeowners/cli/check.rb
211
+ - lib/codeowners/cli/config.rb
212
+ - lib/codeowners/cli/filter.rb
213
+ - lib/codeowners/cli/main.rb
214
+ - lib/codeowners/config.rb
215
+ homepage:
216
+ licenses:
217
+ - MIT
218
+ metadata: {}
219
+ post_install_message:
220
+ rdoc_options: []
221
+ require_paths:
222
+ - lib
223
+ required_ruby_version: !ruby/object:Gem::Requirement
224
+ requirements:
225
+ - - ">="
226
+ - !ruby/object:Gem::Version
227
+ version: '0'
228
+ required_rubygems_version: !ruby/object:Gem::Requirement
229
+ requirements:
230
+ - - ">="
231
+ - !ruby/object:Gem::Version
232
+ version: '0'
233
+ requirements: []
234
+ rubyforge_project:
235
+ rubygems_version: 2.7.7
236
+ signing_key:
237
+ specification_version: 4
238
+ summary: Check consistency of Github CODEOWNERS and git changes.
239
+ test_files: []