code_ownership 1.36.1 → 1.36.3
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 +19 -22
- data/lib/code_ownership/configuration.rb +2 -2
- data/lib/code_ownership/mapper.rb +10 -14
- data/lib/code_ownership/private/codeowners_file.rb +18 -14
- data/lib/code_ownership/private/extension_loader.rb +1 -1
- data/lib/code_ownership/private/glob_cache.rb +12 -42
- data/lib/code_ownership/private/owner_assigner.rb +22 -0
- data/lib/code_ownership/private/ownership_mappers/directory_ownership.rb +6 -6
- data/lib/code_ownership/private/ownership_mappers/file_annotations.rb +18 -19
- data/lib/code_ownership/private/ownership_mappers/js_package_ownership.rb +6 -6
- data/lib/code_ownership/private/ownership_mappers/package_ownership.rb +5 -6
- data/lib/code_ownership/private/ownership_mappers/team_globs.rb +14 -16
- data/lib/code_ownership/private/ownership_mappers/team_yml_ownership.rb +9 -9
- data/lib/code_ownership/private/parse_js_packages.rb +5 -5
- data/lib/code_ownership/private/permit_pack_owner_top_level_key.rb +0 -1
- data/lib/code_ownership/private/validations/files_have_owners.rb +3 -3
- data/lib/code_ownership/private/validations/files_have_unique_owners.rb +1 -1
- data/lib/code_ownership/private/validations/github_codeowners_up_to_date.rb +24 -24
- data/lib/code_ownership/private.rb +7 -9
- data/lib/code_ownership/validator.rb +1 -2
- data/lib/code_ownership.rb +14 -12
- metadata +35 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb464ff5e39c0c9f2712a7af6498f30b3b42e3c456e7da52a5209d7f30467346
|
4
|
+
data.tar.gz: 5fd3f8974a041ff298fbdaceca0b3eccee027f4d9c94bdfaa344ae9b8ade72d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef51049577f74ab3faed01b4a37df119eaf577465fa10f342b79c0fabf7bc89b59ed993f8c1d8f687c53a3957745dfe579430e081473b9eb63f9b3cbe1f8eb19
|
7
|
+
data.tar.gz: ba03e839b81e0dcdfd1c07356b99dbf850b9abddaa83049c2c74e458012c4b81bdfaead12ed6c59fc744848261e078d715f6512df876d1d1fa238362910bfcb9
|
data/lib/code_ownership/cli.rb
CHANGED
@@ -14,7 +14,7 @@ module CodeOwnership
|
|
14
14
|
for_file(argv)
|
15
15
|
elsif command == 'for_team'
|
16
16
|
for_team(argv)
|
17
|
-
elsif [nil,
|
17
|
+
elsif [nil, 'help'].include?(command)
|
18
18
|
puts <<~USAGE
|
19
19
|
Usage: bin/codeownership <subcommand>
|
20
20
|
|
@@ -27,7 +27,6 @@ module CodeOwnership
|
|
27
27
|
else
|
28
28
|
puts "'#{command}' is not a code_ownership command. See `bin/codeownership help`."
|
29
29
|
end
|
30
|
-
|
31
30
|
end
|
32
31
|
|
33
32
|
def self.validate!(argv)
|
@@ -53,16 +52,16 @@ module CodeOwnership
|
|
53
52
|
exit
|
54
53
|
end
|
55
54
|
end
|
56
|
-
args = parser.order!(argv)
|
55
|
+
args = parser.order!(argv)
|
57
56
|
parser.parse!(args)
|
58
57
|
|
59
58
|
files = if options[:diff]
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
59
|
+
ENV.fetch('CODEOWNERS_GIT_STAGED_FILES') { `git diff --staged --name-only` }.split("\n").select do |file|
|
60
|
+
File.exist?(file)
|
61
|
+
end
|
62
|
+
else
|
63
|
+
nil
|
64
|
+
end
|
66
65
|
|
67
66
|
CodeOwnership.validate!(
|
68
67
|
files: files,
|
@@ -79,7 +78,7 @@ module CodeOwnership
|
|
79
78
|
# Long-term, we probably want to use something like `thor` so we don't have to implement logic
|
80
79
|
# like this. In the short-term, this is a simple way for us to use the built-in OptionParser
|
81
80
|
# while having an ergonomic CLI.
|
82
|
-
files = argv.
|
81
|
+
files = argv.reject { |arg| arg.start_with?('--') }
|
83
82
|
|
84
83
|
parser = OptionParser.new do |opts|
|
85
84
|
opts.banner = 'Usage: bin/codeownership for_file [options]'
|
@@ -93,22 +92,22 @@ module CodeOwnership
|
|
93
92
|
exit
|
94
93
|
end
|
95
94
|
end
|
96
|
-
args = parser.order!(argv)
|
95
|
+
args = parser.order!(argv)
|
97
96
|
parser.parse!(args)
|
98
97
|
|
99
98
|
if files.count != 1
|
100
|
-
raise
|
99
|
+
raise 'Please pass in one file. Use `bin/codeownership for_file --help` for more info'
|
101
100
|
end
|
102
|
-
|
101
|
+
|
103
102
|
team = CodeOwnership.for_file(files.first)
|
104
103
|
|
105
|
-
team_name = team&.name ||
|
106
|
-
team_yml = team&.config_yml ||
|
104
|
+
team_name = team&.name || 'Unowned'
|
105
|
+
team_yml = team&.config_yml || 'Unowned'
|
107
106
|
|
108
107
|
if options[:json]
|
109
108
|
json = {
|
110
109
|
team_name: team_name,
|
111
|
-
team_yml: team_yml
|
110
|
+
team_yml: team_yml
|
112
111
|
}
|
113
112
|
|
114
113
|
puts json.to_json
|
@@ -121,8 +120,6 @@ module CodeOwnership
|
|
121
120
|
end
|
122
121
|
|
123
122
|
def self.for_team(argv)
|
124
|
-
options = {}
|
125
|
-
|
126
123
|
parser = OptionParser.new do |opts|
|
127
124
|
opts.banner = 'Usage: bin/codeownership for_team \'Team Name\''
|
128
125
|
|
@@ -131,14 +128,14 @@ module CodeOwnership
|
|
131
128
|
exit
|
132
129
|
end
|
133
130
|
end
|
134
|
-
teams = argv.
|
135
|
-
args = parser.order!(argv)
|
131
|
+
teams = argv.reject { |arg| arg.start_with?('--') }
|
132
|
+
args = parser.order!(argv)
|
136
133
|
parser.parse!(args)
|
137
134
|
|
138
135
|
if teams.count != 1
|
139
|
-
raise
|
136
|
+
raise 'Please pass in one team. Use `bin/codeownership for_team --help` for more info'
|
140
137
|
end
|
141
|
-
|
138
|
+
|
142
139
|
puts CodeOwnership.for_team(teams.first)
|
143
140
|
end
|
144
141
|
|
@@ -17,8 +17,8 @@ module CodeOwnership
|
|
17
17
|
def self.fetch
|
18
18
|
config_hash = YAML.load_file('config/code_ownership.yml')
|
19
19
|
|
20
|
-
if config_hash.key?(
|
21
|
-
config_hash[
|
20
|
+
if config_hash.key?('require')
|
21
|
+
config_hash['require'].each do |require_directive|
|
22
22
|
Private::ExtensionLoader.load(require_directive)
|
23
23
|
end
|
24
24
|
end
|
@@ -29,21 +29,19 @@ module CodeOwnership
|
|
29
29
|
# This should be fast when run with ONE file
|
30
30
|
#
|
31
31
|
sig do
|
32
|
-
abstract.params(file: String)
|
33
|
-
returns(T.nilable(::CodeTeams::Team))
|
34
|
-
end
|
35
|
-
def map_file_to_owner(file)
|
32
|
+
abstract.params(file: String)
|
33
|
+
.returns(T.nilable(::CodeTeams::Team))
|
36
34
|
end
|
35
|
+
def map_file_to_owner(file); end
|
37
36
|
|
38
37
|
#
|
39
38
|
# This should be fast when run with MANY files
|
40
39
|
#
|
41
40
|
sig do
|
42
|
-
abstract.params(files: T::Array[String])
|
43
|
-
returns(T::Hash[String, ::CodeTeams::Team])
|
44
|
-
end
|
45
|
-
def globs_to_owner(files)
|
41
|
+
abstract.params(files: T::Array[String])
|
42
|
+
.returns(T::Hash[String, ::CodeTeams::Team])
|
46
43
|
end
|
44
|
+
def globs_to_owner(files); end
|
47
45
|
|
48
46
|
#
|
49
47
|
# This should be fast when run with MANY files
|
@@ -51,16 +49,13 @@ module CodeOwnership
|
|
51
49
|
sig do
|
52
50
|
abstract.params(cache: GlobsToOwningTeamMap, files: T::Array[String]).returns(GlobsToOwningTeamMap)
|
53
51
|
end
|
54
|
-
def update_cache(cache, files)
|
55
|
-
end
|
52
|
+
def update_cache(cache, files); end
|
56
53
|
|
57
54
|
sig { abstract.returns(String) }
|
58
|
-
def description
|
59
|
-
end
|
55
|
+
def description; end
|
60
56
|
|
61
57
|
sig { abstract.void }
|
62
|
-
def bust_caches
|
63
|
-
end
|
58
|
+
def bust_caches!; end
|
64
59
|
|
65
60
|
sig { returns(Private::GlobCache) }
|
66
61
|
def self.to_glob_cache
|
@@ -72,6 +67,7 @@ module CodeOwnership
|
|
72
67
|
|
73
68
|
mapped_files.each do |glob, owner|
|
74
69
|
next if owner.nil?
|
70
|
+
|
75
71
|
glob_to_owner_map_by_mapper_description.fetch(mapper.description)[glob] = owner
|
76
72
|
end
|
77
73
|
end
|
@@ -13,15 +13,15 @@ module CodeOwnership
|
|
13
13
|
|
14
14
|
sig { returns(T::Array[String]) }
|
15
15
|
def self.actual_contents_lines
|
16
|
-
if
|
17
|
-
[""]
|
18
|
-
else
|
16
|
+
if path.exist?
|
19
17
|
content = path.read
|
20
18
|
lines = path.read.split("\n")
|
21
19
|
if content.end_with?("\n")
|
22
|
-
lines <<
|
20
|
+
lines << ''
|
23
21
|
end
|
24
22
|
lines
|
23
|
+
else
|
24
|
+
['']
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -53,7 +53,7 @@ module CodeOwnership
|
|
53
53
|
|
54
54
|
cache.each do |mapper_description, ownership_map_cache|
|
55
55
|
ownership_entries = []
|
56
|
-
sorted_ownership_map_cache = ownership_map_cache.sort_by do |glob,
|
56
|
+
sorted_ownership_map_cache = ownership_map_cache.sort_by do |glob, _team|
|
57
57
|
glob
|
58
58
|
end
|
59
59
|
sorted_ownership_map_cache.to_h.each do |path, code_team|
|
@@ -64,22 +64,23 @@ module CodeOwnership
|
|
64
64
|
# 1) It allows the CODEOWNERS file to be used as a cache for validations
|
65
65
|
# 2) It allows users to specifically see what their team will not be notified about.
|
66
66
|
entry = if ignored_teams.include?(code_team.name)
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
67
|
+
"# /#{path} #{team_mapping}"
|
68
|
+
else
|
69
|
+
"/#{path} #{team_mapping}"
|
70
|
+
end
|
71
71
|
ownership_entries << entry
|
72
72
|
end
|
73
73
|
|
74
74
|
next if ownership_entries.none?
|
75
|
+
|
75
76
|
codeowners_file_lines += ['', "# #{mapper_description}", *ownership_entries.sort]
|
76
77
|
end
|
77
78
|
|
78
79
|
[
|
79
80
|
*header.split("\n"),
|
80
|
-
|
81
|
+
'', # For line between header and codeowners_file_lines
|
81
82
|
*codeowners_file_lines,
|
82
|
-
|
83
|
+
'' # For end-of-file newline
|
83
84
|
]
|
84
85
|
end
|
85
86
|
|
@@ -123,20 +124,22 @@ module CodeOwnership
|
|
123
124
|
mapper_descriptions = Set.new(Mapper.all.map(&:description))
|
124
125
|
|
125
126
|
path.readlines.each do |line|
|
126
|
-
line_with_no_comment = line.chomp.gsub(
|
127
|
+
line_with_no_comment = line.chomp.gsub('# ', '')
|
127
128
|
if mapper_descriptions.include?(line_with_no_comment)
|
128
129
|
current_mapper = line_with_no_comment
|
129
130
|
else
|
130
131
|
next if current_mapper.nil?
|
131
|
-
next if line.chomp ==
|
132
|
+
next if line.chomp == ''
|
133
|
+
|
132
134
|
# The codeowners file stores paths relative to the root of directory
|
133
135
|
# Since a `/` means root of the file system from the perspective of ruby,
|
134
136
|
# we remove that beginning slash so we can correctly glob the files out.
|
135
|
-
normalized_line = line.gsub(/^# /, '').gsub(
|
137
|
+
normalized_line = line.gsub(/^# /, '').gsub(%r{^/}, '')
|
136
138
|
split_line = normalized_line.split
|
137
139
|
# Most lines will be in the format: /path/to/file my-github-team
|
138
140
|
# This will skip over lines that are not of the correct form
|
139
141
|
next if split_line.count > 2
|
142
|
+
|
140
143
|
entry, github_team = split_line
|
141
144
|
code_team = github_team_to_code_team_map[T.must(github_team)]
|
142
145
|
# If a GitHub team is changed and a user runs `bin/codeownership validate`, we won't be able to identify
|
@@ -144,6 +147,7 @@ module CodeOwnership
|
|
144
147
|
# Therefore, if we can't determine the team, we just skip it.
|
145
148
|
# This affects how complete the cache is, but that will still be caught by `bin/codeownership validate`.
|
146
149
|
next if code_team.nil?
|
150
|
+
|
147
151
|
raw_cache_contents[current_mapper] ||= {}
|
148
152
|
raw_cache_contents.fetch(current_mapper)[T.must(entry)] = github_team_to_code_team_map.fetch(T.must(github_team))
|
149
153
|
end
|
@@ -12,7 +12,7 @@ module CodeOwnership
|
|
12
12
|
def load(require_directive)
|
13
13
|
# We want to transform the require directive to behave differently
|
14
14
|
# if it's a specific local file being required versus a gem
|
15
|
-
if require_directive.start_with?(
|
15
|
+
if require_directive.start_with?('.')
|
16
16
|
require File.join(Pathname.pwd, require_directive)
|
17
17
|
else
|
18
18
|
require require_directive
|
@@ -34,14 +34,15 @@ module CodeOwnership
|
|
34
34
|
|
35
35
|
sig { params(files: T::Array[String]).returns(FilesByMapper) }
|
36
36
|
def mapper_descriptions_that_map_files(files)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
files_by_mappers_via_file_fnmatch(files)
|
37
|
+
files_by_mappers = files.to_h { |f| [f, Set.new([])] }
|
38
|
+
|
39
|
+
files_by_mappers_via_expanded_cache.each do |file, mappers|
|
40
|
+
mappers.each do |mapper|
|
41
|
+
T.must(files_by_mappers[file]) << mapper if files_by_mappers[file]
|
42
|
+
end
|
44
43
|
end
|
44
|
+
|
45
|
+
files_by_mappers
|
45
46
|
end
|
46
47
|
|
47
48
|
private
|
@@ -53,25 +54,19 @@ module CodeOwnership
|
|
53
54
|
@expanded_cache ||= begin
|
54
55
|
expanded_cache = {}
|
55
56
|
@raw_cache_contents.each do |mapper_description, globs_by_owner|
|
56
|
-
expanded_cache[mapper_description] =
|
57
|
-
globs_by_owner.each do |glob, owner|
|
58
|
-
Dir.glob(glob).each do |file, owner|
|
59
|
-
expanded_cache[mapper_description][file] = owner
|
60
|
-
end
|
61
|
-
end
|
57
|
+
expanded_cache[mapper_description] = OwnerAssigner.assign_owners(globs_by_owner)
|
62
58
|
end
|
63
|
-
|
64
59
|
expanded_cache
|
65
60
|
end
|
66
61
|
end
|
67
62
|
|
68
63
|
sig { returns(FilesByMapper) }
|
69
64
|
def files_by_mappers_via_expanded_cache
|
70
|
-
@
|
71
|
-
@
|
65
|
+
@files_by_mappers_via_expanded_cache ||= T.let(@files_by_mappers_via_expanded_cache, T.nilable(FilesByMapper))
|
66
|
+
@files_by_mappers_via_expanded_cache ||= begin
|
72
67
|
files_by_mappers = T.let({}, FilesByMapper)
|
73
68
|
expanded_cache.each do |mapper_description, file_by_owner|
|
74
|
-
file_by_owner.
|
69
|
+
file_by_owner.each_key do |file|
|
75
70
|
files_by_mappers[file] ||= Set.new([])
|
76
71
|
files_by_mappers.fetch(file) << mapper_description
|
77
72
|
end
|
@@ -80,31 +75,6 @@ module CodeOwnership
|
|
80
75
|
files_by_mappers
|
81
76
|
end
|
82
77
|
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
|
108
78
|
end
|
109
79
|
end
|
110
80
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module CodeOwnership
|
5
|
+
module Private
|
6
|
+
class OwnerAssigner
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { params(globs_to_owning_team_map: GlobsToOwningTeamMap).returns(GlobsToOwningTeamMap) }
|
10
|
+
def self.assign_owners(globs_to_owning_team_map)
|
11
|
+
globs_to_owning_team_map.each_with_object({}) do |(glob, owner), mapping|
|
12
|
+
# addresses the case where a directory name includes regex characters
|
13
|
+
# such as `app/services/[test]/some_other_file.ts`
|
14
|
+
mapping[glob] = owner if File.exist?(glob)
|
15
|
+
Dir.glob(glob).each do |file|
|
16
|
+
mapping[file] ||= owner
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -14,8 +14,8 @@ module CodeOwnership
|
|
14
14
|
@@directory_cache = T.let({}, T::Hash[String, T.nilable(CodeTeams::Team)]) # rubocop:disable Style/ClassVars
|
15
15
|
|
16
16
|
sig do
|
17
|
-
override.params(file: String)
|
18
|
-
returns(T.nilable(::CodeTeams::Team))
|
17
|
+
override.params(file: String)
|
18
|
+
.returns(T.nilable(::CodeTeams::Team))
|
19
19
|
end
|
20
20
|
def map_file_to_owner(file)
|
21
21
|
map_file_to_relevant_owner(file)
|
@@ -37,8 +37,8 @@ module CodeOwnership
|
|
37
37
|
# subset of files, but rather we want code ownership for all files.
|
38
38
|
#
|
39
39
|
sig do
|
40
|
-
override.params(files: T::Array[String])
|
41
|
-
returns(T::Hash[String, ::CodeTeams::Team])
|
40
|
+
override.params(files: T::Array[String])
|
41
|
+
.returns(T::Hash[String, ::CodeTeams::Team])
|
42
42
|
end
|
43
43
|
def globs_to_owner(files)
|
44
44
|
# The T.unsafe is because the upstream RBI is wrong for Pathname.glob
|
@@ -97,7 +97,7 @@ module CodeOwnership
|
|
97
97
|
(path_components.length - 1).downto(0).each do |i|
|
98
98
|
team = get_team_from_codeowners_file_within_directory(
|
99
99
|
Pathname.new(File.join(*T.unsafe(path_components[0...i])))
|
100
|
-
|
100
|
+
)
|
101
101
|
return team unless team.nil?
|
102
102
|
end
|
103
103
|
|
@@ -121,7 +121,7 @@ module CodeOwnership
|
|
121
121
|
@@directory_cache[potential_codeowners_file_name] = nil
|
122
122
|
end
|
123
123
|
|
124
|
-
|
124
|
+
team
|
125
125
|
end
|
126
126
|
end
|
127
127
|
end
|
@@ -18,24 +18,24 @@ module CodeOwnership
|
|
18
18
|
extend T::Sig
|
19
19
|
include Mapper
|
20
20
|
|
21
|
-
TEAM_PATTERN = T.let(
|
21
|
+
TEAM_PATTERN = T.let(%r{\A(?:#|//) @team (?<team>.*)\Z}.freeze, Regexp)
|
22
22
|
DESCRIPTION = 'Annotations at the top of file'
|
23
23
|
|
24
24
|
sig do
|
25
|
-
override.params(file: String)
|
26
|
-
returns(T.nilable(::CodeTeams::Team))
|
25
|
+
override.params(file: String)
|
26
|
+
.returns(T.nilable(::CodeTeams::Team))
|
27
27
|
end
|
28
28
|
def map_file_to_owner(file)
|
29
29
|
file_annotation_based_owner(file)
|
30
30
|
end
|
31
31
|
|
32
32
|
sig do
|
33
|
-
override
|
34
|
-
params(files: T::Array[String])
|
35
|
-
returns(T::Hash[String, ::CodeTeams::Team])
|
33
|
+
override
|
34
|
+
.params(files: T::Array[String])
|
35
|
+
.returns(T::Hash[String, ::CodeTeams::Team])
|
36
36
|
end
|
37
37
|
def globs_to_owner(files)
|
38
|
-
files.each_with_object({}) do |filename_relative_to_root, mapping|
|
38
|
+
files.each_with_object({}) do |filename_relative_to_root, mapping|
|
39
39
|
owner = file_annotation_based_owner(filename_relative_to_root)
|
40
40
|
next unless owner
|
41
41
|
|
@@ -56,10 +56,10 @@ module CodeOwnership
|
|
56
56
|
invalid_files = cache.keys.select do |file|
|
57
57
|
# If a file is not tracked, it should be removed from the cache
|
58
58
|
!Private.file_tracked?(file) ||
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
59
|
+
# If a file no longer has a file annotation (i.e. `globs_to_owner` doesn't map it)
|
60
|
+
# it should be removed from the cache
|
61
|
+
# We make sure to only apply this to the input files since otherwise `updated_cache_for_files.key?(file)` would always return `false` when files == []
|
62
|
+
(fileset.include?(file) && !updated_cache_for_files.key?(file))
|
63
63
|
end
|
64
64
|
|
65
65
|
invalid_files.each do |invalid_file|
|
@@ -83,14 +83,14 @@ module CodeOwnership
|
|
83
83
|
# and if the annotation isn't in the first two lines we assume it
|
84
84
|
# doesn't exist.
|
85
85
|
|
86
|
-
|
86
|
+
line1 = File.foreach(filename).first
|
87
87
|
|
88
|
-
return if !
|
88
|
+
return if !line1
|
89
89
|
|
90
90
|
begin
|
91
|
-
team =
|
92
|
-
rescue ArgumentError =>
|
93
|
-
if
|
91
|
+
team = line1[TEAM_PATTERN, :team]
|
92
|
+
rescue ArgumentError => e
|
93
|
+
if e.message.include?('invalid byte sequence')
|
94
94
|
team = nil
|
95
95
|
else
|
96
96
|
raise
|
@@ -110,7 +110,7 @@ module CodeOwnership
|
|
110
110
|
if file_annotation_based_owner(filename)
|
111
111
|
filepath = Pathname.new(filename)
|
112
112
|
lines = filepath.read.split("\n")
|
113
|
-
new_lines = lines.
|
113
|
+
new_lines = lines.reject { |line| line[TEAM_PATTERN] }
|
114
114
|
# We explicitly add a final new line since splitting by new line when reading the file lines
|
115
115
|
# ignores new lines at the ends of files
|
116
116
|
# We also remove leading new lines, since there is after a new line after an annotation
|
@@ -125,8 +125,7 @@ module CodeOwnership
|
|
125
125
|
end
|
126
126
|
|
127
127
|
sig { override.void }
|
128
|
-
def bust_caches
|
129
|
-
end
|
128
|
+
def bust_caches!; end
|
130
129
|
end
|
131
130
|
end
|
132
131
|
end
|
@@ -12,8 +12,8 @@ module CodeOwnership
|
|
12
12
|
@@package_json_cache = T.let({}, T::Hash[String, T.nilable(ParseJsPackages::Package)]) # rubocop:disable Style/ClassVars
|
13
13
|
|
14
14
|
sig do
|
15
|
-
override.params(file: String)
|
16
|
-
returns(T.nilable(::CodeTeams::Team))
|
15
|
+
override.params(file: String)
|
16
|
+
.returns(T.nilable(::CodeTeams::Team))
|
17
17
|
end
|
18
18
|
def map_file_to_owner(file)
|
19
19
|
package = map_file_to_relevant_package(file)
|
@@ -39,8 +39,8 @@ module CodeOwnership
|
|
39
39
|
# subset of files, but rather we want code ownership for all files.
|
40
40
|
#
|
41
41
|
sig do
|
42
|
-
override.params(files: T::Array[String])
|
43
|
-
returns(T::Hash[String, ::CodeTeams::Team])
|
42
|
+
override.params(files: T::Array[String])
|
43
|
+
.returns(T::Hash[String, ::CodeTeams::Team])
|
44
44
|
end
|
45
45
|
def globs_to_owner(files)
|
46
46
|
ParseJsPackages.all.each_with_object({}) do |package, res|
|
@@ -85,8 +85,8 @@ module CodeOwnership
|
|
85
85
|
|
86
86
|
(path_components.length - 1).downto(0).each do |i|
|
87
87
|
potential_relative_path_name = T.must(path_components[0...i]).reduce(Pathname.new('')) { |built_path, path| built_path.join(path) }
|
88
|
-
potential_package_json_path = potential_relative_path_name
|
89
|
-
join(ParseJsPackages::PACKAGE_JSON_NAME)
|
88
|
+
potential_package_json_path = potential_relative_path_name
|
89
|
+
.join(ParseJsPackages::PACKAGE_JSON_NAME)
|
90
90
|
|
91
91
|
potential_package_json_string = potential_package_json_path.to_s
|
92
92
|
|
@@ -10,8 +10,8 @@ module CodeOwnership
|
|
10
10
|
include Mapper
|
11
11
|
|
12
12
|
sig do
|
13
|
-
override.params(file: String)
|
14
|
-
returns(T.nilable(::CodeTeams::Team))
|
13
|
+
override.params(file: String)
|
14
|
+
.returns(T.nilable(::CodeTeams::Team))
|
15
15
|
end
|
16
16
|
def map_file_to_owner(file)
|
17
17
|
package = Packs.for_file(file)
|
@@ -30,8 +30,8 @@ module CodeOwnership
|
|
30
30
|
# subset of files, but rather we want code ownership for all files.
|
31
31
|
#
|
32
32
|
sig do
|
33
|
-
override.params(files: T::Array[String])
|
34
|
-
returns(T::Hash[String, ::CodeTeams::Team])
|
33
|
+
override.params(files: T::Array[String])
|
34
|
+
.returns(T::Hash[String, ::CodeTeams::Team])
|
35
35
|
end
|
36
36
|
def globs_to_owner(files)
|
37
37
|
Packs.all.each_with_object({}) do |package, res|
|
@@ -66,8 +66,7 @@ module CodeOwnership
|
|
66
66
|
end
|
67
67
|
|
68
68
|
sig { override.void }
|
69
|
-
def bust_caches
|
70
|
-
end
|
69
|
+
def bust_caches!; end
|
71
70
|
end
|
72
71
|
end
|
73
72
|
end
|
@@ -14,11 +14,11 @@ module CodeOwnership
|
|
14
14
|
@@map_files_to_owners = {} # rubocop:disable Style/ClassVars
|
15
15
|
|
16
16
|
sig do
|
17
|
-
params(files: T::Array[String])
|
18
|
-
returns(T::Hash[String, ::CodeTeams::Team])
|
17
|
+
params(files: T::Array[String])
|
18
|
+
.returns(T::Hash[String, ::CodeTeams::Team])
|
19
19
|
end
|
20
|
-
def map_files_to_owners(files)
|
21
|
-
return @@map_files_to_owners if @@map_files_to_owners&.keys && @@map_files_to_owners.keys.count
|
20
|
+
def map_files_to_owners(files)
|
21
|
+
return @@map_files_to_owners if @@map_files_to_owners&.keys && @@map_files_to_owners.keys.count.positive?
|
22
22
|
|
23
23
|
@@map_files_to_owners = CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars
|
24
24
|
TeamPlugins::Ownership.for(team).owned_globs.each do |glob|
|
@@ -42,7 +42,7 @@ module CodeOwnership
|
|
42
42
|
sig { returns(String) }
|
43
43
|
def description
|
44
44
|
# These are sorted only to prevent non-determinism in output between local and CI environments.
|
45
|
-
sorted_contexts = mapping_contexts.sort_by{|context| context.team.config_yml.to_s }
|
45
|
+
sorted_contexts = mapping_contexts.sort_by { |context| context.team.config_yml.to_s }
|
46
46
|
description_args = sorted_contexts.map do |context|
|
47
47
|
"`#{context.glob}` (from `#{context.team.config_yml}`)"
|
48
48
|
end
|
@@ -56,7 +56,7 @@ module CodeOwnership
|
|
56
56
|
end
|
57
57
|
def find_overlapping_globs
|
58
58
|
mapped_files = T.let({}, T::Hash[String, T::Array[MappingContext]])
|
59
|
-
CodeTeams.all.each_with_object({}) do |team,
|
59
|
+
CodeTeams.all.each_with_object({}) do |team, _map|
|
60
60
|
TeamPlugins::Ownership.for(team).owned_globs.each do |glob|
|
61
61
|
Dir.glob(glob).each do |filename|
|
62
62
|
mapped_files[filename] ||= []
|
@@ -66,24 +66,22 @@ module CodeOwnership
|
|
66
66
|
end
|
67
67
|
|
68
68
|
overlaps = T.let([], T::Array[GlobOverlap])
|
69
|
-
mapped_files.
|
69
|
+
mapped_files.each_value do |mapping_contexts|
|
70
70
|
if mapping_contexts.count > 1
|
71
71
|
overlaps << GlobOverlap.new(mapping_contexts: mapping_contexts)
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
-
|
75
|
+
overlaps.uniq do |glob_overlap|
|
76
76
|
glob_overlap.mapping_contexts.map do |context|
|
77
77
|
[context.glob, context.team.name]
|
78
78
|
end
|
79
79
|
end
|
80
|
-
|
81
|
-
deduplicated_overlaps
|
82
80
|
end
|
83
81
|
|
84
82
|
sig do
|
85
|
-
override.params(file: String)
|
86
|
-
returns(T.nilable(::CodeTeams::Team))
|
83
|
+
override.params(file: String)
|
84
|
+
.returns(T.nilable(::CodeTeams::Team))
|
87
85
|
end
|
88
86
|
def map_file_to_owner(file)
|
89
87
|
map_files_to_owners([file])[file]
|
@@ -97,11 +95,11 @@ module CodeOwnership
|
|
97
95
|
end
|
98
96
|
|
99
97
|
sig do
|
100
|
-
override.params(files: T::Array[String])
|
101
|
-
returns(T::Hash[String, ::CodeTeams::Team])
|
98
|
+
override.params(files: T::Array[String])
|
99
|
+
.returns(T::Hash[String, ::CodeTeams::Team])
|
102
100
|
end
|
103
101
|
def globs_to_owner(files)
|
104
|
-
CodeTeams.all.each_with_object({}) do |team, map|
|
102
|
+
CodeTeams.all.each_with_object({}) do |team, map|
|
105
103
|
TeamPlugins::Ownership.for(team).owned_globs.each do |owned_glob|
|
106
104
|
map[owned_glob] = team
|
107
105
|
end
|
@@ -128,7 +126,7 @@ module CodeOwnership
|
|
128
126
|
errors << <<~MSG
|
129
127
|
`owned_globs` cannot overlap between teams. The following globs overlap:
|
130
128
|
|
131
|
-
#{overlapping_globs.map { |overlap| "- #{overlap.description}"}.join("\n")}
|
129
|
+
#{overlapping_globs.map { |overlap| "- #{overlap.description}" }.join("\n")}
|
132
130
|
MSG
|
133
131
|
end
|
134
132
|
|
@@ -13,11 +13,11 @@ module CodeOwnership
|
|
13
13
|
@@map_files_to_owners = {} # rubocop:disable Style/ClassVars
|
14
14
|
|
15
15
|
sig do
|
16
|
-
params(files: T::Array[String])
|
17
|
-
returns(T::Hash[String, ::CodeTeams::Team])
|
16
|
+
params(files: T::Array[String])
|
17
|
+
.returns(T::Hash[String, ::CodeTeams::Team])
|
18
18
|
end
|
19
|
-
def map_files_to_owners(files)
|
20
|
-
return @@map_files_to_owners if @@map_files_to_owners&.keys && @@map_files_to_owners.keys.count
|
19
|
+
def map_files_to_owners(files)
|
20
|
+
return @@map_files_to_owners if @@map_files_to_owners&.keys && @@map_files_to_owners.keys.count.positive?
|
21
21
|
|
22
22
|
@@map_files_to_owners = CodeTeams.all.each_with_object({}) do |team, map| # rubocop:disable Style/ClassVars
|
23
23
|
map[team.config_yml] = team
|
@@ -25,19 +25,19 @@ module CodeOwnership
|
|
25
25
|
end
|
26
26
|
|
27
27
|
sig do
|
28
|
-
override.params(file: String)
|
29
|
-
returns(T.nilable(::CodeTeams::Team))
|
28
|
+
override.params(file: String)
|
29
|
+
.returns(T.nilable(::CodeTeams::Team))
|
30
30
|
end
|
31
31
|
def map_file_to_owner(file)
|
32
32
|
map_files_to_owners([file])[file]
|
33
33
|
end
|
34
34
|
|
35
35
|
sig do
|
36
|
-
override.params(files: T::Array[String])
|
37
|
-
returns(T::Hash[String, ::CodeTeams::Team])
|
36
|
+
override.params(files: T::Array[String])
|
37
|
+
.returns(T::Hash[String, ::CodeTeams::Team])
|
38
38
|
end
|
39
39
|
def globs_to_owner(files)
|
40
|
-
CodeTeams.all.each_with_object({}) do |team, map|
|
40
|
+
CodeTeams.all.each_with_object({}) do |team, map|
|
41
41
|
map[team.config_yml] = team
|
42
42
|
end
|
43
43
|
end
|
@@ -23,10 +23,10 @@ module CodeOwnership
|
|
23
23
|
package_loaded_json = JSON.parse(pathname.read)
|
24
24
|
|
25
25
|
package_name = if pathname.dirname == Pathname.new('.')
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
ROOT_PACKAGE_NAME
|
27
|
+
else
|
28
|
+
pathname.dirname.cleanpath.to_s
|
29
|
+
end
|
30
30
|
|
31
31
|
new(
|
32
32
|
name: package_name,
|
@@ -41,7 +41,7 @@ module CodeOwnership
|
|
41
41
|
Please either make the JSON in that file valid or specify `js_package_paths` in config/code_ownership.yml.
|
42
42
|
MESSAGE
|
43
43
|
|
44
|
-
raise InvalidCodeOwnershipConfigurationError
|
44
|
+
raise InvalidCodeOwnershipConfigurationError, error_message
|
45
45
|
end
|
46
46
|
|
47
47
|
sig { returns(Pathname) }
|
@@ -12,8 +12,8 @@ module CodeOwnership
|
|
12
12
|
def validation_errors(files:, autocorrect: true, stage_changes: true)
|
13
13
|
cache = Private.glob_cache
|
14
14
|
file_mappings = cache.mapper_descriptions_that_map_files(files)
|
15
|
-
files_not_mapped_at_all = file_mappings.select do |
|
16
|
-
mapper_descriptions.count
|
15
|
+
files_not_mapped_at_all = file_mappings.select do |_file, mapper_descriptions|
|
16
|
+
mapper_descriptions.count.zero?
|
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,
|
25
|
+
#{files_not_mapped_at_all.map { |file, _mappers| "- #{file}" }.join("\n")}
|
26
26
|
MSG
|
27
27
|
end
|
28
28
|
|
@@ -12,7 +12,7 @@ module CodeOwnership
|
|
12
12
|
def validation_errors(files:, autocorrect: true, stage_changes: true)
|
13
13
|
cache = Private.glob_cache
|
14
14
|
file_mappings = cache.mapper_descriptions_that_map_files(files)
|
15
|
-
files_mapped_by_multiple_mappers = file_mappings.select do |
|
15
|
+
files_mapped_by_multiple_mappers = file_mappings.select do |_file, mapper_descriptions|
|
16
16
|
mapper_descriptions.count > 1
|
17
17
|
end
|
18
18
|
|
@@ -25,7 +25,7 @@ module CodeOwnership
|
|
25
25
|
`git add #{CodeownersFile.path}`
|
26
26
|
end
|
27
27
|
# If there is no current file or its empty, display a shorter message.
|
28
|
-
elsif actual_content_lines == [
|
28
|
+
elsif actual_content_lines == ['']
|
29
29
|
errors << <<~CODEOWNERS_ERROR
|
30
30
|
CODEOWNERS out of date. Run `bin/codeownership validate` to update the CODEOWNERS file
|
31
31
|
CODEOWNERS_ERROR
|
@@ -34,33 +34,33 @@ module CodeOwnership
|
|
34
34
|
extra_lines = actual_content_lines - expected_content_lines
|
35
35
|
|
36
36
|
missing_lines_text = if missing_lines.any?
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
37
|
+
<<~COMMENT
|
38
|
+
CODEOWNERS should contain the following lines, but does not:
|
39
|
+
#{missing_lines.map { |line| "- \"#{line}\"" }.join("\n")}
|
40
|
+
COMMENT
|
41
|
+
end
|
42
42
|
|
43
43
|
extra_lines_text = if extra_lines.any?
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
44
|
+
<<~COMMENT
|
45
|
+
CODEOWNERS should not contain the following lines, but it does:
|
46
|
+
#{extra_lines.map { |line| "- \"#{line}\"" }.join("\n")}
|
47
|
+
COMMENT
|
48
|
+
end
|
49
49
|
|
50
50
|
diff_text = if missing_lines_text && extra_lines_text
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
51
|
+
"#{missing_lines_text}\n#{extra_lines_text}".chomp
|
52
|
+
elsif missing_lines_text
|
53
|
+
missing_lines_text
|
54
|
+
elsif extra_lines_text
|
55
|
+
extra_lines_text
|
56
|
+
else
|
57
|
+
<<~TEXT
|
58
|
+
There may be extra lines, or lines are out of order.
|
59
|
+
You can try to regenerate the CODEOWNERS file from scratch:
|
60
|
+
1) `rm .github/CODEOWNERS`
|
61
|
+
2) `bin/codeownership validate`
|
62
|
+
TEXT
|
63
|
+
end
|
64
64
|
|
65
65
|
errors << <<~CODEOWNERS_ERROR
|
66
66
|
CODEOWNERS out of date. Run `bin/codeownership validate` to update the CODEOWNERS file
|
@@ -8,6 +8,7 @@ require 'code_ownership/private/team_plugins/github'
|
|
8
8
|
require 'code_ownership/private/codeowners_file'
|
9
9
|
require 'code_ownership/private/parse_js_packages'
|
10
10
|
require 'code_ownership/private/glob_cache'
|
11
|
+
require 'code_ownership/private/owner_assigner'
|
11
12
|
require 'code_ownership/private/validations/files_have_owners'
|
12
13
|
require 'code_ownership/private/validations/github_codeowners_up_to_date'
|
13
14
|
require 'code_ownership/private/validations/files_have_unique_owners'
|
@@ -91,14 +92,13 @@ module CodeOwnership
|
|
91
92
|
# However, globbing out can take 5 or more seconds on a large repository, dramatically slowing down
|
92
93
|
# invocations to `bin/codeownership validate --diff`.
|
93
94
|
# Using `File.fnmatch?` is a lot faster!
|
94
|
-
in_owned_globs = configuration.owned_globs.
|
95
|
+
in_owned_globs = configuration.owned_globs.any? do |owned_glob|
|
95
96
|
File.fnmatch?(owned_glob, file, File::FNM_PATHNAME | File::FNM_EXTGLOB)
|
96
97
|
end
|
97
98
|
|
98
99
|
in_unowned_globs = configuration.unowned_globs.any? do |unowned_glob|
|
99
100
|
File.fnmatch?(unowned_glob, file, File::FNM_PATHNAME | File::FNM_EXTGLOB)
|
100
101
|
end
|
101
|
-
|
102
102
|
in_owned_globs && !in_unowned_globs && File.exist?(file)
|
103
103
|
end
|
104
104
|
|
@@ -115,13 +115,11 @@ module CodeOwnership
|
|
115
115
|
sig { returns(GlobCache) }
|
116
116
|
def self.glob_cache
|
117
117
|
@glob_cache ||= T.let(@glob_cache, T.nilable(GlobCache))
|
118
|
-
@glob_cache ||=
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
end
|
124
|
-
end
|
118
|
+
@glob_cache ||= if CodeownersFile.use_codeowners_cache?
|
119
|
+
CodeownersFile.to_glob_cache
|
120
|
+
else
|
121
|
+
Mapper.to_glob_cache
|
122
|
+
end
|
125
123
|
end
|
126
124
|
end
|
127
125
|
|
@@ -8,8 +8,7 @@ module CodeOwnership
|
|
8
8
|
interface!
|
9
9
|
|
10
10
|
sig { abstract.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) }
|
11
|
-
def validation_errors(files:, autocorrect: true, stage_changes: true)
|
12
|
-
end
|
11
|
+
def validation_errors(files:, autocorrect: true, stage_changes: true); end
|
13
12
|
|
14
13
|
class << self
|
15
14
|
extend T::Sig
|
data/lib/code_ownership.rb
CHANGED
@@ -18,7 +18,8 @@ if defined?(Packwerk)
|
|
18
18
|
end
|
19
19
|
|
20
20
|
module CodeOwnership
|
21
|
-
|
21
|
+
module_function
|
22
|
+
|
22
23
|
extend T::Sig
|
23
24
|
extend T::Helpers
|
24
25
|
|
@@ -39,7 +40,7 @@ module CodeOwnership
|
|
39
40
|
|
40
41
|
Mapper.all.each do |mapper|
|
41
42
|
owner = mapper.map_file_to_owner(file)
|
42
|
-
break if owner
|
43
|
+
break if owner # TODO: what if there are multiple owners? Should we respond with an error instead of the first match?
|
43
44
|
end
|
44
45
|
|
45
46
|
@for_file[file] = owner
|
@@ -57,6 +58,7 @@ module CodeOwnership
|
|
57
58
|
ownership_for_mapper = []
|
58
59
|
glob_to_owning_team_map.each do |glob, owning_team|
|
59
60
|
next if owning_team != team
|
61
|
+
|
60
62
|
ownership_for_mapper << "- #{glob}"
|
61
63
|
end
|
62
64
|
|
@@ -66,7 +68,7 @@ module CodeOwnership
|
|
66
68
|
ownership_information += ownership_for_mapper.sort
|
67
69
|
end
|
68
70
|
|
69
|
-
ownership_information <<
|
71
|
+
ownership_information << ''
|
70
72
|
end
|
71
73
|
|
72
74
|
ownership_information.join("\n")
|
@@ -84,7 +86,7 @@ module CodeOwnership
|
|
84
86
|
params(
|
85
87
|
autocorrect: T::Boolean,
|
86
88
|
stage_changes: T::Boolean,
|
87
|
-
files: T.nilable(T::Array[String])
|
89
|
+
files: T.nilable(T::Array[String])
|
88
90
|
).void
|
89
91
|
end
|
90
92
|
def validate!(
|
@@ -95,10 +97,10 @@ module CodeOwnership
|
|
95
97
|
Private.load_configuration!
|
96
98
|
|
97
99
|
tracked_file_subset = if files
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
100
|
+
files.select { |f| Private.file_tracked?(f) }
|
101
|
+
else
|
102
|
+
Private.tracked_files
|
103
|
+
end
|
102
104
|
|
103
105
|
Private.validate!(files: tracked_file_subset, autocorrect: autocorrect, stage_changes: stage_changes)
|
104
106
|
end
|
@@ -150,7 +152,7 @@ module CodeOwnership
|
|
150
152
|
|
151
153
|
[
|
152
154
|
CodeOwnership.for_file(file),
|
153
|
-
file
|
155
|
+
file
|
154
156
|
]
|
155
157
|
end
|
156
158
|
end
|
@@ -161,15 +163,15 @@ module CodeOwnership
|
|
161
163
|
@memoized_values ||= T.let(@memoized_values, T.nilable(T::Hash[String, T.nilable(::CodeTeams::Team)]))
|
162
164
|
@memoized_values ||= {}
|
163
165
|
# We use key because the memoized value could be `nil`
|
164
|
-
if
|
166
|
+
if @memoized_values.key?(klass.to_s)
|
167
|
+
@memoized_values[klass.to_s]
|
168
|
+
else
|
165
169
|
path = Private.path_from_klass(klass)
|
166
170
|
return nil if path.nil?
|
167
171
|
|
168
172
|
value_to_memoize = for_file(path)
|
169
173
|
@memoized_values[klass.to_s] = value_to_memoize
|
170
174
|
value_to_memoize
|
171
|
-
else
|
172
|
-
@memoized_values[klass.to_s]
|
173
175
|
end
|
174
176
|
end
|
175
177
|
|
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.36.
|
4
|
+
version: 1.36.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gusto Engineers
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-07-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: code_teams
|
@@ -44,16 +44,16 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 0.5.
|
47
|
+
version: 0.5.11249
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0.5.
|
54
|
+
version: 0.5.11249
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: debug
|
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:
|
70
|
+
name: packwerk
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
@@ -81,21 +81,21 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: railties
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- - "
|
87
|
+
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
89
|
+
version: '0'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- - "
|
94
|
+
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
96
|
+
version: '0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
98
|
+
name: rake
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - ">="
|
@@ -109,7 +109,21 @@ dependencies:
|
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
112
|
+
name: rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '3.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '3.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rubocop
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
114
128
|
requirements:
|
115
129
|
- - ">="
|
@@ -123,7 +137,7 @@ dependencies:
|
|
123
137
|
- !ruby/object:Gem::Version
|
124
138
|
version: '0'
|
125
139
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
140
|
+
name: sorbet
|
127
141
|
requirement: !ruby/object:Gem::Requirement
|
128
142
|
requirements:
|
129
143
|
- - ">="
|
@@ -137,7 +151,7 @@ dependencies:
|
|
137
151
|
- !ruby/object:Gem::Version
|
138
152
|
version: '0'
|
139
153
|
- !ruby/object:Gem::Dependency
|
140
|
-
name:
|
154
|
+
name: tapioca
|
141
155
|
requirement: !ruby/object:Gem::Requirement
|
142
156
|
requirements:
|
143
157
|
- - ">="
|
@@ -168,6 +182,7 @@ files:
|
|
168
182
|
- lib/code_ownership/private/codeowners_file.rb
|
169
183
|
- lib/code_ownership/private/extension_loader.rb
|
170
184
|
- lib/code_ownership/private/glob_cache.rb
|
185
|
+
- lib/code_ownership/private/owner_assigner.rb
|
171
186
|
- lib/code_ownership/private/ownership_mappers/directory_ownership.rb
|
172
187
|
- lib/code_ownership/private/ownership_mappers/file_annotations.rb
|
173
188
|
- lib/code_ownership/private/ownership_mappers/js_package_ownership.rb
|
@@ -190,7 +205,7 @@ metadata:
|
|
190
205
|
source_code_uri: https://github.com/rubyatscale/code_ownership
|
191
206
|
changelog_uri: https://github.com/rubyatscale/code_ownership/releases
|
192
207
|
allowed_push_host: https://rubygems.org
|
193
|
-
post_install_message:
|
208
|
+
post_install_message:
|
194
209
|
rdoc_options: []
|
195
210
|
require_paths:
|
196
211
|
- lib
|
@@ -198,15 +213,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
198
213
|
requirements:
|
199
214
|
- - ">="
|
200
215
|
- !ruby/object:Gem::Version
|
201
|
-
version: '
|
216
|
+
version: '2.6'
|
202
217
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
203
218
|
requirements:
|
204
219
|
- - ">="
|
205
220
|
- !ruby/object:Gem::Version
|
206
221
|
version: '0'
|
207
222
|
requirements: []
|
208
|
-
rubygems_version: 3.
|
209
|
-
signing_key:
|
223
|
+
rubygems_version: 3.5.11
|
224
|
+
signing_key:
|
210
225
|
specification_version: 4
|
211
226
|
summary: A gem to help engineering teams declare ownership of code
|
212
227
|
test_files: []
|