code_ownership 1.32.8 → 1.32.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
- - ">="
|