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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18e702e6635a7d3d264eaae41f61fb6f83e423e8021ca76e92a6972fe5444848
4
- data.tar.gz: 90f34b5a02b21a1f78ff781f7ef3e8220a45549e77c4b93b829381a57f37ba7c
3
+ metadata.gz: e0713332aa7dacd9ec6f097bba250555cb25208f594fd08bfba4a4ecf951d06b
4
+ data.tar.gz: aa039e732cfa8aad7a16439bc2608f51c043eef2f9b642513a7616b95b319c01
5
5
  SHA512:
6
- metadata.gz: f1deba0493c9ebbff4e39b81c0a282ffd634cef6db756f3f4be2b3b91f8c43e0d0f6d9362e03f2779ac5fe302101ae19fa8bf90022c55f24afbd9cd7c3b1fb13
7
- data.tar.gz: 0a1a3da4c7c758b0b64b7d9f817a92924c7a5e54fb4918655af2208a5ba8c65f12d41b37f0f568f443a817dd5893d16e351de745be641b3a56d3479e257add45
6
+ metadata.gz: 1c7b7a855b051aa6c856f39144e17484180fa6ff530ec7af3b7870ebf4cbf61b5b33c1a17d2402a319d784a7a17d7b226b64542c65a002707bb87419e1a660dd
7
+ data.tar.gz: b91f701455620e4fe27f2a99b20b58f6b03d78fa04d66548d3f85b1e4202cdf9c09ec2b53a454a3bca419d33b65150c5c24977e9eca93efc102c25e36df21303
@@ -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
@@ -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
- return @@map_files_to_owners if @@map_files_to_owners&.keys && @@map_files_to_owners.keys.count > 0
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
- !fileset.include?(file)
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
- 'Annotations at the top of file'
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
- return @@codeowners_lines_to_owners if @@codeowners_lines_to_owners&.keys && @@codeowners_lines_to_owners.keys.count > 0
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
- return @@codeowners_lines_to_owners if @@codeowners_lines_to_owners&.keys && @@codeowners_lines_to_owners.keys.count > 0
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
- 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.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: rake
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: pry
70
+ name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="