codeowners-checker 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []