code_ownership 1.36.3 → 1.38.0

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: eb464ff5e39c0c9f2712a7af6498f30b3b42e3c456e7da52a5209d7f30467346
4
- data.tar.gz: 5fd3f8974a041ff298fbdaceca0b3eccee027f4d9c94bdfaa344ae9b8ade72d5
3
+ metadata.gz: 7883f0d73587e099adf43101b91f0a2f296912cfa3e994e2f1c70472de918e4b
4
+ data.tar.gz: 1e11b8c1ce41986fa40325b3edf10369b00a7ed8a42f47e2ed4fe9694f565642
5
5
  SHA512:
6
- metadata.gz: ef51049577f74ab3faed01b4a37df119eaf577465fa10f342b79c0fabf7bc89b59ed993f8c1d8f687c53a3957745dfe579430e081473b9eb63f9b3cbe1f8eb19
7
- data.tar.gz: ba03e839b81e0dcdfd1c07356b99dbf850b9abddaa83049c2c74e458012c4b81bdfaead12ed6c59fc744848261e078d715f6512df876d1d1fa238362910bfcb9
6
+ metadata.gz: 5a381fc3a9867de019b70ada03aca768c427b05198449c4d6478f245f79fefd8e2bd56ab5df06fbe62577d12ce6f6bf80a6509f8347f65831ab8251075b1047f
7
+ data.tar.gz: 5ea28b28bbf41f5620fbf07d6d4083f8dcfea577648612fd8f05d4e128007ff73d6cec2e1a433ef84cf35a1e4b2c8b4a2d4fca0a8bbaeafedca948be293aaaba
data/README.md CHANGED
@@ -8,6 +8,30 @@ Check out [`code_ownership_spec.rb`](https://github.com/rubyatscale/code_ownersh
8
8
 
9
9
  There is also a [companion VSCode Extension]([url](https://github.com/rubyatscale/code-ownership-vscode)) for this gem. Just search `Gusto.code-ownership-vscode` in the VSCode Extension Marketplace.
10
10
 
11
+ ## Getting started
12
+
13
+ To get started there's a few things you should do.
14
+
15
+ 1) Create a `config/code_ownership.yml` file and declare where your files live. Here's a sample to start with:
16
+ ```yml
17
+ owned_globs:
18
+ - '{app,components,config,frontend,lib,packs,spec}/**/*.{rb,rake,js,jsx,ts,tsx}'
19
+ js_package_paths: []
20
+ unowned_globs:
21
+ - db/**/*
22
+ - app/services/some_file1.rb
23
+ - app/services/some_file2.rb
24
+ - frontend/javascripts/**/__generated__/**/*
25
+ ```
26
+ 2) Declare some teams. Here's an example, that would live at `config/teams/operations.yml`:
27
+ ```yml
28
+ name: Operations
29
+ github:
30
+ team: '@my-org/operations-team'
31
+ ```
32
+ 3) Declare ownership. You can do this at a directory level or at a file level. All of the files within the `owned_globs` you declared in step 1 will need to have an owner assigned (or be opted out via `unowned_globs`). See the next section for more detail.
33
+ 4) Run validations when you commit, and/or in CI. If you run validations in CI, ensure that if your `.github/CODEOWNERS` file gets changed, that gets pushed to the PR.
34
+
11
35
  ## Usage: Declaring Ownership
12
36
 
13
37
  There are three ways to declare code ownership using this gem.
@@ -48,7 +48,8 @@ module CodeOwnership
48
48
  .map(&:cleanpath)
49
49
  .each_with_object({}) do |pathname, res|
50
50
  owner = owner_for_codeowners_file(pathname)
51
- res[pathname.dirname.cleanpath.join('**/**').to_s] = owner
51
+ glob = glob_for_codeowners_file(pathname)
52
+ res[glob] = owner
52
53
  end
53
54
  end
54
55
 
@@ -77,7 +78,7 @@ module CodeOwnership
77
78
  # Takes a file and finds the relevant `.codeowner` file by walking up the directory
78
79
  # structure. Example, given `a/b/c.rb`, this looks for `a/b/.codeowner`, `a/.codeowner`,
79
80
  # and `.codeowner` in that order, stopping at the first file to actually exist.
80
- # If the parovided file is a directory, it will look for `.codeowner` in that directory and then upwards.
81
+ # If the provided file is a directory, it will look for `.codeowner` in that directory and then upwards.
81
82
  # We do additional caching so that we don't have to check for file existence every time.
82
83
  sig { params(file: String).returns(T.nilable(CodeTeams::Team)) }
83
84
  def map_file_to_relevant_owner(file)
@@ -123,6 +124,26 @@ module CodeOwnership
123
124
 
124
125
  team
125
126
  end
127
+
128
+ sig { params(codeowners_file: Pathname).returns(String) }
129
+ def glob_for_codeowners_file(codeowners_file)
130
+ unescaped = codeowners_file.dirname.cleanpath.join('**/**').to_s
131
+
132
+ # Globs can contain certain regex characters, like "[" and "]".
133
+ # However, when we are generating a glob from a .codeowner file, we
134
+ # need to escape bracket characters and interpret them literally.
135
+ # Otherwise the resulting glob will not actually match the directory
136
+ # containing the .codeowner file.
137
+ #
138
+ # Example
139
+ # file: "/some/[dir]/.codeowner"
140
+ # unescaped: "/some/[dir]/**/**"
141
+ # matches: "/some/d/file"
142
+ # matches: "/some/i/file"
143
+ # matches: "/some/r/file"
144
+ # does not match!: "/some/[dir]/file"
145
+ unescaped.gsub(/[\[\]]/) { |x| "\\#{x}" }
146
+ end
126
147
  end
127
148
  end
128
149
  end
@@ -39,7 +39,8 @@ module CodeOwnership
39
39
  owner = file_annotation_based_owner(filename_relative_to_root)
40
40
  next unless owner
41
41
 
42
- mapping[filename_relative_to_root] = owner
42
+ escaped_filename = escaped_path_for_codeowners_file(filename_relative_to_root)
43
+ mapping[escaped_filename] = owner
43
44
  end
44
45
  end
45
46
 
@@ -55,7 +56,8 @@ module CodeOwnership
55
56
 
56
57
  invalid_files = cache.keys.select do |file|
57
58
  # If a file is not tracked, it should be removed from the cache
58
- !Private.file_tracked?(file) ||
59
+ unescaped_file = unescaped_path_for_codeowners_file(file)
60
+ !Private.file_tracked?(unescaped_file) ||
59
61
  # If a file no longer has a file annotation (i.e. `globs_to_owner` doesn't map it)
60
62
  # it should be removed from the cache
61
63
  # 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 == []
@@ -126,6 +128,32 @@ module CodeOwnership
126
128
 
127
129
  sig { override.void }
128
130
  def bust_caches!; end
131
+
132
+ sig { params(filename: String).returns(String) }
133
+ def escaped_path_for_codeowners_file(filename)
134
+ # Globs can contain certain regex characters, like "[" and "]".
135
+ # However, when we are generating a glob from a file annotation, we
136
+ # need to escape bracket characters and interpret them literally.
137
+ # Otherwise the resulting glob will not actually match the directory
138
+ # containing the file.
139
+ #
140
+ # Example
141
+ # filename: "/some/[xId]/myfile.tsx"
142
+ # matches: "/some/1/file"
143
+ # matches: "/some/2/file"
144
+ # matches: "/some/3/file"
145
+ # does not match!: "/some/[xId]/myfile.tsx"
146
+ filename.gsub(/[\[\]]/) { |x| "\\#{x}" }
147
+ end
148
+
149
+ sig { params(filename: String).returns(String) }
150
+ def unescaped_path_for_codeowners_file(filename)
151
+ # Globs can contain certain regex characters, like "[" and "]".
152
+ # We escape bracket characters and interpret them literally for
153
+ # the CODEOWNERS file. However, we want to compare the unescaped
154
+ # glob to the actual file path when we check if the file was deleted.
155
+ filename.gsub(/\\([\[\]])/, '\1')
156
+ end
129
157
  end
130
158
  end
131
159
  end
@@ -99,6 +99,7 @@ module CodeOwnership
99
99
  in_unowned_globs = configuration.unowned_globs.any? do |unowned_glob|
100
100
  File.fnmatch?(unowned_glob, file, File::FNM_PATHNAME | File::FNM_EXTGLOB)
101
101
  end
102
+
102
103
  in_owned_globs && !in_unowned_globs && File.exist?(file)
103
104
  end
104
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.36.3
4
+ version: 1.38.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: 2024-07-30 00:00:00.000000000 Z
11
+ date: 2024-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: code_teams
@@ -220,7 +220,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
220
220
  - !ruby/object:Gem::Version
221
221
  version: '0'
222
222
  requirements: []
223
- rubygems_version: 3.5.11
223
+ rubygems_version: 3.5.22
224
224
  signing_key:
225
225
  specification_version: 4
226
226
  summary: A gem to help engineering teams declare ownership of code