fast_code_owners 0.0.10 → 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 +1 -1
- 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 +162 -51
- data/ext/cargo-vendor/codeowners-0.2.5/src/project.rs +32 -0
- 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 +22 -8
- 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 +1 -1
- data/ext/fast_code_owners/src/lib.rs +3 -3
- data/lib/fast_code_owners/version.rb +1 -1
- data/lib/fast_code_owners.rb +1 -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",
|
@@ -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":"22ce2ecab5226a86eccf91cea0c2c679115ddaf764d81910ff001722c391f391","src/ownership/file_generator.rs":"be57f0fe6208bb810bafa59a3c782e8337d099d02728214e932ca2fe620ddb5d","src/ownership/file_owner_finder.rs":"104626da603ddcaccb217387f13141c81fb768b41cf8b7fbc5f65809f487231b","src/ownership/for_file_fast.rs":"4ed19d8dec2b2e1593502a54aaae244a9d600602069fddfe962efb547f788290","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":"c44eca21365a9202131ac11f51b38bb9946da9ba39d0ec2c923273107007cedc","src/project_builder.rs":"c048962bec93c1be487cb578c4f9f9254b02fed5f7a55fa2f6414558388c3ba4","src/project_file_builder.rs":"c93ecc878178281180e64a5b01e1934634f85502545ef6478f5252a7c1b67f62","src/runner.rs":"862f61fa9daf4d587f5bb0b5117dbecf7566b8d0ba669df1f12228f12c2d77c0","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,7 +115,7 @@ 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()) {
|
@@ -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
|
+
}
|
@@ -195,3 +195,35 @@ impl Project {
|
|
195
195
|
result
|
196
196
|
}
|
197
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};
|
@@ -144,12 +145,12 @@ pub fn validate(run_config: &RunConfig, _file_paths: Vec<String>) -> RunResult {
|
|
144
145
|
run_with_runner(run_config, |runner| runner.validate())
|
145
146
|
}
|
146
147
|
|
147
|
-
pub fn generate(run_config: &RunConfig) -> RunResult {
|
148
|
-
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))
|
149
150
|
}
|
150
151
|
|
151
|
-
pub fn generate_and_validate(run_config: &RunConfig, _file_paths: Vec<String
|
152
|
-
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))
|
153
154
|
}
|
154
155
|
|
155
156
|
pub fn delete_cache(run_config: &RunConfig) -> RunResult {
|
@@ -253,13 +254,18 @@ impl Runner {
|
|
253
254
|
}
|
254
255
|
}
|
255
256
|
|
256
|
-
pub fn generate(&self) -> RunResult {
|
257
|
+
pub fn generate(&self, git_stage: bool) -> RunResult {
|
257
258
|
let content = self.ownership.generate_file();
|
258
259
|
if let Some(parent) = &self.run_config.codeowners_file_path.parent() {
|
259
260
|
let _ = std::fs::create_dir_all(parent);
|
260
261
|
}
|
261
262
|
match std::fs::write(&self.run_config.codeowners_file_path, content) {
|
262
|
-
Ok(_) =>
|
263
|
+
Ok(_) => {
|
264
|
+
if git_stage {
|
265
|
+
self.git_stage();
|
266
|
+
}
|
267
|
+
RunResult::default()
|
268
|
+
}
|
263
269
|
Err(err) => RunResult {
|
264
270
|
io_errors: vec![err.to_string()],
|
265
271
|
..Default::default()
|
@@ -267,14 +273,22 @@ impl Runner {
|
|
267
273
|
}
|
268
274
|
}
|
269
275
|
|
270
|
-
pub fn generate_and_validate(&self) -> RunResult {
|
271
|
-
let run_result = self.generate();
|
276
|
+
pub fn generate_and_validate(&self, git_stage: bool) -> RunResult {
|
277
|
+
let run_result = self.generate(git_stage);
|
272
278
|
if run_result.has_errors() {
|
273
279
|
return run_result;
|
274
280
|
}
|
275
281
|
self.validate()
|
276
282
|
}
|
277
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
|
+
|
278
292
|
// TODO: remove this once we've verified the fast path is working
|
279
293
|
#[allow(dead_code)]
|
280
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
|
+
}
|
@@ -17,7 +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
|
20
|
+
codeowners = { git = "https://github.com/rubyatscale/codeowners-rs.git", branch = "ph.blazing-fast-for-file" }
|
21
21
|
|
22
22
|
[dev-dependencies]
|
23
23
|
rb-sys = { version = "0.9.117", features = [
|
@@ -43,9 +43,9 @@ fn validate() -> Result<Value, Error> {
|
|
43
43
|
validate_result(&run_result)
|
44
44
|
}
|
45
45
|
|
46
|
-
fn generate_and_validate() -> Result<Value, Error> {
|
46
|
+
fn generate_and_validate(skip_stage: bool) -> Result<Value, Error> {
|
47
47
|
let run_config = build_run_config();
|
48
|
-
let run_result = runner::generate_and_validate(&run_config, vec![]);
|
48
|
+
let run_result = runner::generate_and_validate(&run_config, vec![], skip_stage);
|
49
49
|
validate_result(&run_result)
|
50
50
|
}
|
51
51
|
|
@@ -85,7 +85,7 @@ fn build_run_config() -> RunConfig {
|
|
85
85
|
fn init(ruby: &Ruby) -> Result<(), Error> {
|
86
86
|
let module = ruby.define_module("RustCodeOwners")?;
|
87
87
|
module.define_singleton_method("for_file", function!(for_file, 1))?;
|
88
|
-
module.define_singleton_method("generate_and_validate", function!(generate_and_validate,
|
88
|
+
module.define_singleton_method("generate_and_validate", function!(generate_and_validate, 1))?;
|
89
89
|
module.define_singleton_method("validate", function!(validate, 0))?;
|
90
90
|
module.define_singleton_method("for_team", function!(for_team, 1))?;
|
91
91
|
|
data/lib/fast_code_owners.rb
CHANGED
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.
|