Skip to content

Commit bb759f6

Browse files
authored
codeowners validate [file...] supports lefthook native staged files (#138)
* feat: add support for explicit file arguments to validate command Allows users to specify files directly on the command line: bin/codeownership validate file1.rb file2.rb This provides a more flexible alternative to the --diff flag for validating specific files. When explicit files are provided, they take precedence over the --diff flag and a warning is displayed. Changes: - Updated CLI to accept file arguments after options - Added warning when both --diff and explicit files are provided - Added comprehensive specs for file argument behavior - Updated README with usage examples * chore: bump version to 2.0.0-6 * rubocop fixes * apply markdown formatting * avoid stubbing stderr * add the bundle
1 parent 94f4400 commit bb759f6

File tree

8 files changed

+108
-19
lines changed

8 files changed

+108
-19
lines changed

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
22

33
gemspec
44

5-
gem 'rake-compiler', '~> 1.3.0'
5+
gem 'rake-compiler', '~> 1.3.0'

README.md

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ Check out [`lib/code_ownership.rb`](https://github.com/rubyatscale/code_ownershi
66

77
Check out [`code_ownership_spec.rb`](https://github.com/rubyatscale/code_ownership/blob/main/spec/lib/code_ownership_spec.rb) to see examples of how code ownership is used.
88

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.
9+
There is also a [companion VSCode Extension](https://github.com/rubyatscale/code-ownership-vscode) for this gem. Just search `Gusto.code-ownership-vscode` in the VSCode Extension Marketplace.
1010

1111
## Getting started
1212

1313
To get started there's a few things you should do.
1414

15-
1) Create a `config/code_ownership.yml` file and declare where your files live. Here's a sample to start with:
15+
1. Create a `config/code_ownership.yml` file and declare where your files live. Here's a sample to start with:
16+
1617
```yml
1718
owned_globs:
1819
- '{app,components,config,frontend,lib,packs,spec}/**/*.{rb,rake,js,jsx,ts,tsx}'
@@ -23,33 +24,42 @@ unowned_globs:
2324
- app/services/some_file2.rb
2425
- frontend/javascripts/**/__generated__/**/*
2526
```
26-
2) Declare some teams. Here's an example, that would live at `config/teams/operations.yml`:
27+
28+
2. Declare some teams. Here's an example, that would live at `config/teams/operations.yml`:
29+
2730
```yml
2831
name: Operations
2932
github:
3033
team: '@my-org/operations-team'
3134
```
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.
35+
36+
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.
37+
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.
3438

3539
## Usage: Declaring Ownership
3640

3741
There are five ways to declare code ownership using this gem:
3842

3943
### Directory-Based Ownership
44+
4045
Directory based ownership allows for all files in that directory and all its sub-directories to be owned by one team. To define this, add a `.codeowner` file inside that directory with the name of the team as the contents of that file.
46+
4147
```
4248
Team
4349
```
4450
4551
### File-Annotation Based Ownership
52+
4653
File annotations are a last resort if there is no clear home for your code. File annotations go at the top of your file, and look like this:
54+
4755
```ruby
4856
# @team MyTeam
4957
```
5058

5159
### Package-Based Ownership
60+
5261
Package based ownership integrates [`packwerk`](https://github.com/Shopify/packwerk) and has ownership defined per package. To define that all files within a package are owned by one team, configure your `package.yml` like this:
62+
5363
```yml
5464
enforce_dependency: true
5565
enforce_privacy: true
@@ -58,16 +68,19 @@ metadata:
5868
```
5969
6070
You can also define `owner` as a top-level key, e.g.
71+
6172
```yml
6273
enforce_dependency: true
6374
enforce_privacy: true
6475
owner: Team
6576
```
6677

67-
To do this, add `code_ownership` to the `require` key of your `packwerk.yml`. See https://github.com/Shopify/packwerk/blob/main/USAGE.md#loading-extensions for more information.
78+
To do this, add `code_ownership` to the `require` key of your `packwerk.yml`. See <https://github.com/Shopify/packwerk/blob/main/USAGE.md#loading-extensions> for more information.
6879

6980
### Glob-Based Ownership
81+
7082
In your team's configured YML (see [`code_teams`](https://github.com/rubyatscale/code_teams)), you can set `owned_globs` to be a glob of files your team owns. For example, in `my_team.yml`:
83+
7184
```yml
7285
name: My Team
7386
owned_globs:
@@ -78,6 +91,7 @@ unowned_globs:
7891
```
7992

8093
### Javascript Package Ownership
94+
8195
Javascript package based ownership allows you to specify an ownership key in a `package.json`. To use this, configure your `package.json` like this:
8296

8397
```json
@@ -91,6 +105,7 @@ Javascript package based ownership allows you to specify an ownership key in a `
91105
```
92106

93107
You can also tell `code_ownership` where to find JS packages in the configuration, like this:
108+
94109
```yml
95110
js_package_paths:
96111
- frontend/javascripts/packages/*
@@ -101,12 +116,15 @@ This defaults `**/`, which makes it look for `package.json` files across your ap
101116

102117
> [!NOTE]
103118
> Javscript package ownership does not respect `unowned_globs`. If you wish to disable usage of this feature you can set `js_package_paths` to an empty list.
119+
104120
```yml
105121
js_package_paths: []
106122
```
107123

108124
## Usage: Reading CodeOwnership
125+
109126
### `for_file`
127+
110128
`CodeOwnership.for_file`, given a relative path to a file returns a `CodeTeams::Team` if there is a team that owns the file, `nil` otherwise.
111129

112130
```ruby
@@ -118,6 +136,7 @@ Contributor note: If you are making updates to this method or the methods gettin
118136
See `code_ownership_spec.rb` for examples.
119137

120138
### `for_backtrace`
139+
121140
`CodeOwnership.for_backtrace` can be given a backtrace and will either return `nil`, or a `CodeTeams::Team`.
122141

123142
```ruby
@@ -141,34 +160,38 @@ Under the hood, this finds the file where the class is defined and returns the o
141160
See `code_ownership_spec.rb` for an example.
142161

143162
### `for_team`
163+
144164
`CodeOwnership.for_team` can be used to generate an ownership report for a team.
165+
145166
```ruby
146167
CodeOwnership.for_team('My Team')
147168
```
148169

149170
You can shovel this into a markdown file for easy viewing using the CLI:
171+
150172
```
151-
bin/codeownership for_team 'My Team' > tmp/ownership_report.md
173+
codeownership for_team 'My Team' > tmp/ownership_report.md
152174
```
153175

154176
## Usage: Generating a `CODEOWNERS` file
155177

156-
A `CODEOWNERS` file defines who owns specific files or paths in a repository. When you run `bin/codeownership validate`, a `.github/CODEOWNERS` file will automatically be generated and updated.
178+
A `CODEOWNERS` file defines who owns specific files or paths in a repository. When you run `codeownership validate`, a `.github/CODEOWNERS` file will automatically be generated and updated.
157179

158180
If `codeowners_path` is set in `code_ownership.yml` codeowners will use that path to generate the `CODEOWNERS` file. For example, `codeowners_path: docs` will generate `docs/CODEOWNERS`.
159181

160182
## Proper Configuration & Validation
161183

162184
CodeOwnership comes with a validation function to ensure the following things are true:
163185

164-
1) Only one mechanism is defining file ownership. That is -- you can't have a file annotation on a file owned via package-based or glob-based ownership. This helps make ownership behavior more clear by avoiding concerns about precedence.
165-
2) All teams referenced as an owner for any file or package is a valid team (i.e. it's in the list of `CodeTeams.all`).
166-
3) All files have ownership. You can specify in `unowned_globs` to represent a TODO list of files to add ownership to.
167-
3) The `.github/CODEOWNERS` file is up to date. This is automatically corrected and staged unless specified otherwise with `bin/codeownership validate --skip-autocorrect --skip-stage`. You can turn this validation off by setting `skip_codeowners_validation: true` in `config/code_ownership.yml`.
186+
1. Only one mechanism is defining file ownership. That is -- you can't have a file annotation on a file owned via package-based or glob-based ownership. This helps make ownership behavior more clear by avoiding concerns about precedence.
187+
2. All teams referenced as an owner for any file or package is a valid team (i.e. it's in the list of `CodeTeams.all`).
188+
3. All files have ownership. You can specify in `unowned_globs` to represent a TODO list of files to add ownership to.
189+
4. The `.github/CODEOWNERS` file is up to date. This is automatically corrected and staged unless specified otherwise with `bin/codeownership validate --skip-autocorrect --skip-stage`. You can turn this validation off by setting `skip_codeowners_validation: true` in `config/code_ownership.yml`.
168190

169191
CodeOwnership also allows you to specify which globs and file extensions should be considered ownable.
170192

171193
Here is an example `config/code_ownership.yml`.
194+
172195
```yml
173196
owned_globs:
174197
- '{app,components,config,frontend,lib,packs,spec}/**/*.{rb,rake,js,jsx,ts,tsx}'
@@ -178,24 +201,37 @@ unowned_globs:
178201
- app/services/some_file2.rb
179202
- frontend/javascripts/**/__generated__/**/*
180203
```
204+
181205
You can call the validation function with the Ruby API
206+
182207
```ruby
183208
CodeOwnership.validate!
184209
```
210+
185211
or the CLI
186-
```
187-
bin/codeownership validate
212+
213+
```bash
214+
# Validate all files
215+
codeownership validate
216+
217+
# Validate specific files
218+
codeownership validate path/to/file1.rb path/to/file2.rb
219+
220+
# Validate only staged files
221+
codeownership validate --diff
188222
```
189223

190224
## Development
191225

192226
Please add to `CHANGELOG.md` and this `README.md` when you make make changes.
193227

194228
## Running specs
229+
195230
```sh
196231
bundle install
197232
bundle exec rake
198233
```
199234

200235
## Creating a new release
236+
201237
Simply [create a new release](https://github.com/rubyatscale/code_ownership/releases/new) with github. The release tag must match the gem version

lib/code_ownership.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
# typed: strict
44

5-
require 'set'
65
require 'code_teams'
76
require 'sorbet-runtime'
87
require 'json'

lib/code_ownership/cli.rb

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def self.validate!(argv)
3737
options = {}
3838

3939
parser = OptionParser.new do |opts|
40-
opts.banner = "Usage: #{EXECUTABLE} validate [options]"
40+
opts.banner = "Usage: #{EXECUTABLE} validate [options] [files...]"
4141

4242
opts.on('--skip-autocorrect', 'Skip automatically correcting any errors, such as the .github/CODEOWNERS file') do
4343
options[:skip_autocorrect] = true
@@ -59,11 +59,22 @@ def self.validate!(argv)
5959
args = parser.order!(argv)
6060
parser.parse!(args)
6161

62-
files = if options[:diff]
62+
# Collect any remaining arguments as file paths
63+
specified_files = argv.reject { |arg| arg.start_with?('--') }
64+
65+
files = if !specified_files.empty?
66+
# Files explicitly provided on command line
67+
if options[:diff]
68+
warn 'Warning: Ignoring --diff flag because explicit files were provided'
69+
end
70+
specified_files.select { |file| File.exist?(file) }
71+
elsif options[:diff]
72+
# Staged files from git
6373
ENV.fetch('CODEOWNERS_GIT_STAGED_FILES') { `git diff --staged --name-only` }.split("\n").select do |file|
6474
File.exist?(file)
6575
end
6676
else
77+
# No files specified, validate all
6778
nil
6879
end
6980

174 KB
Binary file not shown.

lib/code_ownership/private/for_file_output_builder.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ module CodeOwnership
66
module Private
77
class ForFileOutputBuilder
88
extend T::Sig
9+
910
private_class_method :new
1011

1112
sig { params(file_path: String, json: T::Boolean, verbose: T::Boolean).void }

lib/code_ownership/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
# frozen_string_literal: true
33

44
module CodeOwnership
5-
VERSION = '2.0.0'
5+
VERSION = '2.1.0'
66
end

spec/lib/code_ownership/cli_spec.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,48 @@
4545
end
4646
end
4747

48+
context 'with explicit file arguments' do
49+
let(:argv) { ['validate', 'app/services/my_file.rb', 'frontend/javascripts/my_file.jsx'] }
50+
51+
it 'validates only the specified files' do
52+
expect(CodeOwnership).to receive(:validate!) do |args|
53+
expect(args[:files]).to match_array(['app/services/my_file.rb', 'frontend/javascripts/my_file.jsx'])
54+
expect(args[:autocorrect]).to eq true
55+
expect(args[:stage_changes]).to eq true
56+
end
57+
subject
58+
end
59+
60+
context 'with options' do
61+
let(:argv) { ['validate', '--skip-autocorrect', '--skip-stage', 'app/services/my_file.rb'] }
62+
63+
it 'passes the options correctly' do
64+
expect(CodeOwnership).to receive(:validate!) do |args|
65+
expect(args[:files]).to eq(['app/services/my_file.rb'])
66+
expect(args[:autocorrect]).to eq false
67+
expect(args[:stage_changes]).to eq false
68+
end
69+
subject
70+
end
71+
end
72+
73+
context 'when combined with --diff flag' do
74+
let(:argv) { ['validate', '--diff', 'app/services/my_file.rb'] }
75+
76+
before do
77+
allow(ENV).to receive(:fetch).and_call_original
78+
allow(ENV).to receive(:fetch).with('CODEOWNERS_GIT_STAGED_FILES').and_return('other_file.rb')
79+
end
80+
81+
it 'prioritizes explicit files over git diff' do
82+
expect(CodeOwnership).to receive(:validate!) do |args|
83+
expect(args[:files]).to eq(['app/services/my_file.rb'])
84+
end
85+
subject
86+
end
87+
end
88+
end
89+
4890
context 'with --diff argument' do
4991
let(:argv) { ['validate', '--diff'] }
5092

0 commit comments

Comments
 (0)