code_ownership 1.32.8 → 1.32.10
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/private/glob_cache.rb +47 -8
- data/lib/code_ownership/private/ownership_mappers/file_annotations.rb +4 -11
- data/lib/code_ownership/private/ownership_mappers/package_ownership.rb +0 -3
- data/lib/code_ownership/private/ownership_mappers/team_globs.rb +1 -6
- data/lib/code_ownership/private/ownership_mappers/team_yml_ownership.rb +1 -6
- 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 +18 -0
- data/lib/code_ownership.rb +11 -5
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e0713332aa7dacd9ec6f097bba250555cb25208f594fd08bfba4a4ecf951d06b
|
4
|
+
data.tar.gz: aa039e732cfa8aad7a16439bc2608f51c043eef2f9b642513a7616b95b319c01
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c7b7a855b051aa6c856f39144e17484180fa6ff530ec7af3b7870ebf4cbf61b5b33c1a17d2402a319d784a7a17d7b226b64542c65a002707bb87419e1a660dd
|
7
|
+
data.tar.gz: b91f701455620e4fe27f2a99b20b58f6b03d78fa04d66548d3f85b1e4202cdf9c09ec2b53a454a3bca419d33b65150c5c24977e9eca93efc102c25e36df21303
|
data/lib/code_ownership/cli.rb
CHANGED
@@ -18,7 +18,7 @@ module CodeOwnership
|
|
18
18
|
FilesByMapper = T.type_alias do
|
19
19
|
T::Hash[
|
20
20
|
String,
|
21
|
-
T::
|
21
|
+
T::Set[MapperDescription]
|
22
22
|
]
|
23
23
|
end
|
24
24
|
|
@@ -32,6 +32,20 @@ module CodeOwnership
|
|
32
32
|
@raw_cache_contents
|
33
33
|
end
|
34
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
|
+
|
35
49
|
sig { returns(CacheShape) }
|
36
50
|
def expanded_cache
|
37
51
|
@expanded_cache = T.let(@expanded_cache, T.nilable(CacheShape))
|
@@ -52,20 +66,45 @@ module CodeOwnership
|
|
52
66
|
end
|
53
67
|
|
54
68
|
sig { returns(FilesByMapper) }
|
55
|
-
def
|
56
|
-
@
|
57
|
-
@
|
58
|
-
|
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)
|
59
73
|
expanded_cache.each do |mapper_description, file_by_owner|
|
60
74
|
file_by_owner.each do |file, owner|
|
61
|
-
|
62
|
-
|
75
|
+
files_by_mappers[file] ||= Set.new([])
|
76
|
+
files_by_mappers.fetch(file) << mapper_description
|
63
77
|
end
|
64
78
|
end
|
65
79
|
|
66
|
-
|
80
|
+
files_by_mappers
|
67
81
|
end
|
68
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
|
69
108
|
end
|
70
109
|
end
|
71
110
|
end
|
@@ -18,9 +18,8 @@ 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, ::CodeTeams::Team])) # rubocop:disable Style/ClassVars
|
22
|
-
|
23
21
|
TEAM_PATTERN = T.let(/\A(?:#|\/\/) @team (?<team>.*)\Z/.freeze, Regexp)
|
22
|
+
DESCRIPTION = 'Annotations at the top of file'
|
24
23
|
|
25
24
|
sig do
|
26
25
|
override.params(file: String).
|
@@ -36,9 +35,7 @@ module CodeOwnership
|
|
36
35
|
returns(T::Hash[String, ::CodeTeams::Team])
|
37
36
|
end
|
38
37
|
def globs_to_owner(files)
|
39
|
-
|
40
|
-
|
41
|
-
@@map_files_to_owners = files.each_with_object({}) do |filename_relative_to_root, mapping| # rubocop:disable Style/ClassVars
|
38
|
+
files.each_with_object({}) do |filename_relative_to_root, mapping| # rubocop:disable Style/ClassVars
|
42
39
|
owner = file_annotation_based_owner(filename_relative_to_root)
|
43
40
|
next unless owner
|
44
41
|
|
@@ -51,11 +48,8 @@ module CodeOwnership
|
|
51
48
|
end
|
52
49
|
def update_cache(cache, files)
|
53
50
|
cache.merge!(globs_to_owner(files))
|
54
|
-
|
55
|
-
# TODO: Make `tracked_files` return a set
|
56
|
-
fileset = Set.new(Private.tracked_files)
|
57
51
|
invalid_files = cache.keys.select do |file|
|
58
|
-
!
|
52
|
+
!Private.file_tracked?(file)
|
59
53
|
end
|
60
54
|
invalid_files.each do |invalid_file|
|
61
55
|
cache.delete(invalid_file)
|
@@ -116,12 +110,11 @@ module CodeOwnership
|
|
116
110
|
|
117
111
|
sig { override.returns(String) }
|
118
112
|
def description
|
119
|
-
|
113
|
+
DESCRIPTION
|
120
114
|
end
|
121
115
|
|
122
116
|
sig { override.void }
|
123
117
|
def bust_caches!
|
124
|
-
@@map_files_to_owners = {} # rubocop:disable Style/ClassVars
|
125
118
|
end
|
126
119
|
end
|
127
120
|
end
|
@@ -9,8 +9,6 @@ module CodeOwnership
|
|
9
9
|
extend T::Sig
|
10
10
|
include Mapper
|
11
11
|
|
12
|
-
@@package_yml_cache = T.let({}, T::Hash[String, T.nilable(Packs::Pack)]) # rubocop:disable Style/ClassVars
|
13
|
-
|
14
12
|
sig do
|
15
13
|
override.params(file: String).
|
16
14
|
returns(T.nilable(::CodeTeams::Team))
|
@@ -69,7 +67,6 @@ module CodeOwnership
|
|
69
67
|
|
70
68
|
sig { override.void }
|
71
69
|
def bust_caches!
|
72
|
-
@@package_yml_cache = {} # rubocop:disable Style/ClassVars
|
73
70
|
end
|
74
71
|
end
|
75
72
|
end
|
@@ -12,8 +12,6 @@ module CodeOwnership
|
|
12
12
|
|
13
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, ::CodeTeams::Team])) # rubocop:disable Style/ClassVars
|
16
|
-
@@codeowners_lines_to_owners = {} # rubocop:disable Style/ClassVars
|
17
15
|
|
18
16
|
sig do
|
19
17
|
params(files: T::Array[String]).
|
@@ -103,9 +101,7 @@ module CodeOwnership
|
|
103
101
|
returns(T::Hash[String, ::CodeTeams::Team])
|
104
102
|
end
|
105
103
|
def globs_to_owner(files)
|
106
|
-
|
107
|
-
|
108
|
-
@@codeowners_lines_to_owners = CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars
|
104
|
+
CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars
|
109
105
|
TeamPlugins::Ownership.for(team).owned_globs.each do |owned_glob|
|
110
106
|
map[owned_glob] = team
|
111
107
|
end
|
@@ -114,7 +110,6 @@ module CodeOwnership
|
|
114
110
|
|
115
111
|
sig { override.void }
|
116
112
|
def bust_caches!
|
117
|
-
@@codeowners_lines_to_owners = {} # rubocop:disable Style/ClassVars
|
118
113
|
@@map_files_to_owners = {} # rubocop:disable Style/ClassVars
|
119
114
|
end
|
120
115
|
|
@@ -11,8 +11,6 @@ module CodeOwnership
|
|
11
11
|
|
12
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, ::CodeTeams::Team])) # rubocop:disable Style/ClassVars
|
15
|
-
@@codeowners_lines_to_owners = {} # rubocop:disable Style/ClassVars
|
16
14
|
|
17
15
|
sig do
|
18
16
|
params(files: T::Array[String]).
|
@@ -39,16 +37,13 @@ module CodeOwnership
|
|
39
37
|
returns(T::Hash[String, ::CodeTeams::Team])
|
40
38
|
end
|
41
39
|
def globs_to_owner(files)
|
42
|
-
|
43
|
-
|
44
|
-
@@codeowners_lines_to_owners = CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars
|
40
|
+
CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars
|
45
41
|
map[team.config_yml] = team
|
46
42
|
end
|
47
43
|
end
|
48
44
|
|
49
45
|
sig { override.void }
|
50
46
|
def bust_caches!
|
51
|
-
@@codeowners_lines_to_owners = {} # rubocop:disable Style/ClassVars
|
52
47
|
@@map_files_to_owners = {} # rubocop:disable Style/ClassVars
|
53
48
|
end
|
54
49
|
|
@@ -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
|
|
@@ -81,6 +81,24 @@ module CodeOwnership
|
|
81
81
|
@tracked_files ||= Dir.glob(configuration.owned_globs) - Dir.glob(configuration.unowned_globs)
|
82
82
|
end
|
83
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
|
+
|
84
102
|
sig { params(team_name: String, location_of_reference: String).returns(CodeTeams::Team) }
|
85
103
|
def self.find_team!(team_name, location_of_reference)
|
86
104
|
found_team = CodeTeams.find(team_name)
|
data/lib/code_ownership.rb
CHANGED
@@ -82,18 +82,24 @@ module CodeOwnership
|
|
82
82
|
|
83
83
|
sig do
|
84
84
|
params(
|
85
|
-
files: T::Array[String],
|
86
85
|
autocorrect: T::Boolean,
|
87
|
-
stage_changes: T::Boolean
|
86
|
+
stage_changes: T::Boolean,
|
87
|
+
files: T.nilable(T::Array[String]),
|
88
88
|
).void
|
89
89
|
end
|
90
90
|
def validate!(
|
91
|
-
files: Private.tracked_files,
|
92
91
|
autocorrect: true,
|
93
|
-
stage_changes: true
|
92
|
+
stage_changes: true,
|
93
|
+
files: nil
|
94
94
|
)
|
95
95
|
Private.load_configuration!
|
96
|
-
|
96
|
+
|
97
|
+
tracked_file_subset = if files
|
98
|
+
files.select{|f| Private.file_tracked?(f)}
|
99
|
+
else
|
100
|
+
Private.tracked_files
|
101
|
+
end
|
102
|
+
|
97
103
|
Private.validate!(files: tracked_file_subset, autocorrect: autocorrect, stage_changes: stage_changes)
|
98
104
|
end
|
99
105
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
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.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gusto Engineers
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: pry
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
@@ -67,7 +67,7 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: rake
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|