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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 01a553b43a3c6b6f9914e4b0968a5a00f2e3200f422582c998be9a6a075947fa
4
- data.tar.gz: d59458c2af972ae6a58d6465903057dac61c7b87b086ddeb3cd53546d6be625f
3
+ metadata.gz: 1380e7d12f10242ac7d11dc6f00a9bfdbf83e8d8157522fbd474ea80027b7902
4
+ data.tar.gz: 9ee1e96b93188cd9e788e479f19fdacde71854bdc18c238739531e3083224e44
5
5
  SHA512:
6
- metadata.gz: d523edd0cad8bca298a3841f6269a8a71ea90ab31e3a6794f0d41c0c402ea3e2b827dbbb00f395d9186a2066c2d2ff2db67a56a558aba50e0d23e3d62ab7d910
7
- data.tar.gz: 5090a71532ffa77ef5e6327d9c5ea23b0eb303f3d834468d82c91435d24ac3c26ae9e8082ca281f43b58e44163ac3d1698f5fb80f98535be4758a8483b164b0a
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-check"]
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-check"
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-check#1bf9386fb74db524d43011cc8d85360ecf43f0a3"
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.0"
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::new();
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 = format!("{}/{}", project_root.display(), glob_str);
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
- lazy_static! {
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 content = fs::read_to_string(path).ok()?;
143
- let line = content.lines().next()?;
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
- let parent_opt = current.parent().map(|p| p.to_path_buf());
171
- let Some(parent) = parent_opt else { break };
172
- let codeowner_path = parent.join(".codeowner");
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 = parent
159
+ let relative_dir = current
177
160
  .strip_prefix(project_root)
178
- .unwrap_or(parent.as_path())
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
- let existing_len = source_directory_depth(existing_source);
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 parent == project_root {
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
- let parent_opt = current.parent().map(|p| p.to_path_buf());
211
- let Some(parent) = parent_opt else { break };
212
- let parent_rel = parent.strip_prefix(project_root).unwrap_or(parent.as_path());
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 = parent.join("package.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!("{}/**/**", rel_str);
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 = parent.join("package.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!("{}/**/**", rel_str);
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 parent == project_root {
226
+ if current == project_root {
246
227
  break;
247
228
  }
248
- current = parent;
249
229
  }
250
230
  None
251
231
  }
252
232
 
253
- fn source_directory_depth(source: &Source) -> usize {
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
+ }
@@ -33,7 +33,7 @@ use self::{
33
33
  pub struct Ownership {
34
34
  project: Arc<Project>,
35
35
  }
36
-
36
+ #[derive(Debug)]
37
37
  pub struct FileOwner {
38
38
  pub team: Team,
39
39
  pub team_config_file_path: String,
@@ -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 walkdir = builder.build();
57
+ let walk_parallel: WalkParallel = builder.build_parallel();
57
58
 
58
- for entry in walkdir {
59
- let entry = entry.change_context(Error::Io)?;
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>) -> RunResult {
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(_) => RunResult::default(),
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.0"
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-check" }
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, 0))?;
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
  }
@@ -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/codeownership <subcommand>
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/codeownership help`."
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/codeownership validate [options]'
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
- result = FastCodeOwners.validate!(
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/codeownership for_file [options]'
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/codeownership for_file --help` for more info'
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/codeownership for_team \'Team Name\''
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/codeownership for_team --help` for more info'
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!
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FastCodeOwners
4
- VERSION = '0.0.9'
4
+ VERSION = '0.0.11'
5
5
  end
@@ -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.9
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.