code_ownership 1.32.8 → 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/private/glob_cache.rb +47 -8
- data/lib/code_ownership/private/ownership_mappers/file_annotations.rb +3 -5
- 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 +1 -1
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
@@ -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
|
@@ -21,6 +21,7 @@ module CodeOwnership
|
|
21
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).
|
@@ -51,11 +52,8 @@ module CodeOwnership
|
|
51
52
|
end
|
52
53
|
def update_cache(cache, files)
|
53
54
|
cache.merge!(globs_to_owner(files))
|
54
|
-
|
55
|
-
# TODO: Make `tracked_files` return a set
|
56
|
-
fileset = Set.new(Private.tracked_files)
|
57
55
|
invalid_files = cache.keys.select do |file|
|
58
|
-
!
|
56
|
+
!Private.file_tracked?(file)
|
59
57
|
end
|
60
58
|
invalid_files.each do |invalid_file|
|
61
59
|
cache.delete(invalid_file)
|
@@ -116,7 +114,7 @@ module CodeOwnership
|
|
116
114
|
|
117
115
|
sig { override.returns(String) }
|
118
116
|
def description
|
119
|
-
|
117
|
+
DESCRIPTION
|
120
118
|
end
|
121
119
|
|
122
120
|
sig { override.void }
|
@@ -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
|
|