code_ownership 1.32.7 → 1.32.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76d700471a2e895926f184939c856b08ac75d5240a0d924a8e4474ac733a7204
4
- data.tar.gz: 3b62947009df98089b522614022643762c4c51d82af4f90f957606bcedfe60bc
3
+ metadata.gz: 71768f254f9fbb891679c002c16c20bd70a2125460e3c66995821cfe2784fe56
4
+ data.tar.gz: ce5698b1505d306026f807a142f648942ea06d3ab65576d05c623f6bdbdc1dfa
5
5
  SHA512:
6
- metadata.gz: df833467c3721e6b7d67aebd0732d59c46073589210251bd0859cb05979103741876264dd336df10401005360bca7a2937c65e861044b22abd698a2b8b65a20d
7
- data.tar.gz: 1b8f489aa18295cd8c534f3d5bcb3e2ab569553f26ec7e5a9445faff90da3aee3860bd3d7a02d6e91b80815d65b96680b0f54b085770ba85a54efa13aa0109b1
6
+ metadata.gz: 7b2e5da9f4e02b4db10c47be40b1b4bce33f7c44ad18797ff326a6e245f5dc57b2a8534c74fde74a1966970720a9bedf50d59858923e07f64d8e21d9f8411df1
7
+ data.tar.gz: b08f25b9008d86b19207931cb44c13adea814b998af8700b1fa445ad09e48e6549f86d6e4b75db41e5a783bf080ea5e25371d97db5307de8c5451bcd3d4fe3da
@@ -61,7 +61,7 @@ module CodeOwnership
61
61
  File.exist?(file)
62
62
  end
63
63
  else
64
- Private.tracked_files
64
+ nil
65
65
  end
66
66
 
67
67
  CodeOwnership.validate!(
@@ -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, T.nilable(::CodeTeams::Team)])
43
+ returns(T::Hash[String, ::CodeTeams::Team])
44
44
  end
45
- def map_files_to_owners(files)
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.returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
52
+ abstract.params(cache: GlobsToOwningTeamMap, files: T::Array[String]).returns(GlobsToOwningTeamMap)
50
53
  end
51
- def codeowners_lines_to_owners
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.codeowners_lines_to_owners
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
- GlobsByMapper
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::Array[MapperDescription]
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 files_by_mapper
57
- @files_by_mapper ||= T.let(@files_by_mapper, T.nilable(FilesByMapper))
58
- @files_by_mapper ||= begin
59
- files_by_mapper = {}
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
- files_by_mapper[file] ||= []
63
- files_by_mapper[file] << mapper_description
75
+ files_by_mappers[file] ||= Set.new([])
76
+ files_by_mappers.fetch(file) << mapper_description
64
77
  end
65
78
  end
66
79
 
67
- files_by_mapper
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, T.nilable(::CodeTeams::Team)])) # rubocop:disable Style/ClassVars
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, T.nilable(::CodeTeams::Team)])
37
+ returns(T::Hash[String, ::CodeTeams::Team])
37
38
  end
38
- def map_files_to_owners(files)
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
- 'Annotations at the top of file'
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 map_files_to_owners(files) # rubocop:disable Lint/UnusedMethodArgument
32
- ParseJsPackages.all.each_with_object({}) do |package, res|
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.returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
42
+ override.params(files: T::Array[String]).
43
+ returns(T::Hash[String, ::CodeTeams::Team])
53
44
  end
54
- def codeowners_lines_to_owners
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.returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
35
+ override.params(files: T::Array[String]).
36
+ returns(T::Hash[String, ::CodeTeams::Team])
53
37
  end
54
- def codeowners_lines_to_owners
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, T.nilable(::CodeTeams::Team)])) # rubocop:disable Style/ClassVars
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, T.nilable(::CodeTeams::Team)])) # 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
16
  @@codeowners_lines_to_owners = {} # rubocop:disable Style/ClassVars
17
17
 
18
18
  sig do
19
- override.
20
- params(files: T::Array[String]).
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.returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
95
+ override.params(cache: GlobsToOwningTeamMap, files: T::Array[String]).returns(GlobsToOwningTeamMap)
97
96
  end
98
- def codeowners_lines_to_owners
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, T.nilable(::CodeTeams::Team)])) # rubocop:disable Style/ClassVars
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, T.nilable(::CodeTeams::Team)])) # 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
15
  @@codeowners_lines_to_owners = {} # rubocop:disable Style/ClassVars
16
16
 
17
17
  sig do
18
- override.
19
- params(files: T::Array[String]).
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.returns(T::Hash[String, T.nilable(::CodeTeams::Team)])
38
+ override.params(files: T::Array[String]).
39
+ returns(T::Hash[String, ::CodeTeams::Team])
40
40
  end
41
- def codeowners_lines_to_owners
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
- files_by_mapper = Private.glob_cache.files_by_mapper
14
-
15
- files_not_mapped_at_all = files.select do |file|
16
- files_by_mapper.fetch(file, []).count == 0
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
- files_by_mapper = Private.glob_cache.files_by_mapper
14
-
15
- files_mapped_by_multiple_mappers = {}
16
- files.each do |file|
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 ||= Mapper.to_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
 
@@ -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.codeowners_lines_to_owners
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
- tracked_file_subset = Private.tracked_files & files
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.7
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 00:00:00.000000000 Z
11
+ date: 2023-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: code_teams