codeowners-checker 1.0.0 → 1.0.1
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 +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
|

|
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
|