code_ownership 1.32.7 → 1.32.9
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/lib/code_ownership/cli.rb +1 -1
- data/lib/code_ownership/mapper.rb +8 -5
- data/lib/code_ownership/private/codeowners_file.rb +52 -0
- data/lib/code_ownership/private/glob_cache.rb +48 -10
- data/lib/code_ownership/private/ownership_mappers/file_annotations.rb +20 -12
- data/lib/code_ownership/private/ownership_mappers/js_package_ownership.rb +6 -15
- data/lib/code_ownership/private/ownership_mappers/package_ownership.rb +10 -19
- data/lib/code_ownership/private/ownership_mappers/team_globs.rb +14 -7
- data/lib/code_ownership/private/ownership_mappers/team_yml_ownership.rb +14 -7
- data/lib/code_ownership/private/validations/files_have_owners.rb +5 -5
- data/lib/code_ownership/private/validations/files_have_unique_owners.rb +5 -9
- data/lib/code_ownership/private.rb +27 -1
- data/lib/code_ownership.rb +13 -6
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 71768f254f9fbb891679c002c16c20bd70a2125460e3c66995821cfe2784fe56
|
4
|
+
data.tar.gz: ce5698b1505d306026f807a142f648942ea06d3ab65576d05c623f6bdbdc1dfa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b2e5da9f4e02b4db10c47be40b1b4bce33f7c44ad18797ff326a6e245f5dc57b2a8534c74fde74a1966970720a9bedf50d59858923e07f64d8e21d9f8411df1
|
7
|
+
data.tar.gz: b08f25b9008d86b19207931cb44c13adea814b998af8700b1fa445ad09e48e6549f86d6e4b75db41e5a783bf080ea5e25371d97db5307de8c5451bcd3d4fe3da
|
data/lib/code_ownership/cli.rb
CHANGED
@@ -40,15 +40,18 @@ module CodeOwnership
|
|
40
40
|
#
|
41
41
|
sig do
|
42
42
|
abstract.params(files: T::Array[String]).
|
43
|
-
returns(T::Hash[String,
|
43
|
+
returns(T::Hash[String, ::CodeTeams::Team])
|
44
44
|
end
|
45
|
-
def
|
45
|
+
def globs_to_owner(files)
|
46
46
|
end
|
47
47
|
|
48
|
+
#
|
49
|
+
# This should be fast when run with MANY files
|
50
|
+
#
|
48
51
|
sig do
|
49
|
-
abstract.
|
52
|
+
abstract.params(cache: GlobsToOwningTeamMap, files: T::Array[String]).returns(GlobsToOwningTeamMap)
|
50
53
|
end
|
51
|
-
def
|
54
|
+
def update_cache(cache, files)
|
52
55
|
end
|
53
56
|
|
54
57
|
sig { abstract.returns(String) }
|
@@ -64,7 +67,7 @@ module CodeOwnership
|
|
64
67
|
glob_to_owner_map_by_mapper_description = {}
|
65
68
|
|
66
69
|
Mapper.all.each do |mapper|
|
67
|
-
mapped_files = mapper.
|
70
|
+
mapped_files = mapper.globs_to_owner(Private.tracked_files)
|
68
71
|
mapped_files.each do |glob, owner|
|
69
72
|
next if owner.nil?
|
70
73
|
glob_to_owner_map_by_mapper_description[mapper.description] ||= {}
|
@@ -90,6 +90,58 @@ module CodeOwnership
|
|
90
90
|
def self.path
|
91
91
|
Pathname.pwd.join('.github/CODEOWNERS')
|
92
92
|
end
|
93
|
+
|
94
|
+
sig { params(files: T::Array[String]).void }
|
95
|
+
def self.update_cache!(files)
|
96
|
+
cache = Private.glob_cache
|
97
|
+
# Each mapper returns a new copy of the cache subset related to that mapper,
|
98
|
+
# which is then stored back into the cache.
|
99
|
+
Mapper.all.each do |mapper|
|
100
|
+
existing_cache = cache.raw_cache_contents.fetch(mapper.description, {})
|
101
|
+
updated_cache = mapper.update_cache(existing_cache, files)
|
102
|
+
cache.raw_cache_contents[mapper.description] = updated_cache
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
sig { returns(T::Boolean) }
|
107
|
+
def self.use_codeowners_cache?
|
108
|
+
CodeownersFile.path.exist? && !Private.configuration.skip_codeowners_validation
|
109
|
+
end
|
110
|
+
|
111
|
+
sig { returns(GlobCache) }
|
112
|
+
def self.to_glob_cache
|
113
|
+
github_team_to_code_team_map = T.let({}, T::Hash[String, CodeTeams::Team])
|
114
|
+
CodeTeams.all.each do |team|
|
115
|
+
github_team = TeamPlugins::Github.for(team).github.team
|
116
|
+
github_team_to_code_team_map[github_team] = team
|
117
|
+
end
|
118
|
+
raw_cache_contents = T.let({}, GlobCache::CacheShape)
|
119
|
+
current_mapper = T.let(nil, T.nilable(String))
|
120
|
+
mapper_descriptions = Set.new(Mapper.all.map(&:description))
|
121
|
+
|
122
|
+
path.readlines.each do |line|
|
123
|
+
line_with_no_comment = line.chomp.gsub("# ", "")
|
124
|
+
if mapper_descriptions.include?(line_with_no_comment)
|
125
|
+
current_mapper = line_with_no_comment
|
126
|
+
else
|
127
|
+
next if current_mapper.nil?
|
128
|
+
next if line.chomp == ""
|
129
|
+
# The codeowners file stores paths relative to the root of directory
|
130
|
+
# Since a `/` means root of the file system from the perspective of ruby,
|
131
|
+
# we remove that beginning slash so we can correctly glob the files out.
|
132
|
+
normalized_line = line.gsub(/^# /, '').gsub(/^\//, '')
|
133
|
+
split_line = normalized_line.split
|
134
|
+
# Most lines will be in the format: /path/to/file my-github-team
|
135
|
+
# This will skip over lines that are not of the correct form
|
136
|
+
next if split_line.count > 2
|
137
|
+
entry, github_team = split_line
|
138
|
+
raw_cache_contents[current_mapper] ||= {}
|
139
|
+
raw_cache_contents.fetch(current_mapper)[T.must(entry)] = github_team_to_code_team_map.fetch(T.must(github_team))
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
GlobCache.new(raw_cache_contents)
|
144
|
+
end
|
93
145
|
end
|
94
146
|
end
|
95
147
|
end
|
@@ -7,19 +7,18 @@ module CodeOwnership
|
|
7
7
|
extend T::Sig
|
8
8
|
|
9
9
|
MapperDescription = T.type_alias { String }
|
10
|
-
GlobsByMapper = T.type_alias { T::Hash[String, CodeTeams::Team] }
|
11
10
|
|
12
11
|
CacheShape = T.type_alias do
|
13
12
|
T::Hash[
|
14
13
|
MapperDescription,
|
15
|
-
|
14
|
+
GlobsToOwningTeamMap
|
16
15
|
]
|
17
16
|
end
|
18
17
|
|
19
18
|
FilesByMapper = T.type_alias do
|
20
19
|
T::Hash[
|
21
20
|
String,
|
22
|
-
T::
|
21
|
+
T::Set[MapperDescription]
|
23
22
|
]
|
24
23
|
end
|
25
24
|
|
@@ -33,6 +32,20 @@ module CodeOwnership
|
|
33
32
|
@raw_cache_contents
|
34
33
|
end
|
35
34
|
|
35
|
+
sig { params(files: T::Array[String]).returns(FilesByMapper) }
|
36
|
+
def mapper_descriptions_that_map_files(files)
|
37
|
+
if files.count > 100
|
38
|
+
# When looking at many files, expanding the cache out using Dir.glob and checking for intersections is faster
|
39
|
+
files_by_mappers = files.map{ |f| [f, Set.new([]) ]}.to_h
|
40
|
+
files_by_mappers.merge(files_by_mappers_via_expanded_cache)
|
41
|
+
else
|
42
|
+
# When looking at few files, using File.fnmatch is faster
|
43
|
+
files_by_mappers_via_file_fnmatch(files)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
36
49
|
sig { returns(CacheShape) }
|
37
50
|
def expanded_cache
|
38
51
|
@expanded_cache = T.let(@expanded_cache, T.nilable(CacheShape))
|
@@ -53,20 +66,45 @@ module CodeOwnership
|
|
53
66
|
end
|
54
67
|
|
55
68
|
sig { returns(FilesByMapper) }
|
56
|
-
def
|
57
|
-
@
|
58
|
-
@
|
59
|
-
|
69
|
+
def files_by_mappers_via_expanded_cache
|
70
|
+
@files_by_mappers ||= T.let(@files_by_mappers, T.nilable(FilesByMapper))
|
71
|
+
@files_by_mappers ||= begin
|
72
|
+
files_by_mappers = T.let({}, FilesByMapper)
|
60
73
|
expanded_cache.each do |mapper_description, file_by_owner|
|
61
74
|
file_by_owner.each do |file, owner|
|
62
|
-
|
63
|
-
|
75
|
+
files_by_mappers[file] ||= Set.new([])
|
76
|
+
files_by_mappers.fetch(file) << mapper_description
|
64
77
|
end
|
65
78
|
end
|
66
79
|
|
67
|
-
|
80
|
+
files_by_mappers
|
68
81
|
end
|
69
82
|
end
|
83
|
+
|
84
|
+
sig { params(files: T::Array[String]).returns(FilesByMapper) }
|
85
|
+
def files_by_mappers_via_file_fnmatch(files)
|
86
|
+
files_by_mappers = T.let({}, FilesByMapper)
|
87
|
+
|
88
|
+
files.each do |file|
|
89
|
+
files_by_mappers[file] ||= Set.new([])
|
90
|
+
@raw_cache_contents.each do |mapper_description, globs_by_owner|
|
91
|
+
# As much as I'd like to *not* special case the file annotations mapper, using File.fnmatch? on the thousands of files mapped by the
|
92
|
+
# file annotations mapper is a lot of unnecessary extra work.
|
93
|
+
# Therefore we can just check if the file is in the globs directly for file annotations, otherwise use File.fnmatch
|
94
|
+
if mapper_description == OwnershipMappers::FileAnnotations::DESCRIPTION
|
95
|
+
files_by_mappers.fetch(file) << mapper_description if globs_by_owner[file]
|
96
|
+
else
|
97
|
+
globs_by_owner.each do |glob, owner|
|
98
|
+
if File.fnmatch?(glob, file, File::FNM_PATHNAME | File::FNM_EXTGLOB)
|
99
|
+
files_by_mappers.fetch(file) << mapper_description
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
files_by_mappers
|
107
|
+
end
|
70
108
|
end
|
71
109
|
end
|
72
110
|
end
|
@@ -18,9 +18,10 @@ module CodeOwnership
|
|
18
18
|
extend T::Sig
|
19
19
|
include Mapper
|
20
20
|
|
21
|
-
@@map_files_to_owners = T.let({}, T.nilable(T::Hash[String,
|
21
|
+
@@map_files_to_owners = T.let({}, T.nilable(T::Hash[String, ::CodeTeams::Team])) # rubocop:disable Style/ClassVars
|
22
22
|
|
23
23
|
TEAM_PATTERN = T.let(/\A(?:#|\/\/) @team (?<team>.*)\Z/.freeze, Regexp)
|
24
|
+
DESCRIPTION = 'Annotations at the top of file'
|
24
25
|
|
25
26
|
sig do
|
26
27
|
override.params(file: String).
|
@@ -33,9 +34,9 @@ module CodeOwnership
|
|
33
34
|
sig do
|
34
35
|
override.
|
35
36
|
params(files: T::Array[String]).
|
36
|
-
returns(T::Hash[String,
|
37
|
+
returns(T::Hash[String, ::CodeTeams::Team])
|
37
38
|
end
|
38
|
-
def
|
39
|
+
def globs_to_owner(files)
|
39
40
|
return @@map_files_to_owners if @@map_files_to_owners&.keys && @@map_files_to_owners.keys.count > 0
|
40
41
|
|
41
42
|
@@map_files_to_owners = files.each_with_object({}) do |filename_relative_to_root, mapping| # rubocop:disable Style/ClassVars
|
@@ -46,6 +47,21 @@ module CodeOwnership
|
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
50
|
+
sig do
|
51
|
+
override.params(cache: GlobsToOwningTeamMap, files: T::Array[String]).returns(GlobsToOwningTeamMap)
|
52
|
+
end
|
53
|
+
def update_cache(cache, files)
|
54
|
+
cache.merge!(globs_to_owner(files))
|
55
|
+
invalid_files = cache.keys.select do |file|
|
56
|
+
!Private.file_tracked?(file)
|
57
|
+
end
|
58
|
+
invalid_files.each do |invalid_file|
|
59
|
+
cache.delete(invalid_file)
|
60
|
+
end
|
61
|
+
|
62
|
+
cache
|
63
|
+
end
|
64
|
+
|
49
65
|
sig { params(filename: String).returns(T.nilable(CodeTeams::Team)) }
|
50
66
|
def file_annotation_based_owner(filename)
|
51
67
|
# If for a directory is named with an ownable extension, we need to skip
|
@@ -96,17 +112,9 @@ module CodeOwnership
|
|
96
112
|
end
|
97
113
|
end
|
98
114
|
|
99
|
-
sig do
|
100
|
-
override.returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
|
101
|
-
end
|
102
|
-
def codeowners_lines_to_owners
|
103
|
-
@@map_files_to_owners = nil # rubocop:disable Style/ClassVars
|
104
|
-
map_files_to_owners(Private.tracked_files)
|
105
|
-
end
|
106
|
-
|
107
115
|
sig { override.returns(String) }
|
108
116
|
def description
|
109
|
-
|
117
|
+
DESCRIPTION
|
110
118
|
end
|
111
119
|
|
112
120
|
sig { override.void }
|
@@ -24,20 +24,10 @@ module CodeOwnership
|
|
24
24
|
end
|
25
25
|
|
26
26
|
sig do
|
27
|
-
override.
|
28
|
-
params(files: T::Array[String]).
|
29
|
-
returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
|
27
|
+
override.params(cache: GlobsToOwningTeamMap, files: T::Array[String]).returns(GlobsToOwningTeamMap)
|
30
28
|
end
|
31
|
-
def
|
32
|
-
|
33
|
-
owner = owner_for_package(package)
|
34
|
-
next if owner.nil?
|
35
|
-
|
36
|
-
glob = package.directory.join('**/**').to_s
|
37
|
-
Dir.glob(glob).each do |path|
|
38
|
-
res[path] = owner
|
39
|
-
end
|
40
|
-
end
|
29
|
+
def update_cache(cache, files)
|
30
|
+
globs_to_owner(files)
|
41
31
|
end
|
42
32
|
|
43
33
|
#
|
@@ -49,9 +39,10 @@ module CodeOwnership
|
|
49
39
|
# subset of files, but rather we want code ownership for all files.
|
50
40
|
#
|
51
41
|
sig do
|
52
|
-
override.
|
42
|
+
override.params(files: T::Array[String]).
|
43
|
+
returns(T::Hash[String, ::CodeTeams::Team])
|
53
44
|
end
|
54
|
-
def
|
45
|
+
def globs_to_owner(files)
|
55
46
|
ParseJsPackages.all.each_with_object({}) do |package, res|
|
56
47
|
owner = owner_for_package(package)
|
57
48
|
next if owner.nil?
|
@@ -23,23 +23,6 @@ module CodeOwnership
|
|
23
23
|
owner_for_package(package)
|
24
24
|
end
|
25
25
|
|
26
|
-
sig do
|
27
|
-
override.
|
28
|
-
params(files: T::Array[String]).
|
29
|
-
returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
|
30
|
-
end
|
31
|
-
def map_files_to_owners(files) # rubocop:disable Lint/UnusedMethodArgument
|
32
|
-
Packs.all.each_with_object({}) do |package, res|
|
33
|
-
owner = owner_for_package(package)
|
34
|
-
next if owner.nil?
|
35
|
-
|
36
|
-
glob = package.relative_path.join('**/**').to_s
|
37
|
-
Dir.glob(glob).each do |path|
|
38
|
-
res[path] = owner
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
26
|
#
|
44
27
|
# Package ownership ignores the passed in files when generating code owners lines.
|
45
28
|
# This is because Package ownership knows that the fastest way to find code owners for package based ownership
|
@@ -49,9 +32,10 @@ module CodeOwnership
|
|
49
32
|
# subset of files, but rather we want code ownership for all files.
|
50
33
|
#
|
51
34
|
sig do
|
52
|
-
override.
|
35
|
+
override.params(files: T::Array[String]).
|
36
|
+
returns(T::Hash[String, ::CodeTeams::Team])
|
53
37
|
end
|
54
|
-
def
|
38
|
+
def globs_to_owner(files)
|
55
39
|
Packs.all.each_with_object({}) do |package, res|
|
56
40
|
owner = owner_for_package(package)
|
57
41
|
next if owner.nil?
|
@@ -65,6 +49,13 @@ module CodeOwnership
|
|
65
49
|
'Owner metadata key in package.yml'
|
66
50
|
end
|
67
51
|
|
52
|
+
sig do
|
53
|
+
override.params(cache: GlobsToOwningTeamMap, files: T::Array[String]).returns(GlobsToOwningTeamMap)
|
54
|
+
end
|
55
|
+
def update_cache(cache, files)
|
56
|
+
globs_to_owner(files)
|
57
|
+
end
|
58
|
+
|
68
59
|
sig { params(package: Packs::Pack).returns(T.nilable(CodeTeams::Team)) }
|
69
60
|
def owner_for_package(package)
|
70
61
|
raw_owner_value = package.metadata['owner']
|
@@ -10,15 +10,14 @@ module CodeOwnership
|
|
10
10
|
include Mapper
|
11
11
|
include Validator
|
12
12
|
|
13
|
-
@@map_files_to_owners = T.let(@map_files_to_owners, T.nilable(T::Hash[String,
|
13
|
+
@@map_files_to_owners = T.let(@map_files_to_owners, T.nilable(T::Hash[String, ::CodeTeams::Team])) # rubocop:disable Style/ClassVars
|
14
14
|
@@map_files_to_owners = {} # rubocop:disable Style/ClassVars
|
15
|
-
@@codeowners_lines_to_owners = T.let(@codeowners_lines_to_owners, T.nilable(T::Hash[String,
|
15
|
+
@@codeowners_lines_to_owners = T.let(@codeowners_lines_to_owners, T.nilable(T::Hash[String, ::CodeTeams::Team])) # rubocop:disable Style/ClassVars
|
16
16
|
@@codeowners_lines_to_owners = {} # rubocop:disable Style/ClassVars
|
17
17
|
|
18
18
|
sig do
|
19
|
-
|
20
|
-
|
21
|
-
returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
|
19
|
+
params(files: T::Array[String]).
|
20
|
+
returns(T::Hash[String, ::CodeTeams::Team])
|
22
21
|
end
|
23
22
|
def map_files_to_owners(files) # rubocop:disable Lint/UnusedMethodArgument
|
24
23
|
return @@map_files_to_owners if @@map_files_to_owners&.keys && @@map_files_to_owners.keys.count > 0
|
@@ -93,9 +92,17 @@ module CodeOwnership
|
|
93
92
|
end
|
94
93
|
|
95
94
|
sig do
|
96
|
-
override.
|
95
|
+
override.params(cache: GlobsToOwningTeamMap, files: T::Array[String]).returns(GlobsToOwningTeamMap)
|
97
96
|
end
|
98
|
-
def
|
97
|
+
def update_cache(cache, files)
|
98
|
+
globs_to_owner(files)
|
99
|
+
end
|
100
|
+
|
101
|
+
sig do
|
102
|
+
override.params(files: T::Array[String]).
|
103
|
+
returns(T::Hash[String, ::CodeTeams::Team])
|
104
|
+
end
|
105
|
+
def globs_to_owner(files)
|
99
106
|
return @@codeowners_lines_to_owners if @@codeowners_lines_to_owners&.keys && @@codeowners_lines_to_owners.keys.count > 0
|
100
107
|
|
101
108
|
@@codeowners_lines_to_owners = CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars
|
@@ -9,15 +9,14 @@ module CodeOwnership
|
|
9
9
|
extend T::Sig
|
10
10
|
include Mapper
|
11
11
|
|
12
|
-
@@map_files_to_owners = T.let(@map_files_to_owners, T.nilable(T::Hash[String,
|
12
|
+
@@map_files_to_owners = T.let(@map_files_to_owners, T.nilable(T::Hash[String, ::CodeTeams::Team])) # rubocop:disable Style/ClassVars
|
13
13
|
@@map_files_to_owners = {} # rubocop:disable Style/ClassVars
|
14
|
-
@@codeowners_lines_to_owners = T.let(@codeowners_lines_to_owners, T.nilable(T::Hash[String,
|
14
|
+
@@codeowners_lines_to_owners = T.let(@codeowners_lines_to_owners, T.nilable(T::Hash[String, ::CodeTeams::Team])) # rubocop:disable Style/ClassVars
|
15
15
|
@@codeowners_lines_to_owners = {} # rubocop:disable Style/ClassVars
|
16
16
|
|
17
17
|
sig do
|
18
|
-
|
19
|
-
|
20
|
-
returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
|
18
|
+
params(files: T::Array[String]).
|
19
|
+
returns(T::Hash[String, ::CodeTeams::Team])
|
21
20
|
end
|
22
21
|
def map_files_to_owners(files) # rubocop:disable Lint/UnusedMethodArgument
|
23
22
|
return @@map_files_to_owners if @@map_files_to_owners&.keys && @@map_files_to_owners.keys.count > 0
|
@@ -36,9 +35,10 @@ module CodeOwnership
|
|
36
35
|
end
|
37
36
|
|
38
37
|
sig do
|
39
|
-
override.
|
38
|
+
override.params(files: T::Array[String]).
|
39
|
+
returns(T::Hash[String, ::CodeTeams::Team])
|
40
40
|
end
|
41
|
-
def
|
41
|
+
def globs_to_owner(files)
|
42
42
|
return @@codeowners_lines_to_owners if @@codeowners_lines_to_owners&.keys && @@codeowners_lines_to_owners.keys.count > 0
|
43
43
|
|
44
44
|
@@codeowners_lines_to_owners = CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars
|
@@ -52,6 +52,13 @@ module CodeOwnership
|
|
52
52
|
@@map_files_to_owners = {} # rubocop:disable Style/ClassVars
|
53
53
|
end
|
54
54
|
|
55
|
+
sig do
|
56
|
+
override.params(cache: GlobsToOwningTeamMap, files: T::Array[String]).returns(GlobsToOwningTeamMap)
|
57
|
+
end
|
58
|
+
def update_cache(cache, files)
|
59
|
+
globs_to_owner(files)
|
60
|
+
end
|
61
|
+
|
55
62
|
sig { override.returns(String) }
|
56
63
|
def description
|
57
64
|
'Team YML ownership'
|
@@ -10,10 +10,10 @@ module CodeOwnership
|
|
10
10
|
|
11
11
|
sig { override.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) }
|
12
12
|
def validation_errors(files:, autocorrect: true, stage_changes: true)
|
13
|
-
|
14
|
-
|
15
|
-
files_not_mapped_at_all =
|
16
|
-
|
13
|
+
cache = Private.glob_cache
|
14
|
+
file_mappings = cache.mapper_descriptions_that_map_files(files)
|
15
|
+
files_not_mapped_at_all = file_mappings.select do |file, mapper_descriptions|
|
16
|
+
mapper_descriptions.count == 0
|
17
17
|
end
|
18
18
|
|
19
19
|
errors = T.let([], T::Array[String])
|
@@ -22,7 +22,7 @@ module CodeOwnership
|
|
22
22
|
errors << <<~MSG
|
23
23
|
Some files are missing ownership:
|
24
24
|
|
25
|
-
#{files_not_mapped_at_all.map { |file| "- #{file}" }.join("\n")}
|
25
|
+
#{files_not_mapped_at_all.map { |file, mappers| "- #{file}" }.join("\n")}
|
26
26
|
MSG
|
27
27
|
end
|
28
28
|
|
@@ -10,14 +10,10 @@ module CodeOwnership
|
|
10
10
|
|
11
11
|
sig { override.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) }
|
12
12
|
def validation_errors(files:, autocorrect: true, stage_changes: true)
|
13
|
-
|
14
|
-
|
15
|
-
files_mapped_by_multiple_mappers =
|
16
|
-
|
17
|
-
mappers = files_by_mapper.fetch(file, [])
|
18
|
-
if mappers.count > 1
|
19
|
-
files_mapped_by_multiple_mappers[file] = mappers
|
20
|
-
end
|
13
|
+
cache = Private.glob_cache
|
14
|
+
file_mappings = cache.mapper_descriptions_that_map_files(files)
|
15
|
+
files_mapped_by_multiple_mappers = file_mappings.select do |file, mapper_descriptions|
|
16
|
+
mapper_descriptions.count > 1
|
21
17
|
end
|
22
18
|
|
23
19
|
errors = T.let([], T::Array[String])
|
@@ -26,7 +22,7 @@ module CodeOwnership
|
|
26
22
|
errors << <<~MSG
|
27
23
|
Code ownership should only be defined for each file in one way. The following files have declared ownership in multiple ways.
|
28
24
|
|
29
|
-
#{files_mapped_by_multiple_mappers.map { |file, descriptions| "- #{file} (#{descriptions.join(', ')})" }.join("\n")}
|
25
|
+
#{files_mapped_by_multiple_mappers.map { |file, descriptions| "- #{file} (#{descriptions.to_a.join(', ')})" }.join("\n")}
|
30
26
|
MSG
|
31
27
|
end
|
32
28
|
|
@@ -44,6 +44,8 @@ module CodeOwnership
|
|
44
44
|
|
45
45
|
sig { params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).void }
|
46
46
|
def self.validate!(files:, autocorrect: true, stage_changes: true)
|
47
|
+
CodeownersFile.update_cache!(files) if CodeownersFile.use_codeowners_cache?
|
48
|
+
|
47
49
|
errors = Validator.all.flat_map do |validator|
|
48
50
|
validator.validation_errors(
|
49
51
|
files: files,
|
@@ -79,6 +81,24 @@ module CodeOwnership
|
|
79
81
|
@tracked_files ||= Dir.glob(configuration.owned_globs) - Dir.glob(configuration.unowned_globs)
|
80
82
|
end
|
81
83
|
|
84
|
+
sig { params(file: String).returns(T::Boolean) }
|
85
|
+
def self.file_tracked?(file)
|
86
|
+
# Another way to accomplish this is
|
87
|
+
# (Dir.glob(configuration.owned_globs) - Dir.glob(configuration.unowned_globs)).include?(file)
|
88
|
+
# However, globbing out can take 5 or more seconds on a large repository, dramatically slowing down
|
89
|
+
# invocations to `bin/codeownership validate --diff`.
|
90
|
+
# Using `File.fnmatch?` is a lot faster!
|
91
|
+
in_owned_globs = configuration.owned_globs.all? do |owned_glob|
|
92
|
+
File.fnmatch?(owned_glob, file, File::FNM_PATHNAME | File::FNM_EXTGLOB)
|
93
|
+
end
|
94
|
+
|
95
|
+
in_unowned_globs = configuration.unowned_globs.all? do |unowned_glob|
|
96
|
+
File.fnmatch?(unowned_glob, file, File::FNM_PATHNAME | File::FNM_EXTGLOB)
|
97
|
+
end
|
98
|
+
|
99
|
+
in_owned_globs && !in_unowned_globs
|
100
|
+
end
|
101
|
+
|
82
102
|
sig { params(team_name: String, location_of_reference: String).returns(CodeTeams::Team) }
|
83
103
|
def self.find_team!(team_name, location_of_reference)
|
84
104
|
found_team = CodeTeams.find(team_name)
|
@@ -92,7 +112,13 @@ module CodeOwnership
|
|
92
112
|
sig { returns(GlobCache) }
|
93
113
|
def self.glob_cache
|
94
114
|
@glob_cache ||= T.let(@glob_cache, T.nilable(GlobCache))
|
95
|
-
@glob_cache ||=
|
115
|
+
@glob_cache ||= begin
|
116
|
+
if CodeownersFile.use_codeowners_cache?
|
117
|
+
CodeownersFile.to_glob_cache
|
118
|
+
else
|
119
|
+
Mapper.to_glob_cache
|
120
|
+
end
|
121
|
+
end
|
96
122
|
end
|
97
123
|
end
|
98
124
|
|
data/lib/code_ownership.rb
CHANGED
@@ -19,6 +19,7 @@ module CodeOwnership
|
|
19
19
|
extend T::Helpers
|
20
20
|
|
21
21
|
requires_ancestor { Kernel }
|
22
|
+
GlobsToOwningTeamMap = T.type_alias { T::Hash[String, CodeTeams::Team] }
|
22
23
|
|
23
24
|
sig { params(file: String).returns(T.nilable(CodeTeams::Team)) }
|
24
25
|
def for_file(file)
|
@@ -50,7 +51,7 @@ module CodeOwnership
|
|
50
51
|
ownership_information << "# Code Ownership Report for `#{team.name}` Team"
|
51
52
|
Mapper.all.each do |mapper|
|
52
53
|
ownership_information << "## #{mapper.description}"
|
53
|
-
codeowners_lines = mapper.
|
54
|
+
codeowners_lines = mapper.globs_to_owner(Private.tracked_files)
|
54
55
|
ownership_for_mapper = []
|
55
56
|
codeowners_lines.each do |line, team_for_line|
|
56
57
|
next if team_for_line.nil?
|
@@ -81,18 +82,24 @@ module CodeOwnership
|
|
81
82
|
|
82
83
|
sig do
|
83
84
|
params(
|
84
|
-
files: T::Array[String],
|
85
85
|
autocorrect: T::Boolean,
|
86
|
-
stage_changes: T::Boolean
|
86
|
+
stage_changes: T::Boolean,
|
87
|
+
files: T.nilable(T::Array[String]),
|
87
88
|
).void
|
88
89
|
end
|
89
90
|
def validate!(
|
90
|
-
files: Private.tracked_files,
|
91
91
|
autocorrect: true,
|
92
|
-
stage_changes: true
|
92
|
+
stage_changes: true,
|
93
|
+
files: nil
|
93
94
|
)
|
94
95
|
Private.load_configuration!
|
95
|
-
|
96
|
+
|
97
|
+
tracked_file_subset = if files
|
98
|
+
files.select{|f| Private.file_tracked?(f)}
|
99
|
+
else
|
100
|
+
Private.tracked_files
|
101
|
+
end
|
102
|
+
|
96
103
|
Private.validate!(files: tracked_file_subset, autocorrect: autocorrect, stage_changes: stage_changes)
|
97
104
|
end
|
98
105
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: code_ownership
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.32.
|
4
|
+
version: 1.32.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gusto Engineers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-04-
|
11
|
+
date: 2023-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: code_teams
|