code_ownership 1.29.2 → 1.30.0

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: 4e796fdc12573836124effdc391e8443cb8ae82cdd5804d52ba417cf9fe21b57
4
- data.tar.gz: bd19a776739342474b75840d51b8c8f441efe9ad7ac26fca58e3062c43426581
3
+ metadata.gz: 9810b0bbef35bf354841da08d53391f864e213540ebb61fa5117b98df3b20ba9
4
+ data.tar.gz: 70dd498844898d361494a86a0c57da788d69cbd7c03eb7e3883c0d4520781089
5
5
  SHA512:
6
- metadata.gz: 72c79c8a88854736b955b006067b9e03578c3dab1463cdb9d7936340e83166d792ebe8b4075c9dd1642725de780681de2bc8434c494f6c5e3a9e4d379402880f
7
- data.tar.gz: bd9a5a8adc21b80d30f34b18f9825969c3f2dfbf8f4b73367154c36867bfc4aa627bc70217cc7ef4480baf7af2d92954af834ecef3f95b6838b28427c5763229
6
+ metadata.gz: d17b0680d21a907fb5581fa98adc16255984580ac2336a2170d69ab1d11b281d9d180e68e2866435ed5aab8dec1dab90f5849d8431311ddaa3d9014c6a961e9d
7
+ data.tar.gz: 3e1d9cf9e16234c199085f1de858590af922c7416a219a84fbc3c55c274a1eff59c7b6dba95e66b46f141328dcbd82005cbdaa55cb45b5d0dac32d2778156602
@@ -31,6 +31,58 @@ module CodeOwnership
31
31
  end
32
32
  end
33
33
 
34
+ class MappingContext < T::Struct
35
+ const :glob, String
36
+ const :team, CodeTeams::Team
37
+ end
38
+
39
+ class GlobOverlap < T::Struct
40
+ extend T::Sig
41
+
42
+ const :mapping_contexts, T::Array[MappingContext]
43
+
44
+ sig { returns(String) }
45
+ def description
46
+ # These are sorted only to prevent non-determinism in output between local and CI environments.
47
+ sorted_contexts = mapping_contexts.sort_by{|context| context.team.config_yml.to_s }
48
+ description_args = sorted_contexts.map do |context|
49
+ "`#{context.glob}` (from `#{context.team.config_yml}`)"
50
+ end
51
+
52
+ description_args.join(', ')
53
+ end
54
+ end
55
+
56
+ sig do
57
+ returns(T::Array[GlobOverlap])
58
+ end
59
+ def find_overlapping_globs
60
+ mapped_files = T.let({}, T::Hash[String, T::Array[MappingContext]])
61
+ CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars
62
+ TeamPlugins::Ownership.for(team).owned_globs.each do |glob|
63
+ Dir.glob(glob).each do |filename|
64
+ mapped_files[filename] ||= []
65
+ T.must(mapped_files[filename]) << MappingContext.new(glob: glob, team: team)
66
+ end
67
+ end
68
+ end
69
+
70
+ overlaps = T.let([], T::Array[GlobOverlap])
71
+ mapped_files.each do |filename, mapping_contexts|
72
+ if mapping_contexts.count > 1
73
+ overlaps << GlobOverlap.new(mapping_contexts: mapping_contexts)
74
+ end
75
+ end
76
+
77
+ deduplicated_overlaps = overlaps.uniq do |glob_overlap|
78
+ glob_overlap.mapping_contexts.map do |context|
79
+ [context.glob, context.team.name]
80
+ end
81
+ end
82
+
83
+ deduplicated_overlaps
84
+ end
85
+
34
86
  sig do
35
87
  override.params(file: String).
36
88
  returns(T.nilable(::CodeTeams::Team))
@@ -0,0 +1,30 @@
1
+ # typed: strict
2
+
3
+ module CodeOwnership
4
+ module Private
5
+ module Validations
6
+ class NoOverlappingGlobs
7
+ extend T::Sig
8
+ extend T::Helpers
9
+ include Interface
10
+
11
+ sig { override.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) }
12
+ def validation_errors(files:, autocorrect: true, stage_changes: true)
13
+ overlapping_globs = OwnershipMappers::TeamGlobs.new.find_overlapping_globs
14
+
15
+ errors = T.let([], T::Array[String])
16
+
17
+ if overlapping_globs.any?
18
+ errors << <<~MSG
19
+ `owned_globs` cannot overlap between teams. The following globs overlap:
20
+
21
+ #{overlapping_globs.map { |overlap| "- #{overlap.description}"}.join("\n")}
22
+ MSG
23
+ end
24
+
25
+ errors
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -10,6 +10,7 @@ require 'code_ownership/private/validations/interface'
10
10
  require 'code_ownership/private/validations/files_have_owners'
11
11
  require 'code_ownership/private/validations/github_codeowners_up_to_date'
12
12
  require 'code_ownership/private/validations/files_have_unique_owners'
13
+ require 'code_ownership/private/validations/no_overlapping_globs'
13
14
  require 'code_ownership/private/ownership_mappers/interface'
14
15
  require 'code_ownership/private/ownership_mappers/file_annotations'
15
16
  require 'code_ownership/private/ownership_mappers/team_globs'
@@ -39,6 +40,7 @@ module CodeOwnership
39
40
  Validations::FilesHaveOwners.new,
40
41
  Validations::FilesHaveUniqueOwners.new,
41
42
  Validations::GithubCodeownersUpToDate.new,
43
+ Validations::NoOverlappingGlobs.new,
42
44
  ]
43
45
 
44
46
  errors = validators.flat_map do |validator|
@@ -58,7 +58,6 @@ module CodeOwnership
58
58
  ownership_information += ownership_for_mapper
59
59
  end
60
60
 
61
-
62
61
  ownership_information << ""
63
62
  end
64
63
 
@@ -93,7 +92,25 @@ module CodeOwnership
93
92
  # first line that corresponds to a file with assigned ownership
94
93
  sig { params(backtrace: T.nilable(T::Array[String]), excluded_teams: T::Array[::CodeTeams::Team]).returns(T.nilable(::CodeTeams::Team)) }
95
94
  def for_backtrace(backtrace, excluded_teams: [])
96
- return unless backtrace
95
+ first_owned_file_for_backtrace(backtrace, excluded_teams: excluded_teams)&.first
96
+ end
97
+
98
+ # Given a backtrace from either `Exception#backtrace` or `caller`, find the
99
+ # first owned file in it, useful for figuring out which file is being blamed.
100
+ sig { params(backtrace: T.nilable(T::Array[String]), excluded_teams: T::Array[::CodeTeams::Team]).returns(T.nilable([::CodeTeams::Team, String])) }
101
+ def first_owned_file_for_backtrace(backtrace, excluded_teams: [])
102
+ backtrace_with_ownership(backtrace).each do |(team, file)|
103
+ if team && !excluded_teams.include?(team)
104
+ return [team, file]
105
+ end
106
+ end
107
+
108
+ nil
109
+ end
110
+
111
+ sig { params(backtrace: T.nilable(T::Array[String])).returns(T::Enumerable[[T.nilable(::CodeTeams::Team), String]]) }
112
+ def backtrace_with_ownership(backtrace)
113
+ return [] unless backtrace
97
114
 
98
115
  # The pattern for a backtrace hasn't changed in forever and is considered
99
116
  # stable: https://github.com/ruby/ruby/blob/trunk/vm_backtrace.c#L303-L317
@@ -110,18 +127,19 @@ module CodeOwnership
110
127
  `(?<function>.*)' # Matches "`block (3 levels) in create'"
111
128
  \z}x
112
129
 
113
- backtrace.each do |line|
130
+ backtrace.lazy.filter_map do |line|
114
131
  match = line.match(backtrace_line)
132
+ next unless match
115
133
 
116
- if match
117
- team = CodeOwnership.for_file(T.must(match[:file]))
118
- if team && !excluded_teams.include?(team)
119
- return team
120
- end
121
- end
134
+ file = T.must(match[:file])
135
+
136
+ [
137
+ CodeOwnership.for_file(file),
138
+ file,
139
+ ]
122
140
  end
123
- nil
124
141
  end
142
+ private_class_method(:backtrace_with_ownership)
125
143
 
126
144
  sig { params(klass: T.nilable(T.any(Class, Module))).returns(T.nilable(::CodeTeams::Team)) }
127
145
  def for_class(klass)
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.29.2
4
+ version: 1.30.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gusto Engineers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-28 00:00:00.000000000 Z
11
+ date: 2023-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: code_teams
@@ -148,6 +148,7 @@ files:
148
148
  - lib/code_ownership/private/validations/files_have_unique_owners.rb
149
149
  - lib/code_ownership/private/validations/github_codeowners_up_to_date.rb
150
150
  - lib/code_ownership/private/validations/interface.rb
151
+ - lib/code_ownership/private/validations/no_overlapping_globs.rb
151
152
  - sorbet/config
152
153
  - sorbet/rbi/gems/code_teams@1.0.0.rbi
153
154
  - sorbet/rbi/gems/packs@0.0.2.rbi