codeowners-checker 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -1
- data/codeowners-checker.gemspec +11 -3
- data/issues.md +29 -0
- data/lib/codeowners/checker.rb +17 -8
- data/lib/codeowners/checker/code_owners.rb +1 -1
- data/lib/codeowners/checker/file_as_array.rb +3 -0
- data/lib/codeowners/checker/group.rb +21 -9
- data/lib/codeowners/checker/group/line.rb +7 -21
- data/lib/codeowners/checker/group/pattern.rb +27 -8
- data/lib/codeowners/checker/line_grouper.rb +7 -1
- data/lib/codeowners/checker/owner.rb +12 -0
- data/lib/codeowners/checker/version.rb +1 -1
- data/lib/codeowners/cli/main.rb +118 -31
- data/lib/codeowners/cli/suggest_file_from_pattern.rb +95 -0
- data/lib/codeowners/config.rb +1 -0
- metadata +7 -17
- data/.gitignore +0 -12
- data/.projections.json +0 -4
- data/.rspec +0 -3
- data/.rubocop.yml +0 -28
- data/.travis.yml +0 -5
- data/Gemfile +0 -5
- data/Gemfile.lock +0 -78
- data/Guardfile +0 -39
- data/Rakefile +0 -10
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/demos/missing_reference.svg +0 -1
- data/demos/useless_pattern.svg +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 94005a8b1e6eae5bf06a313f752d8adc3aa060d452487f20f22d8e1bef3dddb3
|
4
|
+
data.tar.gz: f02eab71b3bbb472c9ee3ca3d721dcecd611cd55b0a8ab5746f9606b8d6f4c4b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71ab0e69066731091f2ac51afb108a1ba67b17dc6e0d7693a4227ea9964c46d7833c9ab1e89cb822187d1f05fc70a39541aaa0a9e3d30c18bd2c2ee04d5431e8
|
7
|
+
data.tar.gz: 839ea4f56af40e7dfdc9ca452c6caddd1ea4ee0d6bc5884f5dd37f7ead3fc2a89739ac8ae01279c5e1518d95feba6df02a4209607648f5051bc5508d3d480ad2
|
data/README.md
CHANGED
@@ -9,7 +9,6 @@ between two git revisions.
|
|
9
9
|
|
10
10
|
## Usage
|
11
11
|
|
12
|
-
|
13
12
|
### Configure
|
14
13
|
|
15
14
|
$ codeowners-checker config owner <@owner>
|
@@ -114,6 +113,9 @@ the comments defining the group are deleted as well as the pattern.
|
|
114
113
|
|
115
114
|
![Useless pattern example](demos/useless_pattern.svg)
|
116
115
|
|
116
|
+
You can also use [fzf](https://github.com/junegunn/fzf) to pick better results
|
117
|
+
and interactively choose the right file.
|
118
|
+
|
117
119
|
Invalid patterns were fixed and the group `Security` was removed when deleting the only pattern
|
118
120
|
in the group:
|
119
121
|
```
|
data/codeowners-checker.gemspec
CHANGED
@@ -3,19 +3,19 @@
|
|
3
3
|
lib = File.expand_path('lib', __dir__)
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
5
|
require 'codeowners/checker/version'
|
6
|
+
require 'codeowners/cli/suggest_file_from_pattern'
|
6
7
|
|
7
8
|
Gem::Specification.new do |spec|
|
8
9
|
spec.name = 'codeowners-checker'
|
9
10
|
spec.version = Codeowners::Checker::VERSION
|
10
11
|
spec.authors = ['Jônatas Davi Paganini', 'Eva Kadlecová', 'Michal Papis']
|
11
12
|
spec.email = ['open-source@toptal.com']
|
13
|
+
spec.homepage = 'https://github.com/toptal/codeowners-checker'
|
12
14
|
|
13
15
|
spec.summary = 'Check consistency of Github CODEOWNERS and git changes.'
|
14
16
|
spec.license = 'MIT'
|
15
17
|
|
16
|
-
spec.files =
|
17
|
-
f.match(%r{^(test|spec|features)/})
|
18
|
-
end
|
18
|
+
spec.files = Dir['codeowners-checker.gemspec', '*.{md,txt}', 'lib/**/*.rb']
|
19
19
|
spec.bindir = 'bin'
|
20
20
|
spec.executables = ['codeowners-checker']
|
21
21
|
spec.require_paths = ['lib']
|
@@ -31,4 +31,12 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.add_development_dependency 'rubocop', '~> 0.61.1'
|
32
32
|
spec.add_development_dependency 'rubocop-rspec', '~> 1.30'
|
33
33
|
spec.add_development_dependency 'simplecov', '~> 0.16.1'
|
34
|
+
|
35
|
+
unless ENV['TRAVIS']
|
36
|
+
unless Codeowners::Cli::SuggestFileFromPattern.installed_fzf?
|
37
|
+
spec.post_install_message = <<~MESSAGE
|
38
|
+
Please, install `fzf` for a better experience.
|
39
|
+
MESSAGE
|
40
|
+
end
|
41
|
+
end
|
34
42
|
end
|
data/issues.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
problems
|
2
|
+
|
3
|
+
- linked lines groups serve to attach or dettach specific info into a chunk of text
|
4
|
+
- linked lines belong to a group and group belong to file
|
5
|
+
|
6
|
+
|
7
|
+
what we're trying to solve
|
8
|
+
|
9
|
+
1. we have a file that have a tree structure without holding the tree
|
10
|
+
CodeOwners holds the flat structure
|
11
|
+
|
12
|
+
2. we need to interpret and parse the tree structure from the file
|
13
|
+
Group holds the tree structure with LineGroupers and pure lines
|
14
|
+
|
15
|
+
3. we need to iterate over the lines to verify the file consistency
|
16
|
+
Checker do that.
|
17
|
+
|
18
|
+
3.1 we interact with user when find similar and duplicated lines
|
19
|
+
|
20
|
+
4. we need to remove a line and remove a group if the line was the last pattern
|
21
|
+
in the group.
|
22
|
+
- the Line knows how to remove them selves.
|
23
|
+
|
24
|
+
5. we need to add or change lines
|
25
|
+
- the Group knows what file it belongs and allow to insert a pattern in the
|
26
|
+
middle or in the bottom of the file
|
27
|
+
|
28
|
+
|
29
|
+
|
data/lib/codeowners/checker.rb
CHANGED
@@ -34,11 +34,8 @@ module Codeowners
|
|
34
34
|
changes_to_analyze.select { |_k, v| v == 'A' }.keys
|
35
35
|
end
|
36
36
|
|
37
|
-
def
|
38
|
-
{
|
39
|
-
missing_ref: missing_reference,
|
40
|
-
useless_pattern: useless_pattern
|
41
|
-
}
|
37
|
+
def fix!
|
38
|
+
catch(:user_quit) { results }
|
42
39
|
end
|
43
40
|
|
44
41
|
def changes_for_patterns(patterns)
|
@@ -82,7 +79,7 @@ module Codeowners
|
|
82
79
|
end
|
83
80
|
|
84
81
|
def pattern_has_files(pattern)
|
85
|
-
@git.ls_files(pattern).any?
|
82
|
+
@git.ls_files(pattern.gsub(%r{^/}, '')).any?
|
86
83
|
end
|
87
84
|
|
88
85
|
def defined_owner?(file)
|
@@ -107,17 +104,29 @@ module Codeowners
|
|
107
104
|
codeowners.main_group
|
108
105
|
end
|
109
106
|
|
107
|
+
def consistent?
|
108
|
+
results.values.all?(&:empty?)
|
109
|
+
end
|
110
|
+
|
110
111
|
def commit_changes!
|
111
112
|
@git.add(codeowners_filename)
|
112
113
|
@git.commit('Fix pattern :robot:')
|
113
114
|
end
|
114
115
|
|
115
|
-
private
|
116
|
-
|
117
116
|
def codeowners_filename
|
118
117
|
directories = ['', '.github', 'docs', '.gitlab']
|
119
118
|
paths = directories.map { |dir| File.join(@repo_dir, dir, 'CODEOWNERS') }
|
120
119
|
Dir.glob(paths).first || paths.first
|
121
120
|
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def results
|
125
|
+
@results ||=
|
126
|
+
{
|
127
|
+
missing_ref: missing_reference,
|
128
|
+
useless_pattern: useless_pattern
|
129
|
+
}
|
130
|
+
end
|
122
131
|
end
|
123
132
|
end
|
@@ -9,12 +9,15 @@ module Codeowners
|
|
9
9
|
@target_dir, = File.split(@file)
|
10
10
|
end
|
11
11
|
|
12
|
+
# @return <Array> of lines chomped
|
12
13
|
def content
|
13
14
|
@content ||= File.readlines(@file).map(&:chomp)
|
14
15
|
rescue Errno::ENOENT
|
15
16
|
@content = []
|
16
17
|
end
|
17
18
|
|
19
|
+
# Save content to the @file
|
20
|
+
# Creates the directory of the file if needed
|
18
21
|
def content=(content)
|
19
22
|
@content = content
|
20
23
|
|
@@ -25,7 +25,7 @@ module Codeowners
|
|
25
25
|
if object.is_a?(Group)
|
26
26
|
object.each(&block)
|
27
27
|
else
|
28
|
-
|
28
|
+
yield(object)
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -38,8 +38,14 @@ module Codeowners
|
|
38
38
|
@list.flat_map(&:to_content)
|
39
39
|
end
|
40
40
|
|
41
|
+
def to_file
|
42
|
+
@list.flat_map(&:to_file)
|
43
|
+
end
|
44
|
+
|
41
45
|
# Returns an array of strings representing the structure of the group.
|
42
46
|
# It indent internal subgroups for readability and debugging purposes.
|
47
|
+
# rubocop:disable Metrics/MethodLength
|
48
|
+
# rubocop:disable Metrics/AbcSize
|
43
49
|
def to_tree(indentation = '')
|
44
50
|
@list.each_with_index.flat_map do |item, index|
|
45
51
|
if indentation.empty?
|
@@ -53,6 +59,8 @@ module Codeowners
|
|
53
59
|
end
|
54
60
|
end
|
55
61
|
end
|
62
|
+
# rubocop:enable Metrics/MethodLength
|
63
|
+
# rubocop:enable Metrics/AbcSize
|
56
64
|
|
57
65
|
def owner
|
58
66
|
owners.first
|
@@ -67,7 +75,7 @@ module Codeowners
|
|
67
75
|
|
68
76
|
def subgroups_owned_by(owner)
|
69
77
|
@list.flat_map do |item|
|
70
|
-
|
78
|
+
next unless item.is_a?(Group)
|
71
79
|
|
72
80
|
a = []
|
73
81
|
a << item if item.owner == owner
|
@@ -110,7 +118,7 @@ module Codeowners
|
|
110
118
|
end
|
111
119
|
|
112
120
|
def ==(other)
|
113
|
-
other.
|
121
|
+
other.is_a?(Group) && other.list == list
|
114
122
|
end
|
115
123
|
|
116
124
|
protected
|
@@ -125,17 +133,21 @@ module Codeowners
|
|
125
133
|
end.compact
|
126
134
|
end
|
127
135
|
|
136
|
+
# rubocop:disable Metrics/AbcSize
|
128
137
|
def insert_at_index(line)
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
138
|
+
new_patterns_sorted = @list.grep(Pattern).dup.push(line).sort
|
139
|
+
previous_line_index = new_patterns_sorted.index { |l| l.equal? line } - 1
|
140
|
+
previous_line = new_patterns_sorted[previous_line_index]
|
141
|
+
padding = previous_line.pattern.size + previous_line.whitespace - line.pattern.size
|
142
|
+
line.whitespace = [1, padding].max
|
143
|
+
|
144
|
+
if previous_line_index >= 0
|
145
|
+
@list.index { |l| l.equal? previous_line } + 1
|
135
146
|
else
|
136
147
|
find_last_line_of_initial_comments
|
137
148
|
end
|
138
149
|
end
|
150
|
+
# rubocop:enable Metrics/AbcSize
|
139
151
|
|
140
152
|
def find_last_line_of_initial_comments
|
141
153
|
@list.each_with_index do |item, index|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'pathname'
|
3
4
|
module Codeowners
|
4
5
|
class Checker
|
5
6
|
class Group
|
@@ -8,7 +9,7 @@ module Codeowners
|
|
8
9
|
class Line
|
9
10
|
attr_accessor :parent
|
10
11
|
|
11
|
-
def self.build(line
|
12
|
+
def self.build(line)
|
12
13
|
subclasses.each do |klass|
|
13
14
|
return klass.new(line) if klass.match?(line)
|
14
15
|
end
|
@@ -31,6 +32,10 @@ module Codeowners
|
|
31
32
|
to_s
|
32
33
|
end
|
33
34
|
|
35
|
+
def to_file
|
36
|
+
to_s
|
37
|
+
end
|
38
|
+
|
34
39
|
def pattern?
|
35
40
|
is_a?(Pattern)
|
36
41
|
end
|
@@ -41,7 +46,7 @@ module Codeowners
|
|
41
46
|
|
42
47
|
def remove!
|
43
48
|
parent&.remove(self)
|
44
|
-
parent = nil
|
49
|
+
self.parent = nil
|
45
50
|
end
|
46
51
|
|
47
52
|
def ==(other)
|
@@ -53,25 +58,6 @@ module Codeowners
|
|
53
58
|
def <=>(other)
|
54
59
|
to_s <=> other.to_s
|
55
60
|
end
|
56
|
-
|
57
|
-
# Pick all files from parent folder of pattern.
|
58
|
-
# This is used to build a list of suggestions case the pattern is not
|
59
|
-
# matching.
|
60
|
-
# If the pattern use "*/*" it will consider "."
|
61
|
-
# If the pattern uses Static files, it tries to reach the parent.
|
62
|
-
# If the pattern revers to the root folder, pick all files from the
|
63
|
-
# current pattern dir.
|
64
|
-
def suggest_files_for_pattern
|
65
|
-
parent_folders = pattern.split('/')[0..-2]
|
66
|
-
parent_folders << '*' if parent_folders[-1] != '*'
|
67
|
-
files = Dir[File.join(*parent_folders)] || []
|
68
|
-
files.map(&method(:normalize_path))
|
69
|
-
end
|
70
|
-
|
71
|
-
def normalize_path(file)
|
72
|
-
Pathname.new(file)
|
73
|
-
.relative_path_from(Pathname.new('.')).to_s
|
74
|
-
end
|
75
61
|
end
|
76
62
|
end
|
77
63
|
end
|
@@ -1,17 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'line'
|
4
|
+
require_relative '../owner'
|
4
5
|
|
5
6
|
module Codeowners
|
6
7
|
class Checker
|
7
8
|
class Group
|
8
9
|
# Defines and manages line type pattern.
|
10
|
+
# Parse the line into pattern, owners and whitespaces.
|
9
11
|
class Pattern < Line
|
10
|
-
attr_accessor :
|
12
|
+
attr_accessor :owners, :whitespace
|
13
|
+
attr_reader :pattern
|
11
14
|
|
12
15
|
def self.match?(line)
|
13
16
|
_pattern, *owners = line.split(/\s+/)
|
14
|
-
|
17
|
+
Owner.valid?(*owners)
|
15
18
|
end
|
16
19
|
|
17
20
|
def initialize(line)
|
@@ -23,22 +26,38 @@ module Codeowners
|
|
23
26
|
owners.first
|
24
27
|
end
|
25
28
|
|
29
|
+
# Parse the line counting whitespaces between pattern and owners.
|
26
30
|
def parse(line)
|
27
31
|
@pattern, *@owners = line.split(/\s+/)
|
32
|
+
@whitespace = line.split('@').first.count(' ') - 1
|
28
33
|
end
|
29
34
|
|
30
35
|
def match_file?(file)
|
31
|
-
|
36
|
+
if !pattern.include?('/') || pattern.include?('**')
|
37
|
+
File.fnmatch(pattern.gsub(%r{^/}, ''), file, File::FNM_DOTMATCH)
|
38
|
+
else
|
39
|
+
File.fnmatch(pattern.gsub(%r{^/}, ''), file, File::FNM_PATHNAME | File::FNM_DOTMATCH)
|
40
|
+
end
|
32
41
|
end
|
33
42
|
|
34
|
-
def
|
35
|
-
|
43
|
+
def pattern=(new_pattern)
|
44
|
+
@whitespace += @pattern.size - new_pattern.size
|
45
|
+
@whitespace = 1 if @whitespace < 1
|
46
|
+
|
47
|
+
@pattern = new_pattern
|
36
48
|
end
|
37
49
|
|
38
|
-
|
50
|
+
# @return String with the pattern and owners
|
51
|
+
# Use @param preserve_whitespaces to keep the previous identation.
|
52
|
+
def to_file(preserve_whitespaces: true)
|
53
|
+
line = pattern
|
54
|
+
spaces = preserve_whitespaces ? whitespace : 0
|
55
|
+
line << ' ' * spaces
|
56
|
+
[line, *owners].join(' ')
|
57
|
+
end
|
39
58
|
|
40
|
-
def
|
41
|
-
|
59
|
+
def to_s
|
60
|
+
to_file(preserve_whitespaces: false)
|
42
61
|
end
|
43
62
|
end
|
44
63
|
end
|
@@ -13,6 +13,9 @@ module Codeowners
|
|
13
13
|
@lines = lines
|
14
14
|
end
|
15
15
|
|
16
|
+
# rubocop:disable Metrics/AbcSize
|
17
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
18
|
+
# rubocop:disable Metrics/MethodLength
|
16
19
|
def call
|
17
20
|
lines.each_with_index do |line, index|
|
18
21
|
case line
|
@@ -46,6 +49,9 @@ module Codeowners
|
|
46
49
|
end
|
47
50
|
group_buffer.first
|
48
51
|
end
|
52
|
+
# rubocop:enable Metrics/AbcSize
|
53
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
54
|
+
# rubocop:enable Metrics/MethodLength
|
49
55
|
|
50
56
|
private
|
51
57
|
|
@@ -55,7 +61,7 @@ module Codeowners
|
|
55
61
|
index.positive? && lines[index - 1].is_a?(Codeowners::Checker::Group::Empty)
|
56
62
|
end
|
57
63
|
|
58
|
-
def new_owner?(line, index)
|
64
|
+
def new_owner?(line, index) # rubocop:disable Metrics/MethodLength
|
59
65
|
if previous_line_empty?(index)
|
60
66
|
offset = 2
|
61
67
|
while (index - offset).positive?
|
data/lib/codeowners/cli/main.rb
CHANGED
@@ -1,29 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'fuzzy_match'
|
4
|
-
|
5
3
|
require_relative '../checker'
|
6
4
|
require_relative 'base'
|
7
5
|
require_relative 'config'
|
8
6
|
require_relative 'filter'
|
7
|
+
require_relative 'suggest_file_from_pattern'
|
8
|
+
require_relative '../checker/owner'
|
9
9
|
|
10
10
|
module Codeowners
|
11
11
|
module Cli
|
12
12
|
# Command Line Interface used by bin/codeowners-checker.
|
13
|
-
class Main < Base
|
13
|
+
class Main < Base # rubocop:disable Metrics/ClassLength
|
14
14
|
LABEL = { missing_ref: 'Missing references', useless_pattern: 'No files matching with the pattern' }.freeze
|
15
15
|
option :from, default: 'origin/master'
|
16
16
|
option :to, default: 'HEAD'
|
17
17
|
option :interactive, default: true, type: :boolean, aliases: '-i'
|
18
|
+
option :autocommit, default: false, type: :boolean, aliases: '-c'
|
18
19
|
desc 'check REPO', 'Checks .github/CODEOWNERS consistency'
|
19
20
|
# for pre-commit: --from HEAD --to index
|
20
21
|
def check(repo = '.')
|
21
22
|
@codeowners_changed = false
|
22
23
|
@repo = repo
|
23
24
|
setup_checker
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
if options[:interactive]
|
26
|
+
interactive_mode
|
27
|
+
else
|
28
|
+
report_inconsistencies
|
29
|
+
end
|
27
30
|
end
|
28
31
|
|
29
32
|
desc 'filter <by-owner>', 'List owners of changed files'
|
@@ -34,12 +37,31 @@ module Codeowners
|
|
34
37
|
|
35
38
|
private
|
36
39
|
|
37
|
-
def
|
40
|
+
def interactive_mode
|
41
|
+
@checker.fix!
|
42
|
+
return unless @codeowners_changed
|
43
|
+
|
44
|
+
write_codeowners
|
45
|
+
@checker.commit_changes! if options[:autocommit] || yes?('Commit changes?')
|
46
|
+
end
|
47
|
+
|
48
|
+
def report_inconsistencies
|
49
|
+
if @checker.consistent?
|
50
|
+
puts '✅ File is consistent'
|
51
|
+
exit 0
|
52
|
+
else
|
53
|
+
puts "File #{@checker.codeowners_filename} is inconsistent:"
|
54
|
+
report_errors!
|
55
|
+
exit(-1)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def setup_checker # rubocop:disable Metrics/AbcSize
|
38
60
|
to = options[:to] != 'index' ? options[:to] : nil
|
39
61
|
@checker = Codeowners::Checker.new(@repo, options[:from], to)
|
40
62
|
@checker.when_useless_pattern = method(:suggest_fix_for)
|
41
63
|
@checker.when_new_file = method(:suggest_add_to_codeowners)
|
42
|
-
@checker.transformers << method(:unrecognized_line)
|
64
|
+
@checker.transformers << method(:unrecognized_line) if options[:interactive]
|
43
65
|
end
|
44
66
|
|
45
67
|
def write_codeowners
|
@@ -47,15 +69,24 @@ module Codeowners
|
|
47
69
|
end
|
48
70
|
|
49
71
|
def suggest_add_to_codeowners(file)
|
50
|
-
|
72
|
+
case add_to_codeowners_dialog(file)
|
73
|
+
when 'y' then add_to_codeowners(file)
|
74
|
+
when 'i' then nil
|
75
|
+
when 'q' then throw :user_quit
|
76
|
+
end
|
77
|
+
end
|
51
78
|
|
52
|
-
|
53
|
-
|
79
|
+
def add_to_codeowners_dialog(file)
|
80
|
+
ask(<<~QUESTION, limited_to: %w[y i q])
|
81
|
+
File added: #{file.inspect}. Add owner to the CODEOWNERS file?
|
82
|
+
(y) yes
|
83
|
+
(i) ignore
|
84
|
+
(q) quit and save
|
85
|
+
QUESTION
|
86
|
+
end
|
54
87
|
|
55
|
-
|
56
|
-
|
57
|
-
return
|
58
|
-
end
|
88
|
+
def add_to_codeowners(file)
|
89
|
+
new_line = create_new_pattern(file)
|
59
90
|
|
60
91
|
subgroups = @checker.main_group.subgroups_owned_by(new_line.owner)
|
61
92
|
add_pattern(new_line, subgroups)
|
@@ -63,14 +94,42 @@ module Codeowners
|
|
63
94
|
@codeowners_changed = true
|
64
95
|
end
|
65
96
|
|
66
|
-
def create_new_pattern(file
|
67
|
-
|
68
|
-
|
97
|
+
def create_new_pattern(file)
|
98
|
+
sorted_owners = @checker.main_group.owners.sort
|
99
|
+
list_owners(sorted_owners)
|
100
|
+
loop do
|
101
|
+
owner = new_owner(sorted_owners)
|
102
|
+
|
103
|
+
unless Codeowners::Checker::Owner.valid?(owner)
|
104
|
+
puts "#{owner.inspect} is not a valid owner name. Try again."
|
105
|
+
next
|
106
|
+
end
|
107
|
+
|
108
|
+
return Codeowners::Checker::Group::Pattern.new("#{file} #{owner}")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def list_owners(sorted_owners)
|
113
|
+
puts 'Owners:'
|
114
|
+
sorted_owners.each_with_index { |owner, i| puts "#{i + 1} - #{owner}" }
|
115
|
+
puts "Choose owner, add new one or leave empty to use #{@config.default_owner.inspect}."
|
116
|
+
end
|
117
|
+
|
118
|
+
def new_owner(sorted_owners)
|
119
|
+
owner = ask('New owner: ')
|
120
|
+
|
121
|
+
if owner.to_i.between?(1, sorted_owners.length)
|
122
|
+
sorted_owners[owner.to_i - 1]
|
123
|
+
elsif owner.empty?
|
124
|
+
@config.default_owner
|
125
|
+
else
|
126
|
+
owner
|
127
|
+
end
|
69
128
|
end
|
70
129
|
|
71
130
|
def add_pattern(pattern, subgroups)
|
72
131
|
unless subgroups.empty?
|
73
|
-
return if insert_pattern_into_subgroup(pattern, subgroups)
|
132
|
+
return if insert_pattern_into_subgroup(pattern, subgroups)
|
74
133
|
end
|
75
134
|
|
76
135
|
@checker.main_group.add(pattern) if yes?('Add to the end of the CODEOWNERS file?')
|
@@ -91,10 +150,10 @@ module Codeowners
|
|
91
150
|
end
|
92
151
|
|
93
152
|
def suggest_fix_for(line)
|
94
|
-
|
95
|
-
suggestion = search.find(line.pattern)
|
153
|
+
return unless options[:interactive]
|
96
154
|
|
97
155
|
puts "Pattern #{line.pattern.inspect} doesn't match."
|
156
|
+
suggestion = Codeowners::Cli::SuggestFileFromPattern.new(line.pattern).pick_suggestion
|
98
157
|
|
99
158
|
# TODO: Handle duplicate patterns.
|
100
159
|
if suggestion
|
@@ -109,22 +168,21 @@ module Codeowners
|
|
109
168
|
def apply_suggestion(line, suggestion)
|
110
169
|
case make_suggestion(suggestion)
|
111
170
|
when 'i' then nil
|
112
|
-
when 'y'
|
113
|
-
|
114
|
-
when '
|
115
|
-
|
116
|
-
when 'd'
|
117
|
-
line.remove!
|
171
|
+
when 'y' then line.pattern = suggestion
|
172
|
+
when 'e' then pattern_change(line)
|
173
|
+
when 'd' then line.remove!
|
174
|
+
when 'q' then throw :user_quit
|
118
175
|
end
|
119
176
|
end
|
120
177
|
|
121
178
|
def make_suggestion(suggestion)
|
122
|
-
ask(<<~QUESTION, limited_to: %w[y i e d])
|
179
|
+
ask(<<~QUESTION, limited_to: %w[y i e d q])
|
123
180
|
Replace with: #{suggestion.inspect}?
|
124
181
|
(y) yes
|
125
182
|
(i) ignore
|
126
183
|
(e) edit the pattern
|
127
184
|
(d) delete the pattern
|
185
|
+
(q) quit and save
|
128
186
|
QUESTION
|
129
187
|
end
|
130
188
|
|
@@ -133,14 +191,16 @@ module Codeowners
|
|
133
191
|
when 'e' then pattern_change(line)
|
134
192
|
when 'i' then nil
|
135
193
|
when 'd' then line.remove!
|
194
|
+
when 'q' then throw :user_quit
|
136
195
|
end
|
137
196
|
end
|
138
197
|
|
139
198
|
def pattern_suggest_fixing
|
140
|
-
ask(<<~QUESTION, limited_to: %w[i e d])
|
199
|
+
ask(<<~QUESTION, limited_to: %w[i e d q])
|
141
200
|
(e) edit the pattern
|
142
201
|
(d) delete the pattern
|
143
202
|
(i) ignore
|
203
|
+
(q) quit and save
|
144
204
|
QUESTION
|
145
205
|
end
|
146
206
|
|
@@ -172,12 +232,39 @@ module Codeowners
|
|
172
232
|
|
173
233
|
def unrecognized_line_new_line
|
174
234
|
line = nil
|
175
|
-
|
235
|
+
loop do
|
176
236
|
new_line_string = ask('New line: ')
|
177
237
|
line = Codeowners::Checker::Group::Line.build(new_line_string)
|
178
|
-
|
238
|
+
break unless line.is_a?(Codeowners::Checker::Group::UnrecognizedLine)
|
239
|
+
end
|
240
|
+
@codeowners_changed = true
|
179
241
|
line
|
180
242
|
end
|
243
|
+
|
244
|
+
def ask(message, *opts)
|
245
|
+
return unless options[:interactive]
|
246
|
+
|
247
|
+
super
|
248
|
+
end
|
249
|
+
|
250
|
+
def yes?(message, *opts)
|
251
|
+
return unless options[:interactive]
|
252
|
+
|
253
|
+
super
|
254
|
+
end
|
255
|
+
|
256
|
+
LABELS = {
|
257
|
+
missing_ref: 'No owner defined',
|
258
|
+
useless_pattern: 'Useless patterns'
|
259
|
+
}.freeze
|
260
|
+
|
261
|
+
def report_errors!
|
262
|
+
@checker.fix!.each do |error_type, inconsistencies|
|
263
|
+
next if inconsistencies.empty?
|
264
|
+
|
265
|
+
puts LABELS[error_type], '-' * 30, inconsistencies, '-' * 30
|
266
|
+
end
|
267
|
+
end
|
181
268
|
end
|
182
269
|
end
|
183
270
|
end
|