fast_code_owners 0.0.9 → 0.0.11
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/.cargo/config +2 -2
- data/Cargo.lock +2 -2
- data/ext/cargo-vendor/codeowners-0.2.5/.cargo-checksum.json +1 -1
- data/ext/cargo-vendor/codeowners-0.2.5/Cargo.toml +4 -0
- data/ext/cargo-vendor/codeowners-0.2.5/src/cli.rs +10 -4
- data/ext/cargo-vendor/codeowners-0.2.5/src/ownership/for_file_fast.rs +163 -52
- data/ext/cargo-vendor/codeowners-0.2.5/src/ownership.rs +1 -1
- data/ext/cargo-vendor/codeowners-0.2.5/src/project.rs +33 -3
- data/ext/cargo-vendor/codeowners-0.2.5/src/project_builder.rs +22 -7
- data/ext/cargo-vendor/codeowners-0.2.5/src/project_file_builder.rs +1 -1
- data/ext/cargo-vendor/codeowners-0.2.5/src/runner.rs +23 -10
- data/ext/cargo-vendor/codeowners-0.2.5/tests/common/mod.rs +100 -0
- data/ext/cargo-vendor/codeowners-0.2.5/tests/git_stage_test.rs +44 -0
- data/ext/fast_code_owners/Cargo.toml +2 -3
- data/ext/fast_code_owners/src/lib.rs +10 -3
- data/lib/fast_code_owners/cli.rb +10 -15
- data/lib/fast_code_owners/version.rb +1 -1
- data/lib/fast_code_owners.rb +7 -1
- metadata +2 -2
- data/ext/cargo-vendor/codeowners-0.2.5/ai-prompts/for-file-optimization.md +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1380e7d12f10242ac7d11dc6f00a9bfdbf83e8d8157522fbd474ea80027b7902
|
4
|
+
data.tar.gz: 9ee1e96b93188cd9e788e479f19fdacde71854bdc18c238739531e3083224e44
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e48a93e1aa738e3a9a1ecbe1ab8b13b704a7c41a99605ce0ce946ccd20e38ec86d10204ed371d6d14c3c269f12501a168c8a5489350a70484c851eb05aa02a06
|
7
|
+
data.tar.gz: dc6a57424e0e1312ebe44aa42a66f7d7b6b8b2e423f11a122acdb034fc10a8906a5d538934b87e8ea216b70b36b052d82bea12b19662f9c5a45eb96fd5a25e45
|
data/.cargo/config
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
[source.crates-io]
|
2
2
|
replace-with = "vendored-sources"
|
3
3
|
|
4
|
-
[source."git+https://github.com/rubyatscale/codeowners-rs.git?branch=ph.fast-file
|
4
|
+
[source."git+https://github.com/rubyatscale/codeowners-rs.git?branch=ph.blazing-fast-for-file"]
|
5
5
|
git = "https://github.com/rubyatscale/codeowners-rs.git"
|
6
|
-
branch = "ph.fast-file
|
6
|
+
branch = "ph.blazing-fast-for-file"
|
7
7
|
replace-with = "vendored-sources"
|
8
8
|
|
9
9
|
[source.vendored-sources]
|
data/Cargo.lock
CHANGED
@@ -189,7 +189,7 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
|
189
189
|
[[package]]
|
190
190
|
name = "codeowners"
|
191
191
|
version = "0.2.5"
|
192
|
-
source = "git+https://github.com/rubyatscale/codeowners-rs.git?branch=ph.fast-file
|
192
|
+
source = "git+https://github.com/rubyatscale/codeowners-rs.git?branch=ph.blazing-fast-for-file#66c46e9b63c415814432205881e7032b53424009"
|
193
193
|
dependencies = [
|
194
194
|
"clap",
|
195
195
|
"clap_derive",
|
@@ -298,7 +298,7 @@ dependencies = [
|
|
298
298
|
|
299
299
|
[[package]]
|
300
300
|
name = "fast_code_owners"
|
301
|
-
version = "0.1.
|
301
|
+
version = "0.1.1"
|
302
302
|
dependencies = [
|
303
303
|
"codeowners",
|
304
304
|
"magnus",
|
@@ -1 +1 @@
|
|
1
|
-
{"files":{".github/workflows/audit.yml":"bde77859031fa481da1fcaf399129b64819d78a0ffbbb2d4c4f5346f4a4dec7e",".github/workflows/ci.yml":"a4b503d5b650f4d59d7e02b3a33f623c373f1ae8c892b6ddcc47b271b7d109db",".github/workflows/dotslash-config.json":"21dbdefc13a01f3aad42923963b9025907d4049d8e75b18208e887f9e601e381",".rustfmt.toml":"394616646760b18ed094433d39e18b7d94986347c21790708bff9ab001f38e0b",".rusty-hook.toml":"9d3d8a8f4f20d80682ce71889a17eb5ba204f4636f27211fafcb6c6b5af5c2bb","Cargo.lock":"13b3503427ab09ad422f8c79bdc03993c2bb89041ae4a9145380321436b5f185","Cargo.toml":"f7c6017dc1d06e2131fbe10a76b0f93fa1253e48496643e55d6899619ad738f7","README.md":"84c2b0f58f5880062b3668fc49bccc8300be209b448340a7fde075bdce5e6fb5","ai-prompts/for-file-optimization.md":"e4c758898de368b9864174fa9bcf0696812b0829e11abda3ef823c4056e237be","dev/run_benchmarks.sh":"81f905201c41e661e4c8208a17f8a4f942b9dcf8becb7b27cadb4ee83283164b","rust-toolchain.toml":"2376c9760d9e4f7ad6a1c27ab074f466872a3b976da1a1652967c6a5eaa65f28","src/bin/compare_for_file.rs":"3ab4adb258f6c6eafcc3689a03d48d9561560e1330c23ba4ef9e8f37645f8ce5","src/cache/file.rs":"b806ba1c8f6f97f6185fd41862da00f8687a8d252b0cc1e8f325b1c9761f6d42","src/cache/mod.rs":"622ad6effc3260c922fe49f3800e0d1c5b3c6a73c81d567296ea6cb550792d32","src/cache/noop.rs":"abfddf1c20cbaca5dd432952d0ff0a0a1d92eb0070866d343e368f29cdd22de9","src/cli.rs":"8e883c291369dd72eff33884db6433a78ef84d902e5f9f475b8a85c50cc071dc","src/common_test.rs":"dfa10d48dc2c5e63b029bfb56ab2d653fc9d07e6ee37859383b9a1d2f55e6c16","src/config.rs":"ad0046ad359dc545c6f06ba61d08e773b7074a5277f8c06e4b34d134f9bbdebc","src/lib.rs":"d48bdfa06836a217b9c0fea3c499e343aea9b50ea939c6b6ad6f2ab664e3da0e","src/main.rs":"ea738dd470e3d8cdf46cbfb397cf5b27c212e0ea94e8d7c2d9c7ea163bbdde65","src/ownership.rs":"30e788b08d2c7ca3df75a3f6c410596848fe54c40e548f9c3dec36aae91d6cae","src/ownership/file_generator.rs":"be57f0fe6208bb810bafa59a3c782e8337d099d02728214e932ca2fe620ddb5d","src/ownership/file_owner_finder.rs":"104626da603ddcaccb217387f13141c81fb768b41cf8b7fbc5f65809f487231b","src/ownership/for_file_fast.rs":"9b7d968e43f1dadf1f371dc0c5254bac7f417ee516394aef4f059e9caf886e14","src/ownership/mapper.rs":"f313ae6cc6bfe57a4837475655fbce1ea7a715231df05fa15570741431fa53b5","src/ownership/mapper/directory_mapper.rs":"a86866b518efbfdbb056a2d472c981eec875f2ddca5899f029405b76e875b018","src/ownership/mapper/escaper.rs":"2faa41beefedca26d493e8d8f68882384ddbb2c4b108953f30b960847a3f841c","src/ownership/mapper/package_mapper.rs":"290e27c6f8f8d1ff072a6341db3029f70cd5214398c103becd55189a744a7f3e","src/ownership/mapper/team_file_mapper.rs":"d9bb2bd7201ae38c626cd9ceff07b221477980a04f7f56cb3bd961d2729b90e8","src/ownership/mapper/team_gem_mapper.rs":"50b6ad9e60c03e055662724296576b4586167b0d7d6c03819f98b30c0f37342f","src/ownership/mapper/team_glob_mapper.rs":"ce0638118ec9c610210880534a62740f9bd4d15494b0b9516bcc11fa2bbfb508","src/ownership/mapper/team_yml_mapper.rs":"3c759139fc9ff362b82102aeaec958aec7ec9df86df87482a3362ab8ea54404f","src/ownership/parser.rs":"82fc23b22bfe046127b98d875196a406dffaad55e1dd120e3b37a1beb1e54e12","src/ownership/tests.rs":"2d4cf9995be92aab8329b35a7c1181091c08724e24d42527358496d7422df35d","src/ownership/validator.rs":"c710c5f3369b2bbb00c72aa7743a4e908b11dd68bacaf5404d87820f531433d0","src/project.rs":"e97b4f0f6a0e3ccba1bda088207766d30026d99c0ae25fa56d119e45e8faaa28","src/project_builder.rs":"c048962bec93c1be487cb578c4f9f9254b02fed5f7a55fa2f6414558388c3ba4","src/project_file_builder.rs":"c93ecc878178281180e64a5b01e1934634f85502545ef6478f5252a7c1b67f62","src/runner.rs":"e8126328fe26a79514e209f24aed296c650f439600850e9cccf0f90381cc7416","tests/cache_test.rs":"80b900f38e177eb8c9e162b9373cd1d7c3a551baade716a1b07554a021bfef05","tests/common/mod.rs":"41f2a3a2e886ea2ebc2a58f9b682d4eaa4dc6874297d219364404d00a034d7da","tests/fixtures/invalid_project/.github/CODEOWNERS":"01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b","tests/fixtures/invalid_project/config/code_ownership.yml":"395b9023f51a29b8326a230c82543d8e8e7656376b4d82b1e77c7c5879620a30","tests/fixtures/invalid_project/config/teams/payments.yml":"205b0d200a5b686ad91c4080eadeefef200cc4974939b5ef02c15ef2edf775a1","tests/fixtures/invalid_project/config/teams/payroll.yml":"f3a85ce896837c2a9d38c2d23a600b1fd72f3bf8c4c65c12719ce942b3b17375","tests/fixtures/invalid_project/gems/payroll_calculator/calculator.rb":"350c85edfe24cd54b2479eda5266aae894b4c5eb6e9a3c0a0a1fb30e8c4d1d0f","tests/fixtures/invalid_project/ruby/app/models/bank_account.rb":"0832b2e95f90fa7ebd8dcbfd53c05618f091d38abf5dd238bd4d41645746160d","tests/fixtures/invalid_project/ruby/app/models/blockchain.rb":"9ffaa2304d02b8d40050017b62edbaf61b8600dc0b1a2c5b7f069b7c55c30616","tests/fixtures/invalid_project/ruby/app/models/payroll.rb":"7f48f8730fd738a2760ee81be8a6028cccb3c6c38be0433c7fd81a4bdd631b60","tests/fixtures/invalid_project/ruby/app/payments/nacha.rb":"c5ced2841986f3d48be0a132185a39dbc327715064574887303a25cf02aaad4d","tests/fixtures/invalid_project/ruby/app/services/.codeowner":"53fe8dfb6d9e1b03219adddcc3ffb741557dd579dc653d026097c463def4a8fe","tests/fixtures/invalid_project/ruby/app/services/multi_owned.rb":"42b340c67ad95016eb7e852abe37b39f7a0b5be41f897f972e05d2e581d6c30f","tests/fixtures/invalid_project/ruby/app/unowned.rb":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","tests/fixtures/invalid_project/ruby/packages/payroll_flow/package.yml":"c1a08643821d02a57131870e8b6325f864db0687c5583f3d0872ada9d004072a","tests/fixtures/multiple-directory-owners/.github/CODEOWNERS":"957d160c3d00eb530f7ebb34cc9b7c61934d108eb52ce3cb03b6d9978fad86df","tests/fixtures/multiple-directory-owners/app/consumers/.codeowner":"842c88863910f6eab22edcbdbe9f0f4dd96c6b46c711bd40277b36b8178943c4","tests/fixtures/multiple-directory-owners/app/consumers/deep/nesting/nestdir/deep_file.rb":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","tests/fixtures/multiple-directory-owners/app/consumers/one_owner.rb":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","tests/fixtures/multiple-directory-owners/app/services/.codeowner":"3eae1599bb7f187b86d6427942d172ba8dd7ee5962aab03e0839ad9d59c37eb0","tests/fixtures/multiple-directory-owners/app/services/exciting/.codeowner":"842c88863910f6eab22edcbdbe9f0f4dd96c6b46c711bd40277b36b8178943c4","tests/fixtures/multiple-directory-owners/app/services/exciting/some_other_file.rb":"c0cf1112663d326ba9b47fbcf961e98170c32e9287d5af574c35b3350fb3c59a","tests/fixtures/multiple-directory-owners/config/code_ownership.yml":"fc9679f69ed09090e104341c7d6635c050e81472370acb504a3a42040a48332f","tests/fixtures/multiple-directory-owners/config/teams/bar.yml":"253d9d9cb7f5b5cb81af19d8af7631b510d0f4ad59b32465c5e26bd78886f9c6","tests/fixtures/multiple-directory-owners/config/teams/foo.yml":"d4ba6d61c16e4227681f13013775e3d117e0cee4631714849405c47ff36ecbfc","tests/fixtures/valid_project/.github/CODEOWNERS":"c8e86365c334ac5aeb1d41a579a7f20236b79b4c0cd0e706b8d48a91fb31b641","tests/fixtures/valid_project/.ignore":"68f30767f9c87c700952d52d9d39693e1a98efb6c07a0b9216fbc87d173ebb5e","tests/fixtures/valid_project/config/code_ownership.yml":"a336d56f500203588631ee9f48101f99adf476a3fd786d74cb30e6c9060ae3b3","tests/fixtures/valid_project/config/teams/payments.yml":"7c4edf4ab7db59c1ef13ef4e7aa498c8e8c23c19427ebebe4f43bca003619f2a","tests/fixtures/valid_project/config/teams/payroll.yml":"f3a85ce896837c2a9d38c2d23a600b1fd72f3bf8c4c65c12719ce942b3b17375","tests/fixtures/valid_project/config/teams/ux.yml":"918e96ab1b5c67c99b30b8e984c2cef8fa956b1c83802a76923b3f448bcdec62","tests/fixtures/valid_project/gems/payroll_calculator/calculator.rb":"991056cdd4824eea15055f9c055c22fb3281cc21cffbca94724b330b8a450e59","tests/fixtures/valid_project/gems/pets/dog.rb":"ed2553442d1646af1c421a7a04b92269612efbc5ec0ccb32930e27ceac65b2ea","tests/fixtures/valid_project/javascript/packages/PayrollFlow/index.tsx":"b78b223d3bdb83af6ef7f78bd305f6e18f11abf961ded3266f7bebdb9eb00686","tests/fixtures/valid_project/javascript/packages/PayrollFlow/package.json":"271a0bb50784a65df66fd5db7167d1dd67430c774c207eda58a96974dbafc408","tests/fixtures/valid_project/javascript/packages/items/(special)/.codeowner":"632e1c5839411a1009ef268e992c4b183cb1d5381b198eac0f8323213a61ed82","tests/fixtures/valid_project/javascript/packages/items/(special)/pay.ts":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","tests/fixtures/valid_project/javascript/packages/items/.codeowner":"53fe8dfb6d9e1b03219adddcc3ffb741557dd579dc653d026097c463def4a8fe","tests/fixtures/valid_project/javascript/packages/items/item.ts":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","tests/fixtures/valid_project/ruby/app/models/bank_account.rb":"0832b2e95f90fa7ebd8dcbfd53c05618f091d38abf5dd238bd4d41645746160d","tests/fixtures/valid_project/ruby/app/models/payroll.rb":"7f48f8730fd738a2760ee81be8a6028cccb3c6c38be0433c7fd81a4bdd631b60","tests/fixtures/valid_project/ruby/app/payments/foo/.codeowner":"53fe8dfb6d9e1b03219adddcc3ffb741557dd579dc653d026097c463def4a8fe","tests/fixtures/valid_project/ruby/app/payments/foo/ownedby_payroll.rb":"705296c5ae6b3c1efdb410c952095b6efba3f9edd0af1b42d20e6ba217b1744d","tests/fixtures/valid_project/ruby/app/payments/nacha.rb":"c5ced2841986f3d48be0a132185a39dbc327715064574887303a25cf02aaad4d","tests/fixtures/valid_project/ruby/app/payroll/.codeowner":"b58412ec67186588652c88a6c71fd7499e27984e1e587e137426bed8ddd53173","tests/fixtures/valid_project/ruby/app/payroll/payroll.rb":"ff6cb08457469043bd93b4fdbcecc37a29a506641ab486f9cafd319125ff00ca","tests/fixtures/valid_project/ruby/ignored_files/git_ignored.rb":"fb333306f05e93ff821123b1c09d264c5f37f1bd739156d91167789795704671","tests/fixtures/valid_project/ruby/packages/payroll_flow/package.yml":"c1a08643821d02a57131870e8b6325f864db0687c5583f3d0872ada9d004072a","tests/fixtures/valid_project/should_be_ignored/an_ignored_file.rb":"bd5ce0746f05ded73148e184b36741f3846e0af7409b42c16abae54a9220113a","tests/invalid_project_structure_test.rs":"af048ddfa756bc2e187ea885ea533a4b67907c5bcc531a082ffceca0728649b4","tests/invalid_project_test.rs":"e62b8129d1af0a5a66ced9547dd1e67f17c42980e2b975205909a4530ec3b8db","tests/multiple_directory_owners_test.rs":"bee053097f88481454ad38349e23e716bc44874348b5f4b67687004c9cb9e52f","tests/valid_project_test.rs":"17df92c5a07c8f1c2b1f243a43927c7fe3e0cba3573996fbb77bb7a19aecab62","tmp/.gitkeep":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},"package":null}
|
1
|
+
{"files":{".github/workflows/audit.yml":"bde77859031fa481da1fcaf399129b64819d78a0ffbbb2d4c4f5346f4a4dec7e",".github/workflows/ci.yml":"a4b503d5b650f4d59d7e02b3a33f623c373f1ae8c892b6ddcc47b271b7d109db",".github/workflows/dotslash-config.json":"21dbdefc13a01f3aad42923963b9025907d4049d8e75b18208e887f9e601e381",".rustfmt.toml":"394616646760b18ed094433d39e18b7d94986347c21790708bff9ab001f38e0b",".rusty-hook.toml":"9d3d8a8f4f20d80682ce71889a17eb5ba204f4636f27211fafcb6c6b5af5c2bb","Cargo.lock":"13b3503427ab09ad422f8c79bdc03993c2bb89041ae4a9145380321436b5f185","Cargo.toml":"97a8a8fafa063e4e721ca8eb297787d0820cc1163e2eb512513ff92fdb9bef13","README.md":"84c2b0f58f5880062b3668fc49bccc8300be209b448340a7fde075bdce5e6fb5","dev/run_benchmarks.sh":"81f905201c41e661e4c8208a17f8a4f942b9dcf8becb7b27cadb4ee83283164b","rust-toolchain.toml":"2376c9760d9e4f7ad6a1c27ab074f466872a3b976da1a1652967c6a5eaa65f28","src/bin/compare_for_file.rs":"3ab4adb258f6c6eafcc3689a03d48d9561560e1330c23ba4ef9e8f37645f8ce5","src/cache/file.rs":"b806ba1c8f6f97f6185fd41862da00f8687a8d252b0cc1e8f325b1c9761f6d42","src/cache/mod.rs":"622ad6effc3260c922fe49f3800e0d1c5b3c6a73c81d567296ea6cb550792d32","src/cache/noop.rs":"abfddf1c20cbaca5dd432952d0ff0a0a1d92eb0070866d343e368f29cdd22de9","src/cli.rs":"f9f8656647ade58b1f975718240b1ea5a85de34c767e9df9ceafa7e1dcf6c546","src/common_test.rs":"dfa10d48dc2c5e63b029bfb56ab2d653fc9d07e6ee37859383b9a1d2f55e6c16","src/config.rs":"ad0046ad359dc545c6f06ba61d08e773b7074a5277f8c06e4b34d134f9bbdebc","src/lib.rs":"d48bdfa06836a217b9c0fea3c499e343aea9b50ea939c6b6ad6f2ab664e3da0e","src/main.rs":"ea738dd470e3d8cdf46cbfb397cf5b27c212e0ea94e8d7c2d9c7ea163bbdde65","src/ownership.rs":"22ce2ecab5226a86eccf91cea0c2c679115ddaf764d81910ff001722c391f391","src/ownership/file_generator.rs":"be57f0fe6208bb810bafa59a3c782e8337d099d02728214e932ca2fe620ddb5d","src/ownership/file_owner_finder.rs":"104626da603ddcaccb217387f13141c81fb768b41cf8b7fbc5f65809f487231b","src/ownership/for_file_fast.rs":"d2b35647eb2ffdda07a815e550262e04d430ac405c073dc4e144e332657063a8","src/ownership/mapper.rs":"f313ae6cc6bfe57a4837475655fbce1ea7a715231df05fa15570741431fa53b5","src/ownership/mapper/directory_mapper.rs":"a86866b518efbfdbb056a2d472c981eec875f2ddca5899f029405b76e875b018","src/ownership/mapper/escaper.rs":"2faa41beefedca26d493e8d8f68882384ddbb2c4b108953f30b960847a3f841c","src/ownership/mapper/package_mapper.rs":"290e27c6f8f8d1ff072a6341db3029f70cd5214398c103becd55189a744a7f3e","src/ownership/mapper/team_file_mapper.rs":"d9bb2bd7201ae38c626cd9ceff07b221477980a04f7f56cb3bd961d2729b90e8","src/ownership/mapper/team_gem_mapper.rs":"50b6ad9e60c03e055662724296576b4586167b0d7d6c03819f98b30c0f37342f","src/ownership/mapper/team_glob_mapper.rs":"ce0638118ec9c610210880534a62740f9bd4d15494b0b9516bcc11fa2bbfb508","src/ownership/mapper/team_yml_mapper.rs":"3c759139fc9ff362b82102aeaec958aec7ec9df86df87482a3362ab8ea54404f","src/ownership/parser.rs":"82fc23b22bfe046127b98d875196a406dffaad55e1dd120e3b37a1beb1e54e12","src/ownership/tests.rs":"2d4cf9995be92aab8329b35a7c1181091c08724e24d42527358496d7422df35d","src/ownership/validator.rs":"c710c5f3369b2bbb00c72aa7743a4e908b11dd68bacaf5404d87820f531433d0","src/project.rs":"367d3ee94960aeb857c821f9b67b812a2d2dd4ad683b865911fb091a29d44ba3","src/project_builder.rs":"cfa11efd86192c7421029d9600b7dda6cfd85249a88c7664b8e27c869c4194d6","src/project_file_builder.rs":"53dc63ce012bfebcab49e50a2ba9b7e40bb88f9d921f38b3aa8ab1bb60d7a609","src/runner.rs":"3bdd9779948228b860c1a57fb6eedc6015954e3bbcc38de64f21ec743b8f7377","tests/cache_test.rs":"80b900f38e177eb8c9e162b9373cd1d7c3a551baade716a1b07554a021bfef05","tests/common/mod.rs":"5b24dc5a6f90be8a5d75dc96d97f3992684f5f8a1f46562245dfddeb86b4232e","tests/fixtures/invalid_project/.github/CODEOWNERS":"01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b","tests/fixtures/invalid_project/config/code_ownership.yml":"395b9023f51a29b8326a230c82543d8e8e7656376b4d82b1e77c7c5879620a30","tests/fixtures/invalid_project/config/teams/payments.yml":"205b0d200a5b686ad91c4080eadeefef200cc4974939b5ef02c15ef2edf775a1","tests/fixtures/invalid_project/config/teams/payroll.yml":"f3a85ce896837c2a9d38c2d23a600b1fd72f3bf8c4c65c12719ce942b3b17375","tests/fixtures/invalid_project/gems/payroll_calculator/calculator.rb":"350c85edfe24cd54b2479eda5266aae894b4c5eb6e9a3c0a0a1fb30e8c4d1d0f","tests/fixtures/invalid_project/ruby/app/models/bank_account.rb":"0832b2e95f90fa7ebd8dcbfd53c05618f091d38abf5dd238bd4d41645746160d","tests/fixtures/invalid_project/ruby/app/models/blockchain.rb":"9ffaa2304d02b8d40050017b62edbaf61b8600dc0b1a2c5b7f069b7c55c30616","tests/fixtures/invalid_project/ruby/app/models/payroll.rb":"7f48f8730fd738a2760ee81be8a6028cccb3c6c38be0433c7fd81a4bdd631b60","tests/fixtures/invalid_project/ruby/app/payments/nacha.rb":"c5ced2841986f3d48be0a132185a39dbc327715064574887303a25cf02aaad4d","tests/fixtures/invalid_project/ruby/app/services/.codeowner":"53fe8dfb6d9e1b03219adddcc3ffb741557dd579dc653d026097c463def4a8fe","tests/fixtures/invalid_project/ruby/app/services/multi_owned.rb":"42b340c67ad95016eb7e852abe37b39f7a0b5be41f897f972e05d2e581d6c30f","tests/fixtures/invalid_project/ruby/app/unowned.rb":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","tests/fixtures/invalid_project/ruby/packages/payroll_flow/package.yml":"c1a08643821d02a57131870e8b6325f864db0687c5583f3d0872ada9d004072a","tests/fixtures/multiple-directory-owners/.github/CODEOWNERS":"957d160c3d00eb530f7ebb34cc9b7c61934d108eb52ce3cb03b6d9978fad86df","tests/fixtures/multiple-directory-owners/app/consumers/.codeowner":"842c88863910f6eab22edcbdbe9f0f4dd96c6b46c711bd40277b36b8178943c4","tests/fixtures/multiple-directory-owners/app/consumers/deep/nesting/nestdir/deep_file.rb":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","tests/fixtures/multiple-directory-owners/app/consumers/one_owner.rb":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","tests/fixtures/multiple-directory-owners/app/services/.codeowner":"3eae1599bb7f187b86d6427942d172ba8dd7ee5962aab03e0839ad9d59c37eb0","tests/fixtures/multiple-directory-owners/app/services/exciting/.codeowner":"842c88863910f6eab22edcbdbe9f0f4dd96c6b46c711bd40277b36b8178943c4","tests/fixtures/multiple-directory-owners/app/services/exciting/some_other_file.rb":"c0cf1112663d326ba9b47fbcf961e98170c32e9287d5af574c35b3350fb3c59a","tests/fixtures/multiple-directory-owners/config/code_ownership.yml":"fc9679f69ed09090e104341c7d6635c050e81472370acb504a3a42040a48332f","tests/fixtures/multiple-directory-owners/config/teams/bar.yml":"253d9d9cb7f5b5cb81af19d8af7631b510d0f4ad59b32465c5e26bd78886f9c6","tests/fixtures/multiple-directory-owners/config/teams/foo.yml":"d4ba6d61c16e4227681f13013775e3d117e0cee4631714849405c47ff36ecbfc","tests/fixtures/valid_project/.github/CODEOWNERS":"c8e86365c334ac5aeb1d41a579a7f20236b79b4c0cd0e706b8d48a91fb31b641","tests/fixtures/valid_project/.ignore":"68f30767f9c87c700952d52d9d39693e1a98efb6c07a0b9216fbc87d173ebb5e","tests/fixtures/valid_project/config/code_ownership.yml":"a336d56f500203588631ee9f48101f99adf476a3fd786d74cb30e6c9060ae3b3","tests/fixtures/valid_project/config/teams/payments.yml":"7c4edf4ab7db59c1ef13ef4e7aa498c8e8c23c19427ebebe4f43bca003619f2a","tests/fixtures/valid_project/config/teams/payroll.yml":"f3a85ce896837c2a9d38c2d23a600b1fd72f3bf8c4c65c12719ce942b3b17375","tests/fixtures/valid_project/config/teams/ux.yml":"918e96ab1b5c67c99b30b8e984c2cef8fa956b1c83802a76923b3f448bcdec62","tests/fixtures/valid_project/gems/payroll_calculator/calculator.rb":"991056cdd4824eea15055f9c055c22fb3281cc21cffbca94724b330b8a450e59","tests/fixtures/valid_project/gems/pets/dog.rb":"ed2553442d1646af1c421a7a04b92269612efbc5ec0ccb32930e27ceac65b2ea","tests/fixtures/valid_project/javascript/packages/PayrollFlow/index.tsx":"b78b223d3bdb83af6ef7f78bd305f6e18f11abf961ded3266f7bebdb9eb00686","tests/fixtures/valid_project/javascript/packages/PayrollFlow/package.json":"271a0bb50784a65df66fd5db7167d1dd67430c774c207eda58a96974dbafc408","tests/fixtures/valid_project/javascript/packages/items/(special)/.codeowner":"632e1c5839411a1009ef268e992c4b183cb1d5381b198eac0f8323213a61ed82","tests/fixtures/valid_project/javascript/packages/items/(special)/pay.ts":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","tests/fixtures/valid_project/javascript/packages/items/.codeowner":"53fe8dfb6d9e1b03219adddcc3ffb741557dd579dc653d026097c463def4a8fe","tests/fixtures/valid_project/javascript/packages/items/item.ts":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","tests/fixtures/valid_project/ruby/app/models/bank_account.rb":"0832b2e95f90fa7ebd8dcbfd53c05618f091d38abf5dd238bd4d41645746160d","tests/fixtures/valid_project/ruby/app/models/payroll.rb":"7f48f8730fd738a2760ee81be8a6028cccb3c6c38be0433c7fd81a4bdd631b60","tests/fixtures/valid_project/ruby/app/payments/foo/.codeowner":"53fe8dfb6d9e1b03219adddcc3ffb741557dd579dc653d026097c463def4a8fe","tests/fixtures/valid_project/ruby/app/payments/foo/ownedby_payroll.rb":"705296c5ae6b3c1efdb410c952095b6efba3f9edd0af1b42d20e6ba217b1744d","tests/fixtures/valid_project/ruby/app/payments/nacha.rb":"c5ced2841986f3d48be0a132185a39dbc327715064574887303a25cf02aaad4d","tests/fixtures/valid_project/ruby/app/payroll/.codeowner":"b58412ec67186588652c88a6c71fd7499e27984e1e587e137426bed8ddd53173","tests/fixtures/valid_project/ruby/app/payroll/payroll.rb":"ff6cb08457469043bd93b4fdbcecc37a29a506641ab486f9cafd319125ff00ca","tests/fixtures/valid_project/ruby/ignored_files/git_ignored.rb":"fb333306f05e93ff821123b1c09d264c5f37f1bd739156d91167789795704671","tests/fixtures/valid_project/ruby/packages/payroll_flow/package.yml":"c1a08643821d02a57131870e8b6325f864db0687c5583f3d0872ada9d004072a","tests/fixtures/valid_project/should_be_ignored/an_ignored_file.rb":"bd5ce0746f05ded73148e184b36741f3846e0af7409b42c16abae54a9220113a","tests/git_stage_test.rs":"b2cc47863571cfcf89da83d7caa5643932a419c330e676faa2eb66860f352272","tests/invalid_project_structure_test.rs":"af048ddfa756bc2e187ea885ea533a4b67907c5bcc531a082ffceca0728649b4","tests/invalid_project_test.rs":"e62b8129d1af0a5a66ced9547dd1e67f17c42980e2b975205909a4530ec3b8db","tests/multiple_directory_owners_test.rs":"bee053097f88481454ad38349e23e716bc44874348b5f4b67687004c9cb9e52f","tests/valid_project_test.rs":"17df92c5a07c8f1c2b1f243a43927c7fe3e0cba3573996fbb77bb7a19aecab62","tmp/.gitkeep":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},"package":null}
|
@@ -37,6 +37,10 @@ path = "src/bin/compare_for_file.rs"
|
|
37
37
|
name = "cache_test"
|
38
38
|
path = "tests/cache_test.rs"
|
39
39
|
|
40
|
+
[[test]]
|
41
|
+
name = "git_stage_test"
|
42
|
+
path = "tests/git_stage_test.rs"
|
43
|
+
|
40
44
|
[[test]]
|
41
45
|
name = "invalid_project_structure_test"
|
42
46
|
path = "tests/invalid_project_structure_test.rs"
|
@@ -26,7 +26,10 @@ enum Command {
|
|
26
26
|
about = "Generate the CODEOWNERS file and save it to '--codeowners-file-path'.",
|
27
27
|
visible_alias = "g"
|
28
28
|
)]
|
29
|
-
Generate
|
29
|
+
Generate {
|
30
|
+
#[arg(long, short, default_value = "false", help = "Skip staging the CODEOWNERS file")]
|
31
|
+
skip_stage: bool,
|
32
|
+
},
|
30
33
|
|
31
34
|
#[clap(
|
32
35
|
about = "Validate the validity of the CODEOWNERS file. A validation failure will exit with a failure code and a detailed output of the validation errors.",
|
@@ -35,7 +38,10 @@ enum Command {
|
|
35
38
|
Validate,
|
36
39
|
|
37
40
|
#[clap(about = "Chains both `generate` and `validate` commands.", visible_alias = "gv")]
|
38
|
-
GenerateAndValidate
|
41
|
+
GenerateAndValidate {
|
42
|
+
#[arg(long, short, default_value = "false", help = "Skip staging the CODEOWNERS file")]
|
43
|
+
skip_stage: bool,
|
44
|
+
},
|
39
45
|
|
40
46
|
#[clap(about = "Delete the cache file.", visible_alias = "d")]
|
41
47
|
DeleteCache,
|
@@ -101,8 +107,8 @@ pub fn cli() -> Result<RunResult, RunnerError> {
|
|
101
107
|
|
102
108
|
let runner_result = match args.command {
|
103
109
|
Command::Validate => runner::validate(&run_config, vec![]),
|
104
|
-
Command::Generate => runner::generate(&run_config),
|
105
|
-
Command::GenerateAndValidate => runner::generate_and_validate(&run_config, vec![]),
|
110
|
+
Command::Generate { skip_stage } => runner::generate(&run_config, !skip_stage),
|
111
|
+
Command::GenerateAndValidate { skip_stage } => runner::generate_and_validate(&run_config, vec![], !skip_stage),
|
106
112
|
Command::ForFile { name, fast } => runner::for_file(&run_config, &name, fast),
|
107
113
|
Command::ForTeam { name } => runner::for_team(&run_config, &name),
|
108
114
|
Command::DeleteCache => runner::delete_cache(&run_config),
|
@@ -6,10 +6,8 @@ use std::{
|
|
6
6
|
|
7
7
|
use fast_glob::glob_match;
|
8
8
|
use glob::glob;
|
9
|
-
use lazy_static::lazy_static;
|
10
|
-
use regex::Regex;
|
11
9
|
|
12
|
-
use crate::{config::Config, project::Team};
|
10
|
+
use crate::{config::Config, project::Team, project_file_builder::build_project_file_without_cache};
|
13
11
|
|
14
12
|
use super::{FileOwner, mapper::Source};
|
15
13
|
|
@@ -106,7 +104,7 @@ pub fn find_file_owners(project_root: &Path, config: &Config, file_path: &Path)
|
|
106
104
|
}
|
107
105
|
|
108
106
|
fn build_teams_by_name_map(teams: &[Team]) -> HashMap<String, Team> {
|
109
|
-
let mut map = HashMap::
|
107
|
+
let mut map = HashMap::with_capacity(teams.len() * 2);
|
110
108
|
for team in teams {
|
111
109
|
map.insert(team.name.clone(), team.clone());
|
112
110
|
map.insert(team.github_team.clone(), team.clone());
|
@@ -117,13 +115,13 @@ fn build_teams_by_name_map(teams: &[Team]) -> HashMap<String, Team> {
|
|
117
115
|
fn load_teams(project_root: &Path, team_file_globs: &[String]) -> std::result::Result<Vec<Team>, String> {
|
118
116
|
let mut teams: Vec<Team> = Vec::new();
|
119
117
|
for glob_str in team_file_globs {
|
120
|
-
let absolute_glob =
|
118
|
+
let absolute_glob = project_root.join(glob_str).to_string_lossy().into_owned();
|
121
119
|
let paths = glob(&absolute_glob).map_err(|e| e.to_string())?;
|
122
120
|
for path in paths.flatten() {
|
123
121
|
match Team::from_team_file_path(path.clone()) {
|
124
122
|
Ok(team) => teams.push(team),
|
125
123
|
Err(e) => {
|
126
|
-
eprintln!("Error parsing team file: {}", e);
|
124
|
+
eprintln!("Error parsing team file: {}, path: {}", e, path.display());
|
127
125
|
continue;
|
128
126
|
}
|
129
127
|
}
|
@@ -132,30 +130,14 @@ fn load_teams(project_root: &Path, team_file_globs: &[String]) -> std::result::R
|
|
132
130
|
Ok(teams)
|
133
131
|
}
|
134
132
|
|
135
|
-
|
136
|
-
// Allow optional leading whitespace before the comment marker
|
137
|
-
static ref TOP_OF_FILE_TEAM_AT_REGEX: Option<Regex> = Regex::new(r#"^\s*(?:#|//)\s*@team\s+(.+)$"#).ok();
|
138
|
-
static ref TOP_OF_FILE_TEAM_COLON_REGEX: Option<Regex> = Regex::new(r#"(?i)^\s*(?:#|//)\s*team\s*:\s*(.+)$"#).ok();
|
139
|
-
}
|
133
|
+
// no regex: parse cheaply with ASCII-aware checks
|
140
134
|
|
141
135
|
fn read_top_of_file_team(path: &Path) -> Option<String> {
|
142
|
-
let
|
143
|
-
let
|
144
|
-
|
145
|
-
if let Some(re) = &*TOP_OF_FILE_TEAM_AT_REGEX {
|
146
|
-
if let Some(cap) = re.captures(line) {
|
147
|
-
if let Some(m) = cap.get(1) {
|
148
|
-
return Some(m.as_str().to_string());
|
149
|
-
}
|
150
|
-
}
|
151
|
-
}
|
152
|
-
if let Some(re) = &*TOP_OF_FILE_TEAM_COLON_REGEX {
|
153
|
-
if let Some(cap) = re.captures(line) {
|
154
|
-
if let Some(m) = cap.get(1) {
|
155
|
-
return Some(m.as_str().to_string());
|
156
|
-
}
|
157
|
-
}
|
136
|
+
let project_file = build_project_file_without_cache(&path.to_path_buf());
|
137
|
+
if let Some(owner) = project_file.owner {
|
138
|
+
return Some(owner);
|
158
139
|
}
|
140
|
+
|
159
141
|
None
|
160
142
|
}
|
161
143
|
|
@@ -167,34 +149,32 @@ fn most_specific_directory_owner(
|
|
167
149
|
let mut current = project_root.join(relative_file_path);
|
168
150
|
let mut best: Option<(String, Source)> = None;
|
169
151
|
loop {
|
170
|
-
|
171
|
-
|
172
|
-
|
152
|
+
if !current.pop() {
|
153
|
+
break;
|
154
|
+
}
|
155
|
+
let codeowner_path = current.join(".codeowner");
|
173
156
|
if let Ok(owner_str) = fs::read_to_string(&codeowner_path) {
|
174
157
|
let owner = owner_str.trim();
|
175
158
|
if let Some(team) = teams_by_name.get(owner) {
|
176
|
-
let relative_dir =
|
159
|
+
let relative_dir = current
|
177
160
|
.strip_prefix(project_root)
|
178
|
-
.unwrap_or(
|
161
|
+
.unwrap_or(current.as_path())
|
179
162
|
.to_string_lossy()
|
180
163
|
.to_string();
|
181
164
|
let candidate = (team.name.clone(), Source::Directory(relative_dir));
|
182
165
|
match &best {
|
183
166
|
None => best = Some(candidate),
|
184
167
|
Some((_, existing_source)) => {
|
185
|
-
|
186
|
-
let candidate_len = source_directory_depth(&candidate.1);
|
187
|
-
if candidate_len > existing_len {
|
168
|
+
if candidate.1.len() > existing_source.len() {
|
188
169
|
best = Some(candidate);
|
189
170
|
}
|
190
171
|
}
|
191
172
|
}
|
192
173
|
}
|
193
174
|
}
|
194
|
-
if
|
175
|
+
if current == project_root {
|
195
176
|
break;
|
196
177
|
}
|
197
|
-
current = parent.clone();
|
198
178
|
}
|
199
179
|
best
|
200
180
|
}
|
@@ -207,17 +187,18 @@ fn nearest_package_owner(
|
|
207
187
|
) -> Option<(String, Source)> {
|
208
188
|
let mut current = project_root.join(relative_file_path);
|
209
189
|
loop {
|
210
|
-
|
211
|
-
|
212
|
-
|
190
|
+
if !current.pop() {
|
191
|
+
break;
|
192
|
+
}
|
193
|
+
let parent_rel = current.strip_prefix(project_root).unwrap_or(current.as_path());
|
213
194
|
if let Some(rel_str) = parent_rel.to_str() {
|
214
195
|
if glob_list_matches(rel_str, &config.ruby_package_paths) {
|
215
|
-
let pkg_yml =
|
196
|
+
let pkg_yml = current.join("package.yml");
|
216
197
|
if pkg_yml.exists() {
|
217
198
|
if let Ok(owner) = read_ruby_package_owner(&pkg_yml) {
|
218
199
|
if let Some(team) = teams_by_name.get(&owner) {
|
219
200
|
let package_path = parent_rel.join("package.yml");
|
220
|
-
let package_glob = format!("{}/**/**"
|
201
|
+
let package_glob = format!("{rel_str}/**/**");
|
221
202
|
return Some((
|
222
203
|
team.name.clone(),
|
223
204
|
Source::Package(package_path.to_string_lossy().to_string(), package_glob),
|
@@ -227,12 +208,12 @@ fn nearest_package_owner(
|
|
227
208
|
}
|
228
209
|
}
|
229
210
|
if glob_list_matches(rel_str, &config.javascript_package_paths) {
|
230
|
-
let pkg_json =
|
211
|
+
let pkg_json = current.join("package.json");
|
231
212
|
if pkg_json.exists() {
|
232
213
|
if let Ok(owner) = read_js_package_owner(&pkg_json) {
|
233
214
|
if let Some(team) = teams_by_name.get(&owner) {
|
234
215
|
let package_path = parent_rel.join("package.json");
|
235
|
-
let package_glob = format!("{}/**/**"
|
216
|
+
let package_glob = format!("{rel_str}/**/**");
|
236
217
|
return Some((
|
237
218
|
team.name.clone(),
|
238
219
|
Source::Package(package_path.to_string_lossy().to_string(), package_glob),
|
@@ -242,20 +223,14 @@ fn nearest_package_owner(
|
|
242
223
|
}
|
243
224
|
}
|
244
225
|
}
|
245
|
-
if
|
226
|
+
if current == project_root {
|
246
227
|
break;
|
247
228
|
}
|
248
|
-
current = parent;
|
249
229
|
}
|
250
230
|
None
|
251
231
|
}
|
252
232
|
|
253
|
-
|
254
|
-
match source {
|
255
|
-
Source::Directory(path) => path.matches('/').count(),
|
256
|
-
_ => 0,
|
257
|
-
}
|
258
|
-
}
|
233
|
+
// removed: use `Source::len()` instead
|
259
234
|
|
260
235
|
fn glob_list_matches(path: &str, globs: &[String]) -> bool {
|
261
236
|
globs.iter().any(|g| glob_match(g, path))
|
@@ -311,3 +286,139 @@ fn source_priority(source: &Source) -> u8 {
|
|
311
286
|
Source::TeamYml => 5,
|
312
287
|
}
|
313
288
|
}
|
289
|
+
|
290
|
+
#[cfg(test)]
|
291
|
+
mod tests {
|
292
|
+
use super::*;
|
293
|
+
use crate::project::Team;
|
294
|
+
use std::collections::HashMap;
|
295
|
+
use tempfile::tempdir;
|
296
|
+
|
297
|
+
fn build_config_for_temp(frontend_glob: &str, ruby_glob: &str, vendored_path: &str) -> crate::config::Config {
|
298
|
+
crate::config::Config {
|
299
|
+
owned_globs: vec!["**/*".to_string()],
|
300
|
+
ruby_package_paths: vec![ruby_glob.to_string()],
|
301
|
+
javascript_package_paths: vec![frontend_glob.to_string()],
|
302
|
+
team_file_glob: vec!["config/teams/**/*.yml".to_string()],
|
303
|
+
unowned_globs: vec![],
|
304
|
+
vendored_gems_path: vendored_path.to_string(),
|
305
|
+
cache_directory: "tmp/cache/codeowners".to_string(),
|
306
|
+
}
|
307
|
+
}
|
308
|
+
|
309
|
+
fn team_named(name: &str) -> Team {
|
310
|
+
Team {
|
311
|
+
path: Path::new("config/teams/foo.yml").to_path_buf(),
|
312
|
+
name: name.to_string(),
|
313
|
+
github_team: format!("@{}Team", name),
|
314
|
+
owned_globs: vec![],
|
315
|
+
subtracted_globs: vec![],
|
316
|
+
owned_gems: vec![],
|
317
|
+
avoid_ownership: false,
|
318
|
+
}
|
319
|
+
}
|
320
|
+
|
321
|
+
#[test]
|
322
|
+
fn test_read_top_of_file_team_parses_at_and_colon_forms() {
|
323
|
+
let td = tempdir().unwrap();
|
324
|
+
|
325
|
+
// @team form
|
326
|
+
let file_at = td.path().join("at_form.rb");
|
327
|
+
std::fs::write(&file_at, "# @team Payroll\nputs 'x'\n").unwrap();
|
328
|
+
assert_eq!(read_top_of_file_team(&file_at), Some("Payroll".to_string()));
|
329
|
+
}
|
330
|
+
|
331
|
+
#[test]
|
332
|
+
fn test_most_specific_directory_owner_prefers_deeper() {
|
333
|
+
let td = tempdir().unwrap();
|
334
|
+
let project_root = td.path();
|
335
|
+
|
336
|
+
// Build directories
|
337
|
+
let deep_dir = project_root.join("a/b/c");
|
338
|
+
std::fs::create_dir_all(&deep_dir).unwrap();
|
339
|
+
let mid_dir = project_root.join("a/b");
|
340
|
+
let top_dir = project_root.join("a");
|
341
|
+
|
342
|
+
// Write .codeowner files
|
343
|
+
std::fs::write(top_dir.join(".codeowner"), "TopTeam").unwrap();
|
344
|
+
std::fs::write(mid_dir.join(".codeowner"), "MidTeam").unwrap();
|
345
|
+
std::fs::write(deep_dir.join(".codeowner"), "DeepTeam").unwrap();
|
346
|
+
|
347
|
+
// Build teams_by_name
|
348
|
+
let mut tbn: HashMap<String, Team> = HashMap::new();
|
349
|
+
for name in ["TopTeam", "MidTeam", "DeepTeam"] {
|
350
|
+
let t = team_named(name);
|
351
|
+
tbn.insert(t.name.clone(), t);
|
352
|
+
}
|
353
|
+
|
354
|
+
let rel_file = Path::new("a/b/c/file.rb");
|
355
|
+
let result = most_specific_directory_owner(project_root, rel_file, &tbn).unwrap();
|
356
|
+
match result.1 {
|
357
|
+
Source::Directory(path) => {
|
358
|
+
assert!(path.ends_with("a/b/c"), "expected deepest directory, got {}", path);
|
359
|
+
}
|
360
|
+
_ => panic!("expected Directory source"),
|
361
|
+
}
|
362
|
+
assert_eq!(result.0, "DeepTeam");
|
363
|
+
}
|
364
|
+
|
365
|
+
#[test]
|
366
|
+
fn test_nearest_package_owner_ruby_and_js() {
|
367
|
+
let td = tempdir().unwrap();
|
368
|
+
let project_root = td.path();
|
369
|
+
let config = build_config_for_temp("frontend/**/*", "packs/**/*", "vendored");
|
370
|
+
|
371
|
+
// Ruby package
|
372
|
+
let ruby_pkg = project_root.join("packs/payroll");
|
373
|
+
std::fs::create_dir_all(&ruby_pkg).unwrap();
|
374
|
+
std::fs::write(ruby_pkg.join("package.yml"), "---\nowner: Payroll\n").unwrap();
|
375
|
+
|
376
|
+
// JS package
|
377
|
+
let js_pkg = project_root.join("frontend/flow");
|
378
|
+
std::fs::create_dir_all(&js_pkg).unwrap();
|
379
|
+
std::fs::write(js_pkg.join("package.json"), r#"{"metadata": {"owner": "UX"}}"#).unwrap();
|
380
|
+
|
381
|
+
// Teams map
|
382
|
+
let mut tbn: HashMap<String, Team> = HashMap::new();
|
383
|
+
for name in ["Payroll", "UX"] {
|
384
|
+
let t = team_named(name);
|
385
|
+
tbn.insert(t.name.clone(), t);
|
386
|
+
}
|
387
|
+
|
388
|
+
// Ruby nearest
|
389
|
+
let rel_ruby = Path::new("packs/payroll/app/models/thing.rb");
|
390
|
+
let ruby_owner = nearest_package_owner(project_root, rel_ruby, &config, &tbn).unwrap();
|
391
|
+
assert_eq!(ruby_owner.0, "Payroll");
|
392
|
+
match ruby_owner.1 {
|
393
|
+
Source::Package(pkg_path, glob) => {
|
394
|
+
assert!(pkg_path.ends_with("packs/payroll/package.yml"));
|
395
|
+
assert_eq!(glob, "packs/payroll/**/**");
|
396
|
+
}
|
397
|
+
_ => panic!("expected Package source for ruby"),
|
398
|
+
}
|
399
|
+
|
400
|
+
// JS nearest
|
401
|
+
let rel_js = Path::new("frontend/flow/src/index.ts");
|
402
|
+
let js_owner = nearest_package_owner(project_root, rel_js, &config, &tbn).unwrap();
|
403
|
+
assert_eq!(js_owner.0, "UX");
|
404
|
+
match js_owner.1 {
|
405
|
+
Source::Package(pkg_path, glob) => {
|
406
|
+
assert!(pkg_path.ends_with("frontend/flow/package.json"));
|
407
|
+
assert_eq!(glob, "frontend/flow/**/**");
|
408
|
+
}
|
409
|
+
_ => panic!("expected Package source for js"),
|
410
|
+
}
|
411
|
+
}
|
412
|
+
|
413
|
+
#[test]
|
414
|
+
fn test_vendored_gem_owner() {
|
415
|
+
let config = build_config_for_temp("frontend/**/*", "packs/**/*", "vendored");
|
416
|
+
let mut teams: Vec<Team> = vec![team_named("Payroll")];
|
417
|
+
teams[0].owned_gems = vec!["awesome_gem".to_string()];
|
418
|
+
|
419
|
+
let path = Path::new("vendored/awesome_gem/lib/a.rb");
|
420
|
+
let result = vendored_gem_owner(path, &config, &teams).unwrap();
|
421
|
+
assert_eq!(result.0, "Payroll");
|
422
|
+
matches!(result.1, Source::TeamGem);
|
423
|
+
}
|
424
|
+
}
|
@@ -178,9 +178,7 @@ impl Project {
|
|
178
178
|
}
|
179
179
|
|
180
180
|
pub fn relative_path<'a>(&'a self, absolute_path: &'a Path) -> &'a Path {
|
181
|
-
absolute_path
|
182
|
-
.strip_prefix(&self.base_path)
|
183
|
-
.expect("Could not generate relative path")
|
181
|
+
absolute_path.strip_prefix(&self.base_path).unwrap_or(absolute_path)
|
184
182
|
}
|
185
183
|
|
186
184
|
pub fn get_team(&self, name: &str) -> Option<Team> {
|
@@ -197,3 +195,35 @@ impl Project {
|
|
197
195
|
result
|
198
196
|
}
|
199
197
|
}
|
198
|
+
|
199
|
+
#[cfg(test)]
|
200
|
+
mod tests {
|
201
|
+
use super::*;
|
202
|
+
|
203
|
+
#[test]
|
204
|
+
fn test_vendored_gem_by_name_maps_all_gems() {
|
205
|
+
let vg1 = VendoredGem {
|
206
|
+
path: PathBuf::from("vendored/a"),
|
207
|
+
name: "a".to_string(),
|
208
|
+
};
|
209
|
+
let vg2 = VendoredGem {
|
210
|
+
path: PathBuf::from("vendored/b"),
|
211
|
+
name: "b".to_string(),
|
212
|
+
};
|
213
|
+
let project = Project {
|
214
|
+
base_path: PathBuf::from("."),
|
215
|
+
files: vec![],
|
216
|
+
packages: vec![],
|
217
|
+
vendored_gems: vec![vg1.clone(), vg2.clone()],
|
218
|
+
teams: vec![],
|
219
|
+
codeowners_file_path: PathBuf::from(".github/CODEOWNERS"),
|
220
|
+
directory_codeowner_files: vec![],
|
221
|
+
teams_by_name: HashMap::new(),
|
222
|
+
};
|
223
|
+
|
224
|
+
let map = project.vendored_gem_by_name();
|
225
|
+
assert_eq!(map.len(), 2);
|
226
|
+
assert_eq!(map.get("a").unwrap().name, vg1.name);
|
227
|
+
assert_eq!(map.get("b").unwrap().name, vg2.name);
|
228
|
+
}
|
229
|
+
}
|
@@ -1,11 +1,12 @@
|
|
1
1
|
use std::{
|
2
2
|
fs::File,
|
3
3
|
path::{Path, PathBuf},
|
4
|
+
sync::{Arc, Mutex},
|
4
5
|
};
|
5
6
|
|
6
7
|
use error_stack::{Result, ResultExt};
|
7
8
|
use fast_glob::glob_match;
|
8
|
-
use ignore::WalkBuilder;
|
9
|
+
use ignore::{WalkBuilder, WalkParallel, WalkState};
|
9
10
|
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
10
11
|
use tracing::{instrument, warn};
|
11
12
|
|
@@ -53,12 +54,29 @@ impl<'a> ProjectBuilder<'a> {
|
|
53
54
|
let mut entry_types = Vec::with_capacity(INITIAL_VECTOR_CAPACITY);
|
54
55
|
let mut builder = WalkBuilder::new(&self.base_path);
|
55
56
|
builder.hidden(false);
|
56
|
-
let
|
57
|
+
let walk_parallel: WalkParallel = builder.build_parallel();
|
57
58
|
|
58
|
-
|
59
|
-
|
59
|
+
let collected = Arc::new(Mutex::new(Vec::with_capacity(INITIAL_VECTOR_CAPACITY)));
|
60
|
+
let collected_for_threads = Arc::clone(&collected);
|
61
|
+
|
62
|
+
walk_parallel.run(move || {
|
63
|
+
let collected = Arc::clone(&collected_for_threads);
|
64
|
+
Box::new(move |res| {
|
65
|
+
if let Ok(entry) = res {
|
66
|
+
if let Ok(mut v) = collected.lock() {
|
67
|
+
v.push(entry);
|
68
|
+
}
|
69
|
+
}
|
70
|
+
WalkState::Continue
|
71
|
+
})
|
72
|
+
});
|
73
|
+
|
74
|
+
// Process sequentially with &mut self
|
75
|
+
let collected_entries = Arc::try_unwrap(collected).unwrap().into_inner().unwrap();
|
76
|
+
for entry in collected_entries {
|
60
77
|
entry_types.push(self.build_entry_type(entry)?);
|
61
78
|
}
|
79
|
+
|
62
80
|
self.build_project_from_entry_types(entry_types)
|
63
81
|
}
|
64
82
|
|
@@ -244,14 +262,11 @@ mod tests {
|
|
244
262
|
|
245
263
|
#[test]
|
246
264
|
fn test_matches_globs() {
|
247
|
-
// should fail because hidden directories are ignored by glob patterns unless explicitly included
|
248
265
|
assert!(matches_globs(Path::new("script/.eslintrc.js"), &[OWNED_GLOB.to_string()]));
|
249
266
|
}
|
250
267
|
|
251
268
|
#[test]
|
252
269
|
fn test_glob_match() {
|
253
|
-
// Exposes bug in glob-match https://github.com/devongovett/glob-match/issues/9
|
254
|
-
// should fail because hidden directories are ignored by glob patterns unless explicitly included
|
255
270
|
assert!(glob_match(OWNED_GLOB, "script/.eslintrc.js"));
|
256
271
|
}
|
257
272
|
}
|
@@ -47,7 +47,7 @@ impl<'a> ProjectFileBuilder<'a> {
|
|
47
47
|
}
|
48
48
|
}
|
49
49
|
|
50
|
-
fn build_project_file_without_cache(path: &PathBuf) -> ProjectFile {
|
50
|
+
pub(crate) fn build_project_file_without_cache(path: &PathBuf) -> ProjectFile {
|
51
51
|
let content = match std::fs::read_to_string(path) {
|
52
52
|
Ok(content) => content,
|
53
53
|
Err(_) => {
|
@@ -2,6 +2,7 @@ use core::fmt;
|
|
2
2
|
use std::{
|
3
3
|
fs::File,
|
4
4
|
path::{Path, PathBuf},
|
5
|
+
process::Command,
|
5
6
|
};
|
6
7
|
|
7
8
|
use error_stack::{Context, Result, ResultExt};
|
@@ -87,8 +88,7 @@ pub fn team_for_file_from_codeowners(run_config: &RunConfig, file_path: &str) ->
|
|
87
88
|
pub fn team_for_file(run_config: &RunConfig, file_path: &str) -> Result<Option<Team>, Error> {
|
88
89
|
let config = config_from_path(&run_config.config_path)?;
|
89
90
|
use crate::ownership::for_file_fast::find_file_owners;
|
90
|
-
let owners = find_file_owners(&run_config.project_root, &config, std::path::Path::new(file_path))
|
91
|
-
.map_err(Error::Io)?;
|
91
|
+
let owners = find_file_owners(&run_config.project_root, &config, std::path::Path::new(file_path)).map_err(Error::Io)?;
|
92
92
|
|
93
93
|
Ok(owners.first().map(|fo| fo.team.clone()))
|
94
94
|
}
|
@@ -145,12 +145,12 @@ pub fn validate(run_config: &RunConfig, _file_paths: Vec<String>) -> RunResult {
|
|
145
145
|
run_with_runner(run_config, |runner| runner.validate())
|
146
146
|
}
|
147
147
|
|
148
|
-
pub fn generate(run_config: &RunConfig) -> RunResult {
|
149
|
-
run_with_runner(run_config, |runner| runner.generate())
|
148
|
+
pub fn generate(run_config: &RunConfig, git_stage: bool) -> RunResult {
|
149
|
+
run_with_runner(run_config, |runner| runner.generate(git_stage))
|
150
150
|
}
|
151
151
|
|
152
|
-
pub fn generate_and_validate(run_config: &RunConfig, _file_paths: Vec<String
|
153
|
-
run_with_runner(run_config, |runner| runner.generate_and_validate())
|
152
|
+
pub fn generate_and_validate(run_config: &RunConfig, _file_paths: Vec<String>, git_stage: bool) -> RunResult {
|
153
|
+
run_with_runner(run_config, |runner| runner.generate_and_validate(git_stage))
|
154
154
|
}
|
155
155
|
|
156
156
|
pub fn delete_cache(run_config: &RunConfig) -> RunResult {
|
@@ -254,13 +254,18 @@ impl Runner {
|
|
254
254
|
}
|
255
255
|
}
|
256
256
|
|
257
|
-
pub fn generate(&self) -> RunResult {
|
257
|
+
pub fn generate(&self, git_stage: bool) -> RunResult {
|
258
258
|
let content = self.ownership.generate_file();
|
259
259
|
if let Some(parent) = &self.run_config.codeowners_file_path.parent() {
|
260
260
|
let _ = std::fs::create_dir_all(parent);
|
261
261
|
}
|
262
262
|
match std::fs::write(&self.run_config.codeowners_file_path, content) {
|
263
|
-
Ok(_) =>
|
263
|
+
Ok(_) => {
|
264
|
+
if git_stage {
|
265
|
+
self.git_stage();
|
266
|
+
}
|
267
|
+
RunResult::default()
|
268
|
+
}
|
264
269
|
Err(err) => RunResult {
|
265
270
|
io_errors: vec![err.to_string()],
|
266
271
|
..Default::default()
|
@@ -268,14 +273,22 @@ impl Runner {
|
|
268
273
|
}
|
269
274
|
}
|
270
275
|
|
271
|
-
pub fn generate_and_validate(&self) -> RunResult {
|
272
|
-
let run_result = self.generate();
|
276
|
+
pub fn generate_and_validate(&self, git_stage: bool) -> RunResult {
|
277
|
+
let run_result = self.generate(git_stage);
|
273
278
|
if run_result.has_errors() {
|
274
279
|
return run_result;
|
275
280
|
}
|
276
281
|
self.validate()
|
277
282
|
}
|
278
283
|
|
284
|
+
fn git_stage(&self) {
|
285
|
+
let _ = Command::new("git")
|
286
|
+
.arg("add")
|
287
|
+
.arg(&self.run_config.codeowners_file_path)
|
288
|
+
.current_dir(&self.run_config.project_root)
|
289
|
+
.output();
|
290
|
+
}
|
291
|
+
|
279
292
|
// TODO: remove this once we've verified the fast path is working
|
280
293
|
#[allow(dead_code)]
|
281
294
|
pub fn for_file(&self, file_path: &str) -> RunResult {
|
@@ -1,4 +1,9 @@
|
|
1
1
|
use std::fs;
|
2
|
+
use std::path::Path;
|
3
|
+
use std::process::Command;
|
4
|
+
|
5
|
+
use codeowners::runner::{self, RunConfig};
|
6
|
+
use tempfile::TempDir;
|
2
7
|
|
3
8
|
#[allow(dead_code)]
|
4
9
|
pub fn teardown() {
|
@@ -11,3 +16,98 @@ pub fn teardown() {
|
|
11
16
|
}
|
12
17
|
});
|
13
18
|
}
|
19
|
+
|
20
|
+
#[allow(dead_code)]
|
21
|
+
pub fn copy_dir_recursive(from: &Path, to: &Path) {
|
22
|
+
fs::create_dir_all(to).expect("failed to create destination root");
|
23
|
+
for entry in fs::read_dir(from).expect("failed to read source dir") {
|
24
|
+
let entry = entry.expect("failed to read dir entry");
|
25
|
+
let file_type = entry.file_type().expect("failed to read file type");
|
26
|
+
let src_path = entry.path();
|
27
|
+
let dest_path = to.join(entry.file_name());
|
28
|
+
if file_type.is_dir() {
|
29
|
+
copy_dir_recursive(&src_path, &dest_path);
|
30
|
+
} else if file_type.is_file() {
|
31
|
+
if let Some(parent) = dest_path.parent() {
|
32
|
+
fs::create_dir_all(parent).expect("failed to create parent dir");
|
33
|
+
}
|
34
|
+
fs::copy(&src_path, &dest_path).expect("failed to copy file");
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
#[allow(dead_code)]
|
40
|
+
pub fn init_git_repo(path: &Path) {
|
41
|
+
let status = Command::new("git")
|
42
|
+
.arg("init")
|
43
|
+
.current_dir(path)
|
44
|
+
.output()
|
45
|
+
.expect("failed to run git init");
|
46
|
+
assert!(
|
47
|
+
status.status.success(),
|
48
|
+
"git init failed: {}",
|
49
|
+
String::from_utf8_lossy(&status.stderr)
|
50
|
+
);
|
51
|
+
|
52
|
+
let _ = Command::new("git")
|
53
|
+
.arg("config")
|
54
|
+
.arg("user.email")
|
55
|
+
.arg("test@example.com")
|
56
|
+
.current_dir(path)
|
57
|
+
.output();
|
58
|
+
let _ = Command::new("git")
|
59
|
+
.arg("config")
|
60
|
+
.arg("user.name")
|
61
|
+
.arg("Test User")
|
62
|
+
.current_dir(path)
|
63
|
+
.output();
|
64
|
+
}
|
65
|
+
|
66
|
+
#[allow(dead_code)]
|
67
|
+
pub fn is_file_staged(repo_root: &Path, rel_path: &str) -> bool {
|
68
|
+
let output = Command::new("git")
|
69
|
+
.arg("diff")
|
70
|
+
.arg("--name-only")
|
71
|
+
.arg("--cached")
|
72
|
+
.current_dir(repo_root)
|
73
|
+
.output()
|
74
|
+
.expect("failed to run git diff --cached");
|
75
|
+
assert!(
|
76
|
+
output.status.success(),
|
77
|
+
"git diff failed: {}",
|
78
|
+
String::from_utf8_lossy(&output.stderr)
|
79
|
+
);
|
80
|
+
let stdout = String::from_utf8_lossy(&output.stdout);
|
81
|
+
stdout.lines().any(|line| line.trim() == rel_path)
|
82
|
+
}
|
83
|
+
|
84
|
+
#[allow(dead_code)]
|
85
|
+
pub fn build_run_config(project_root: &Path, codeowners_rel_path: &str) -> RunConfig {
|
86
|
+
let project_root = project_root.canonicalize().expect("failed to canonicalize project root");
|
87
|
+
let codeowners_file_path = project_root.join(codeowners_rel_path);
|
88
|
+
let config_path = project_root.join("config/code_ownership.yml");
|
89
|
+
RunConfig {
|
90
|
+
project_root,
|
91
|
+
codeowners_file_path,
|
92
|
+
config_path,
|
93
|
+
no_cache: true,
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
#[allow(dead_code)]
|
98
|
+
pub fn setup_fixture_repo(fixture_root: &Path) -> TempDir {
|
99
|
+
let temp_dir = tempfile::tempdir().expect("failed to create tempdir");
|
100
|
+
copy_dir_recursive(fixture_root, temp_dir.path());
|
101
|
+
init_git_repo(temp_dir.path());
|
102
|
+
temp_dir
|
103
|
+
}
|
104
|
+
|
105
|
+
#[allow(dead_code)]
|
106
|
+
pub fn assert_no_run_errors(result: &runner::RunResult) {
|
107
|
+
assert!(result.io_errors.is_empty(), "io_errors: {:?}", result.io_errors);
|
108
|
+
assert!(
|
109
|
+
result.validation_errors.is_empty(),
|
110
|
+
"validation_errors: {:?}",
|
111
|
+
result.validation_errors
|
112
|
+
);
|
113
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
use std::path::Path;
|
2
|
+
|
3
|
+
use codeowners::runner::{self, RunConfig};
|
4
|
+
|
5
|
+
mod common;
|
6
|
+
use common::{assert_no_run_errors, build_run_config, is_file_staged, setup_fixture_repo};
|
7
|
+
|
8
|
+
#[test]
|
9
|
+
fn test_generate_stages_codeowners() {
|
10
|
+
run_and_check(runner::generate, true, true);
|
11
|
+
}
|
12
|
+
|
13
|
+
#[test]
|
14
|
+
fn test_generate_and_validate_stages_codeowners() {
|
15
|
+
run_and_check(|rc, s| runner::generate_and_validate(rc, vec![], s), true, true);
|
16
|
+
}
|
17
|
+
|
18
|
+
#[test]
|
19
|
+
fn test_generate_does_not_stage_codeowners() {
|
20
|
+
run_and_check(runner::generate, false, false);
|
21
|
+
}
|
22
|
+
|
23
|
+
#[test]
|
24
|
+
fn test_generate_and_validate_does_not_stage_codeowners() {
|
25
|
+
run_and_check(|rc, s| runner::generate_and_validate(rc, vec![], s), false, false);
|
26
|
+
}
|
27
|
+
|
28
|
+
const FIXTURE: &str = "tests/fixtures/valid_project";
|
29
|
+
const CODEOWNERS_REL: &str = ".github/CODEOWNERS";
|
30
|
+
|
31
|
+
fn run_and_check<F>(func: F, stage: bool, expected_staged: bool)
|
32
|
+
where
|
33
|
+
F: FnOnce(&RunConfig, bool) -> runner::RunResult,
|
34
|
+
{
|
35
|
+
let temp_dir = setup_fixture_repo(Path::new(FIXTURE));
|
36
|
+
let run_config = build_run_config(temp_dir.path(), CODEOWNERS_REL);
|
37
|
+
|
38
|
+
let result = func(&run_config, stage);
|
39
|
+
assert_no_run_errors(&result);
|
40
|
+
|
41
|
+
assert!(run_config.codeowners_file_path.exists(), "CODEOWNERS file was not created");
|
42
|
+
let staged = is_file_staged(&run_config.project_root, CODEOWNERS_REL);
|
43
|
+
assert_eq!(staged, expected_staged, "unexpected staged state for CODEOWNERS");
|
44
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[package]
|
2
2
|
name = "fast_code_owners"
|
3
|
-
version = "0.1.
|
3
|
+
version = "0.1.1"
|
4
4
|
edition = "2024"
|
5
5
|
authors = ["Perry Hertler <perry.hertler@gusto.com>"]
|
6
6
|
publish = false
|
@@ -17,8 +17,7 @@ rb-sys = { version = "0.9.117", features = [
|
|
17
17
|
magnus = { version = "0.7.1" }
|
18
18
|
serde = { version = "1.0.219", features = ["derive"] }
|
19
19
|
serde_magnus = "0.9.0"
|
20
|
-
codeowners = { git = "https://github.com/rubyatscale/codeowners-rs.git", branch = "ph.fast-file
|
21
|
-
#codeowners = { path = "../../../codeowners-rs" }
|
20
|
+
codeowners = { git = "https://github.com/rubyatscale/codeowners-rs.git", branch = "ph.blazing-fast-for-file" }
|
22
21
|
|
23
22
|
[dev-dependencies]
|
24
23
|
rb-sys = { version = "0.9.117", features = [
|
@@ -11,6 +11,12 @@ pub struct Team {
|
|
11
11
|
pub team_config_yml: String,
|
12
12
|
}
|
13
13
|
|
14
|
+
fn for_team(team_name: String) -> Result<Value, Error> {
|
15
|
+
let run_config = build_run_config();
|
16
|
+
let team = runner::for_team(&run_config, &team_name);
|
17
|
+
validate_result(&team)
|
18
|
+
}
|
19
|
+
|
14
20
|
fn for_file(file_path: String) -> Result<Option<Value>, Error> {
|
15
21
|
let run_config = build_run_config();
|
16
22
|
|
@@ -37,9 +43,9 @@ fn validate() -> Result<Value, Error> {
|
|
37
43
|
validate_result(&run_result)
|
38
44
|
}
|
39
45
|
|
40
|
-
fn generate_and_validate() -> Result<Value, Error> {
|
46
|
+
fn generate_and_validate(skip_stage: bool) -> Result<Value, Error> {
|
41
47
|
let run_config = build_run_config();
|
42
|
-
let run_result = runner::generate_and_validate(&run_config, vec![]);
|
48
|
+
let run_result = runner::generate_and_validate(&run_config, vec![], skip_stage);
|
43
49
|
validate_result(&run_result)
|
44
50
|
}
|
45
51
|
|
@@ -79,8 +85,9 @@ fn build_run_config() -> RunConfig {
|
|
79
85
|
fn init(ruby: &Ruby) -> Result<(), Error> {
|
80
86
|
let module = ruby.define_module("RustCodeOwners")?;
|
81
87
|
module.define_singleton_method("for_file", function!(for_file, 1))?;
|
82
|
-
module.define_singleton_method("generate_and_validate", function!(generate_and_validate,
|
88
|
+
module.define_singleton_method("generate_and_validate", function!(generate_and_validate, 1))?;
|
83
89
|
module.define_singleton_method("validate", function!(validate, 0))?;
|
90
|
+
module.define_singleton_method("for_team", function!(for_team, 1))?;
|
84
91
|
|
85
92
|
Ok(())
|
86
93
|
}
|
data/lib/fast_code_owners/cli.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'optparse'
|
4
4
|
require 'pathname'
|
5
5
|
require 'fileutils'
|
6
|
+
require 'json'
|
6
7
|
|
7
8
|
module FastCodeOwners
|
8
9
|
class Cli
|
@@ -16,7 +17,7 @@ module FastCodeOwners
|
|
16
17
|
for_team(argv)
|
17
18
|
elsif [nil, 'help'].include?(command)
|
18
19
|
puts <<~USAGE
|
19
|
-
Usage: bin/
|
20
|
+
Usage: bin/codeowners <subcommand>
|
20
21
|
|
21
22
|
Subcommands:
|
22
23
|
validate - run all validations
|
@@ -25,7 +26,7 @@ module FastCodeOwners
|
|
25
26
|
help - display help information about code_ownership
|
26
27
|
USAGE
|
27
28
|
else
|
28
|
-
puts "'#{command}' is not a code_ownership command. See `bin/
|
29
|
+
puts "'#{command}' is not a code_ownership command. See `bin/codeowners help`."
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
@@ -33,7 +34,7 @@ module FastCodeOwners
|
|
33
34
|
options = {}
|
34
35
|
|
35
36
|
parser = OptionParser.new do |opts|
|
36
|
-
opts.banner = 'Usage: bin/
|
37
|
+
opts.banner = 'Usage: bin/codeowners validate [options]'
|
37
38
|
|
38
39
|
opts.on('--skip-autocorrect', 'Skip automatically correcting any errors, such as the .github/CODEOWNERS file') do
|
39
40
|
options[:skip_autocorrect] = true
|
@@ -63,17 +64,11 @@ module FastCodeOwners
|
|
63
64
|
nil
|
64
65
|
end
|
65
66
|
|
66
|
-
|
67
|
+
FastCodeOwners.validate!(
|
67
68
|
files: files,
|
68
69
|
autocorrect: !options[:skip_autocorrect],
|
69
70
|
stage_changes: !options[:skip_stage]
|
70
71
|
)
|
71
|
-
puts "cli result: #{result}"
|
72
|
-
if result.nil?
|
73
|
-
puts 'No errors found'
|
74
|
-
else
|
75
|
-
puts result
|
76
|
-
end
|
77
72
|
end
|
78
73
|
|
79
74
|
# For now, this just returns team ownership
|
@@ -87,7 +82,7 @@ module FastCodeOwners
|
|
87
82
|
files = argv.reject { |arg| arg.start_with?('--') }
|
88
83
|
|
89
84
|
parser = OptionParser.new do |opts|
|
90
|
-
opts.banner = 'Usage: bin/
|
85
|
+
opts.banner = 'Usage: bin/codeowners for_file [options]'
|
91
86
|
|
92
87
|
opts.on('--json', 'Output as JSON') do
|
93
88
|
options[:json] = true
|
@@ -102,7 +97,7 @@ module FastCodeOwners
|
|
102
97
|
parser.parse!(args)
|
103
98
|
|
104
99
|
if files.count != 1
|
105
|
-
raise 'Please pass in one file. Use `bin/
|
100
|
+
raise 'Please pass in one file. Use `bin/codeowners for_file --help` for more info'
|
106
101
|
end
|
107
102
|
|
108
103
|
team = FastCodeOwners.for_file(files.first)
|
@@ -127,7 +122,7 @@ module FastCodeOwners
|
|
127
122
|
|
128
123
|
def self.for_team(argv)
|
129
124
|
parser = OptionParser.new do |opts|
|
130
|
-
opts.banner = 'Usage: bin/
|
125
|
+
opts.banner = 'Usage: bin/codeowners for_team \'Team Name\''
|
131
126
|
|
132
127
|
opts.on('--help', 'Shows this prompt') do
|
133
128
|
puts opts
|
@@ -139,10 +134,10 @@ module FastCodeOwners
|
|
139
134
|
parser.parse!(args)
|
140
135
|
|
141
136
|
if teams.count != 1
|
142
|
-
raise 'Please pass in one team. Use `bin/
|
137
|
+
raise 'Please pass in one team. Use `bin/codeowners for_team --help` for more info'
|
143
138
|
end
|
144
139
|
|
145
|
-
puts FastCodeOwners.for_team(teams.first)
|
140
|
+
puts FastCodeOwners.for_team(teams.first).join("\n")
|
146
141
|
end
|
147
142
|
|
148
143
|
private_class_method :validate!
|
data/lib/fast_code_owners.rb
CHANGED
@@ -38,7 +38,7 @@ module FastCodeOwners
|
|
38
38
|
files: nil
|
39
39
|
)
|
40
40
|
if autocorrect
|
41
|
-
::RustCodeOwners.generate_and_validate
|
41
|
+
::RustCodeOwners.generate_and_validate(!stage_changes)
|
42
42
|
else
|
43
43
|
::RustCodeOwners.validate
|
44
44
|
end
|
@@ -73,6 +73,12 @@ module FastCodeOwners
|
|
73
73
|
TeamFinder.first_owned_file_for_backtrace(backtrace, excluded_teams: excluded_teams)
|
74
74
|
end
|
75
75
|
|
76
|
+
sig { params(team: T.any(CodeTeams::Team, String)).returns(String) }
|
77
|
+
def for_team(team)
|
78
|
+
team = T.must(CodeTeams.find(team)) if team.is_a?(String)
|
79
|
+
::RustCodeOwners.for_team(team.name)
|
80
|
+
end
|
81
|
+
|
76
82
|
sig { void }
|
77
83
|
def bust_cache!
|
78
84
|
FilePathTeamCache.bust_cache!
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fast_code_owners
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Perry Hertler
|
@@ -841,7 +841,6 @@ files:
|
|
841
841
|
- "./ext/cargo-vendor/codeowners-0.2.5/Cargo.lock"
|
842
842
|
- "./ext/cargo-vendor/codeowners-0.2.5/Cargo.toml"
|
843
843
|
- "./ext/cargo-vendor/codeowners-0.2.5/README.md"
|
844
|
-
- "./ext/cargo-vendor/codeowners-0.2.5/ai-prompts/for-file-optimization.md"
|
845
844
|
- "./ext/cargo-vendor/codeowners-0.2.5/dev/run_benchmarks.sh"
|
846
845
|
- "./ext/cargo-vendor/codeowners-0.2.5/rust-toolchain.toml"
|
847
846
|
- "./ext/cargo-vendor/codeowners-0.2.5/src/bin/compare_for_file.rs"
|
@@ -921,6 +920,7 @@ files:
|
|
921
920
|
- "./ext/cargo-vendor/codeowners-0.2.5/tests/fixtures/valid_project/ruby/ignored_files/git_ignored.rb"
|
922
921
|
- "./ext/cargo-vendor/codeowners-0.2.5/tests/fixtures/valid_project/ruby/packages/payroll_flow/package.yml"
|
923
922
|
- "./ext/cargo-vendor/codeowners-0.2.5/tests/fixtures/valid_project/should_be_ignored/an_ignored_file.rb"
|
923
|
+
- "./ext/cargo-vendor/codeowners-0.2.5/tests/git_stage_test.rs"
|
924
924
|
- "./ext/cargo-vendor/codeowners-0.2.5/tests/invalid_project_structure_test.rs"
|
925
925
|
- "./ext/cargo-vendor/codeowners-0.2.5/tests/invalid_project_test.rs"
|
926
926
|
- "./ext/cargo-vendor/codeowners-0.2.5/tests/multiple_directory_owners_test.rs"
|
@@ -1,21 +0,0 @@
|
|
1
|
-
## Re-architect `codeowners-rs`
|
2
|
-
|
3
|
-
The CLI behaviors live in `src/cli.rs`.
|
4
|
-
|
5
|
-
### What this tool does
|
6
|
-
- **Generate CODEOWNERS**: Builds `.github/CODEOWNERS` from multiple ownership sources. See mappers in `src/ownership/mapper/`.
|
7
|
-
- **Answer per-file ownership**: The `for-file` command returns the owner of a given file even if the checked-in `CODEOWNERS` is not up to date.
|
8
|
-
|
9
|
-
### Current implementations
|
10
|
-
- **Fast but potentially inaccurate**: `Runner::for_file_from_codeowners` (in `src/runner.rs`) parses the existing `CODEOWNERS` file to find a file’s owner. It’s very fast, but can be wrong if `CODEOWNERS` is stale.
|
11
|
-
- **Accurate but slow**: `Runner::for_file` is correct but can take up to ~4 seconds on large repositories because it effectively determines ownership for many files and then returns the single result needed.
|
12
|
-
|
13
|
-
Ownership resolution is complex because definitions often live in other files (packages, team configs, globs, etc.).
|
14
|
-
|
15
|
-
## Assignment
|
16
|
-
Implement a faster `for_file` that remains accurate.
|
17
|
-
|
18
|
-
### Requirements
|
19
|
-
- **Performance**: Under 1 second on large codebases (e.g., `$HOME/workspace/large`).
|
20
|
-
- **Correctness**: All existing tests must continue to pass.
|
21
|
-
- **Compatibility**: No changes to the external behavior of the `for-file` CLI command.
|