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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18e702e6635a7d3d264eaae41f61fb6f83e423e8021ca76e92a6972fe5444848
4
- data.tar.gz: 90f34b5a02b21a1f78ff781f7ef3e8220a45549e77c4b93b829381a57f37ba7c
3
+ metadata.gz: 71768f254f9fbb891679c002c16c20bd70a2125460e3c66995821cfe2784fe56
4
+ data.tar.gz: ce5698b1505d306026f807a142f648942ea06d3ab65576d05c623f6bdbdc1dfa
5
5
  SHA512:
6
- metadata.gz: f1deba0493c9ebbff4e39b81c0a282ffd634cef6db756f3f4be2b3b91f8c43e0d0f6d9362e03f2779ac5fe302101ae19fa8bf90022c55f24afbd9cd7c3b1fb13
7
- data.tar.gz: 0a1a3da4c7c758b0b64b7d9f817a92924c7a5e54fb4918655af2208a5ba8c65f12d41b37f0f568f443a817dd5893d16e351de745be641b3a56d3479e257add45
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!(
@@ -18,7 +18,7 @@ module CodeOwnership
18
18
  FilesByMapper = T.type_alias do
19
19
  T::Hash[
20
20
  String,
21
- T::Array[MapperDescription]
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 files_by_mapper
56
- @files_by_mapper ||= T.let(@files_by_mapper, T.nilable(FilesByMapper))
57
- @files_by_mapper ||= begin
58
- 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)
59
73
  expanded_cache.each do |mapper_description, file_by_owner|
60
74
  file_by_owner.each do |file, owner|
61
- files_by_mapper[file] ||= []
62
- files_by_mapper[file] << mapper_description
75
+ files_by_mappers[file] ||= Set.new([])
76
+ files_by_mappers.fetch(file) << mapper_description
63
77
  end
64
78
  end
65
79
 
66
- files_by_mapper
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
- !fileset.include?(file)
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
- 'Annotations at the top of file'
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
- 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
 
@@ -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)
@@ -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
- 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
+
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.8
4
+ version: 1.32.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gusto Engineers