code_ownership 2.1.1 → 2.1.3

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.
Files changed (265) hide show
  1. checksums.yaml +4 -4
  2. data/.cargo/config +2 -2
  3. data/Cargo.lock +9 -2
  4. data/README.md +8 -8
  5. data/ext/cargo-vendor/codeowners-0.3.3/.cargo-checksum.json +1 -0
  6. data/ext/cargo-vendor/codeowners-0.3.3/.github/CODEOWNERS +1 -0
  7. data/ext/cargo-vendor/codeowners-0.3.3/AGENTS.md +38 -0
  8. data/ext/cargo-vendor/codeowners-0.3.3/CLAUDE.md +1 -0
  9. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/Cargo.lock +8 -1
  10. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/Cargo.toml +18 -1
  11. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/README.md +11 -1
  12. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/cli.rs +9 -5
  13. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/config.rs +81 -1
  14. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/crosscheck.rs +5 -8
  15. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/ownership/codeowners_file_parser.rs +3 -3
  16. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/ownership/file_generator.rs +119 -9
  17. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/ownership/file_owner_resolver.rs +3 -1
  18. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/ownership/mapper/package_mapper.rs +2 -2
  19. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/ownership/mapper.rs +2 -2
  20. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/ownership/validator.rs +84 -17
  21. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/ownership.rs +1 -0
  22. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/project.rs +3 -0
  23. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/project_builder.rs +111 -44
  24. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/runner/api.rs +7 -6
  25. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/runner/types.rs +2 -1
  26. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/runner.rs +67 -16
  27. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/tracked_files.rs +29 -1
  28. data/ext/cargo-vendor/codeowners-0.3.3/tests/codeowners_path_test.rs +92 -0
  29. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/common/mod.rs +2 -1
  30. data/ext/cargo-vendor/codeowners-0.3.3/tests/executable_name_config_test.rs +88 -0
  31. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/custom_codeowners_path/config/code_ownership.yml +11 -0
  32. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/custom_codeowners_path/config/teams/test_team.yml +6 -0
  33. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/custom_codeowners_path/docs/CODEOWNERS +14 -0
  34. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/custom_codeowners_path/expected/CODEOWNERS +14 -0
  35. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/custom_codeowners_path/ruby/app/models/test.rb +3 -0
  36. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/custom_executable_name/.github/CODEOWNERS +10 -0
  37. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/custom_executable_name/app/foo.rb +3 -0
  38. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/custom_executable_name/config/code_ownership.yml +4 -0
  39. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/custom_executable_name/config/teams/foo.yml +5 -0
  40. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/custom_executable_name/config/teams/payments.yml +6 -0
  41. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/custom_executable_name/ruby/app/payments/foo.rb +4 -0
  42. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/default_executable_name/.github/CODEOWNERS +11 -0
  43. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/default_executable_name/app/bar.rb +3 -0
  44. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/default_executable_name/config/code_ownership.yml +5 -0
  45. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/default_executable_name/config/teams/bar.yml +5 -0
  46. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/missing_github_team/.github/CODEOWNERS +10 -0
  47. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/missing_github_team/config/code_ownership.yml +10 -0
  48. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/missing_github_team/config/teams/bad_team.yml +1 -0
  49. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/missing_github_team/config/teams/good.yml +3 -0
  50. data/ext/cargo-vendor/codeowners-0.3.3/tests/fixtures/valid_project/gems/pets/dog.rb +5 -0
  51. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/git_stage_test.rs +4 -1
  52. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/invalid_project_test.rs +30 -0
  53. data/ext/cargo-vendor/codeowners-0.3.3/tests/missing_github_team_test.rs +23 -0
  54. data/ext/cargo-vendor/codeowners-0.3.3/tests/run_config_executable_override_test.rs +98 -0
  55. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/runner_api.rs +8 -4
  56. data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/valid_project_test.rs +3 -3
  57. data/ext/cargo-vendor/codeowners-0.3.3/tests/validate_files_test.rs +378 -0
  58. data/ext/cargo-vendor/similar-2.7.0/.cargo/config.toml +2 -0
  59. data/ext/cargo-vendor/similar-2.7.0/.cargo-checksum.json +1 -0
  60. data/ext/cargo-vendor/similar-2.7.0/.cargo_vcs_info.json +6 -0
  61. data/ext/cargo-vendor/similar-2.7.0/.github/FUNDING.yml +1 -0
  62. data/ext/cargo-vendor/similar-2.7.0/.github/workflows/clippy.yml +16 -0
  63. data/ext/cargo-vendor/similar-2.7.0/.github/workflows/rustfmt.yml +16 -0
  64. data/ext/cargo-vendor/similar-2.7.0/.github/workflows/tests.yml +49 -0
  65. data/ext/cargo-vendor/similar-2.7.0/.vscode/settings.json +5 -0
  66. data/ext/cargo-vendor/similar-2.7.0/CHANGELOG.md +132 -0
  67. data/ext/cargo-vendor/similar-2.7.0/Cargo.lock +373 -0
  68. data/ext/cargo-vendor/similar-2.7.0/Cargo.lock.msrv +266 -0
  69. data/ext/cargo-vendor/similar-2.7.0/Cargo.toml +149 -0
  70. data/ext/cargo-vendor/similar-2.7.0/Cargo.toml.orig +73 -0
  71. data/ext/cargo-vendor/similar-2.7.0/LICENSE +201 -0
  72. data/ext/cargo-vendor/similar-2.7.0/Makefile +31 -0
  73. data/ext/cargo-vendor/similar-2.7.0/README.md +59 -0
  74. data/ext/cargo-vendor/similar-2.7.0/clippy.toml +1 -0
  75. data/ext/cargo-vendor/similar-2.7.0/examples/close-matches.rs +15 -0
  76. data/ext/cargo-vendor/similar-2.7.0/examples/large.rs +8 -0
  77. data/ext/cargo-vendor/similar-2.7.0/examples/nonstring.rs +13 -0
  78. data/ext/cargo-vendor/similar-2.7.0/examples/original-slices.rs +11 -0
  79. data/ext/cargo-vendor/similar-2.7.0/examples/patience.rs +48 -0
  80. data/ext/cargo-vendor/similar-2.7.0/examples/serde.rs +15 -0
  81. data/ext/cargo-vendor/similar-2.7.0/examples/terminal-inline.rs +60 -0
  82. data/ext/cargo-vendor/similar-2.7.0/examples/terminal.rs +20 -0
  83. data/ext/cargo-vendor/similar-2.7.0/examples/udiff.rs +24 -0
  84. data/ext/cargo-vendor/similar-2.7.0/scripts/wasmtime-wrapper.sh +4 -0
  85. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/capture.rs +117 -0
  86. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/compact.rs +351 -0
  87. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/hook.rs +178 -0
  88. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/lcs.rs +294 -0
  89. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/mod.rs +134 -0
  90. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/myers.rs +442 -0
  91. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/patience.rs +198 -0
  92. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/replace.rs +221 -0
  93. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/snapshots/similar__algorithms__capture__capture_hook_grouping-2.snap +60 -0
  94. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/snapshots/similar__algorithms__capture__capture_hook_grouping.snap +64 -0
  95. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/snapshots/similar__algorithms__lcs__contiguous.snap +28 -0
  96. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/snapshots/similar__algorithms__lcs__diff.snap +22 -0
  97. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/snapshots/similar__algorithms__lcs__pat.snap +31 -0
  98. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/snapshots/similar__algorithms__lcs__same.snap +12 -0
  99. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/snapshots/similar__algorithms__myers__contiguous.snap +28 -0
  100. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/snapshots/similar__algorithms__myers__deadline_reached.snap +22 -0
  101. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/snapshots/similar__algorithms__myers__diff.snap +22 -0
  102. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/snapshots/similar__algorithms__myers__pat.snap +31 -0
  103. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/snapshots/similar__algorithms__patience__patience.snap +45 -0
  104. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/snapshots/similar__algorithms__patience__patience_out_of_bounds_bug.snap +16 -0
  105. data/ext/cargo-vendor/similar-2.7.0/src/algorithms/utils.rs +379 -0
  106. data/ext/cargo-vendor/similar-2.7.0/src/common.rs +185 -0
  107. data/ext/cargo-vendor/similar-2.7.0/src/deadline_support.rs +37 -0
  108. data/ext/cargo-vendor/similar-2.7.0/src/iter.rs +195 -0
  109. data/ext/cargo-vendor/similar-2.7.0/src/lib.rs +176 -0
  110. data/ext/cargo-vendor/similar-2.7.0/src/snapshots/similar__udiff__unified_diff.snap +25 -0
  111. data/ext/cargo-vendor/similar-2.7.0/src/snapshots/similar__udiff__unified_diff_newline_hint-2.snap +10 -0
  112. data/ext/cargo-vendor/similar-2.7.0/src/snapshots/similar__udiff__unified_diff_newline_hint.snap +11 -0
  113. data/ext/cargo-vendor/similar-2.7.0/src/text/abstraction.rs +446 -0
  114. data/ext/cargo-vendor/similar-2.7.0/src/text/inline.rs +342 -0
  115. data/ext/cargo-vendor/similar-2.7.0/src/text/mod.rs +792 -0
  116. data/ext/cargo-vendor/similar-2.7.0/src/text/snapshots/similar__text__captured_ops.snap +22 -0
  117. data/ext/cargo-vendor/similar-2.7.0/src/text/snapshots/similar__text__captured_word_ops.snap +202 -0
  118. data/ext/cargo-vendor/similar-2.7.0/src/text/snapshots/similar__text__char_diff.snap +39 -0
  119. data/ext/cargo-vendor/similar-2.7.0/src/text/snapshots/similar__text__inline__line_ops_inline.snap +126 -0
  120. data/ext/cargo-vendor/similar-2.7.0/src/text/snapshots/similar__text__inline__serde.snap +107 -0
  121. data/ext/cargo-vendor/similar-2.7.0/src/text/snapshots/similar__text__lifetimes_on_iter.snap +42 -0
  122. data/ext/cargo-vendor/similar-2.7.0/src/text/snapshots/similar__text__line_ops.snap +42 -0
  123. data/ext/cargo-vendor/similar-2.7.0/src/text/snapshots/similar__text__serde.snap +55 -0
  124. data/ext/cargo-vendor/similar-2.7.0/src/text/snapshots/similar__text__serde_ops.snap +38 -0
  125. data/ext/cargo-vendor/similar-2.7.0/src/text/snapshots/similar__text__unified_diff.snap +12 -0
  126. data/ext/cargo-vendor/similar-2.7.0/src/text/snapshots/similar__text__virtual_newlines.snap +32 -0
  127. data/ext/cargo-vendor/similar-2.7.0/src/text/utils.rs +55 -0
  128. data/ext/cargo-vendor/similar-2.7.0/src/types.rs +502 -0
  129. data/ext/cargo-vendor/similar-2.7.0/src/udiff.rs +359 -0
  130. data/ext/cargo-vendor/similar-2.7.0/src/utils.rs +412 -0
  131. data/ext/cargo-vendor/unicode-ident-1.0.19/.cargo-checksum.json +1 -1
  132. data/ext/code_ownership/Cargo.toml +1 -1
  133. data/ext/code_ownership/src/lib.rs +2 -2
  134. data/lib/code_ownership/private/file_path_finder.rb +19 -3
  135. data/lib/code_ownership/private/team_finder.rb +1 -2
  136. data/lib/code_ownership/version.rb +1 -1
  137. data/lib/code_ownership.rb +2 -0
  138. metadata +252 -152
  139. data/ext/cargo-vendor/codeowners-0.3.0/.cargo-checksum.json +0 -1
  140. data/ext/cargo-vendor/codeowners-0.3.0/tests/validate_files_test.rs +0 -144
  141. data/ext/cargo-vendor/unicode-ident-1.0.19/tests/fst/.gitignore +0 -1
  142. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/.github/workflows/audit.yml +0 -0
  143. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/.github/workflows/ci.yml +0 -0
  144. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/.github/workflows/dotslash-config.json +0 -0
  145. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/.rustfmt.toml +0 -0
  146. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/.rusty-hook.toml +0 -0
  147. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/dev/run_benchmarks_for_file.sh +0 -0
  148. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/dev/run_benchmarks_for_gv.sh +0 -0
  149. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/rust-toolchain.toml +0 -0
  150. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/cache/file.rs +0 -0
  151. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/cache/mod.rs +0 -0
  152. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/cache/noop.rs +0 -0
  153. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/common_test.rs +0 -0
  154. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/lib.rs +0 -0
  155. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/main.rs +0 -0
  156. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/ownership/codeowners_query.rs +0 -0
  157. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/ownership/file_owner_finder.rs +0 -0
  158. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/ownership/mapper/annotated_file_mapper.rs +0 -0
  159. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/ownership/mapper/directory_mapper.rs +0 -0
  160. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/ownership/mapper/escaper.rs +0 -0
  161. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/ownership/mapper/team_gem_mapper.rs +0 -0
  162. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/ownership/mapper/team_glob_mapper.rs +0 -0
  163. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/ownership/mapper/team_yml_mapper.rs +0 -0
  164. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/path_utils.rs +0 -0
  165. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/src/project_file_builder.rs +0 -0
  166. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/cache_test.rs +0 -0
  167. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/crosscheck_owners_test.rs +0 -0
  168. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/invalid_project/.github/CODEOWNERS +0 -0
  169. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/invalid_project/config/code_ownership.yml +0 -0
  170. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/invalid_project/config/teams/payments.yml +0 -0
  171. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/invalid_project/config/teams/payroll.yml +0 -0
  172. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/invalid_project/gems/payroll_calculator/calculator.rb +0 -0
  173. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/invalid_project/ruby/app/models/bank_account.rb +0 -0
  174. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/invalid_project/ruby/app/models/blockchain.rb +0 -0
  175. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/invalid_project/ruby/app/models/payroll.rb +0 -0
  176. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/invalid_project/ruby/app/payments/nacha.rb +0 -0
  177. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/invalid_project/ruby/app/services/.codeowner +0 -0
  178. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/invalid_project/ruby/app/services/multi_owned.rb +0 -0
  179. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/invalid_project/ruby/app/unowned.rb +0 -0
  180. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/invalid_project/ruby/packages/payroll_flow/package.yml +0 -0
  181. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/javascript_only_project/.github/CODEOWNERS +0 -0
  182. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/javascript_only_project/.keep +0 -0
  183. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/javascript_only_project/config/code_ownership.yml +0 -0
  184. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/javascript_only_project/config/teams/design.yml +0 -0
  185. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/javascript_only_project/config/teams/frontend.yml +0 -0
  186. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/javascript_only_project/frontend/apps/public/index.tsx +0 -0
  187. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/javascript_only_project/frontend/packages/dashboard/package.json +0 -0
  188. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/javascript_only_project/frontend/packages/dashboard/src/index.tsx +0 -0
  189. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/javascript_only_project/frontend/packages/ui-kit/.codeowner +0 -0
  190. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/javascript_only_project/frontend/packages/ui-kit/src/button.tsx +0 -0
  191. /data/ext/cargo-vendor/{codeowners-0.3.0/tests/fixtures/valid_project → codeowners-0.3.3/tests/fixtures/missing_github_team}/gems/pets/dog.rb +0 -0
  192. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/multiple-directory-owners/.github/CODEOWNERS +0 -0
  193. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/multiple-directory-owners/app/consumers/.codeowner +0 -0
  194. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/multiple-directory-owners/app/consumers/deep/nesting/nestdir/deep_file.rb +0 -0
  195. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/multiple-directory-owners/app/consumers/one_owner.rb +0 -0
  196. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/multiple-directory-owners/app/services/.codeowner +0 -0
  197. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/multiple-directory-owners/app/services/exciting/.codeowner +0 -0
  198. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/multiple-directory-owners/app/services/exciting/some_other_file.rb +0 -0
  199. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/multiple-directory-owners/config/code_ownership.yml +0 -0
  200. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/multiple-directory-owners/config/teams/bar.yml +0 -0
  201. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/multiple-directory-owners/config/teams/foo.yml +0 -0
  202. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/.github/CODEOWNERS +0 -0
  203. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/.ignore +0 -0
  204. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/config/code_ownership.yml +0 -0
  205. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/config/teams/payments.yml +0 -0
  206. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/config/teams/payroll.yml +0 -0
  207. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/config/teams/ux.yml +0 -0
  208. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/gems/payroll_calculator/calculator.rb +0 -0
  209. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/javascript/packages/PayrollFlow/index.tsx +0 -0
  210. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/javascript/packages/PayrollFlow/package.json +0 -0
  211. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/javascript/packages/items/(special)/.codeowner +0 -0
  212. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/javascript/packages/items/(special)/pay.ts +0 -0
  213. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/javascript/packages/items/.codeowner +0 -0
  214. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/javascript/packages/items/item.ts +0 -0
  215. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/javascript/packages/list/page-admin.tsx +0 -0
  216. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/ruby/app/models/bank_account.rb +0 -0
  217. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/ruby/app/models/payroll.rb +0 -0
  218. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/ruby/app/payments/foo/.codeowner +0 -0
  219. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/ruby/app/payments/foo/ownedby_payroll.rb +0 -0
  220. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/ruby/app/payments/nacha.rb +0 -0
  221. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/ruby/app/payroll/.codeowner +0 -0
  222. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/ruby/app/payroll/payroll.rb +0 -0
  223. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/ruby/app/views/foos/edit.erb +0 -0
  224. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/ruby/app/views/foos/index.html.erb +0 -0
  225. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/ruby/app/views/foos/new.html.erb +0 -0
  226. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/ruby/ignored_files/git_ignored.rb +0 -0
  227. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/ruby/packages/payroll_flow/package.yml +0 -0
  228. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project/should_be_ignored/an_ignored_file.rb +0 -0
  229. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/.github/CODEOWNERS +0 -0
  230. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/config/code_ownership.yml +0 -0
  231. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/config/teams/brewers.yml +0 -0
  232. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/config/teams/cubs.yml +0 -0
  233. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/config/teams/giants.yml +0 -0
  234. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/config/teams/rockies.yml +0 -0
  235. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/frontend/packages/components/datepicker/package.json +0 -0
  236. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/frontend/packages/components/datepicker/src/picks/dp.tsx +0 -0
  237. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/frontend/packages/components/list/package.json +0 -0
  238. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/frontend/packages/components/list/src/item.tsx +0 -0
  239. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/frontend/packages/components/textfield/package.json +0 -0
  240. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/frontend/packages/components/textfield/src/field.tsx +0 -0
  241. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/frontend/packages/components/textfield/src/fields/small.tsx +0 -0
  242. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/gems/apollo/lib/apollo.rb +0 -0
  243. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/gems/ivy/lib/ivy.rb +0 -0
  244. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/gems/lager/lib/lager.rb +0 -0
  245. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/gems/summit/lib/summit.rb +0 -0
  246. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/packs/games/app/services/stats.rb +0 -0
  247. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/packs/games/package.yml +0 -0
  248. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/packs/locations/app/services/capacity.rb +0 -0
  249. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/packs/locations/package.yml +0 -0
  250. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/packs/schedule/app/services/date.rb +0 -0
  251. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/packs/schedule/package.yml +0 -0
  252. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/ruby/app/brewers/lib/util.rb +0 -0
  253. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/ruby/app/brewers/services/play.rb +0 -0
  254. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/ruby/app/cubs/.codeowner +0 -0
  255. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/ruby/app/cubs/services/models/.codeowner +0 -0
  256. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/ruby/app/cubs/services/models/db/price.rb +0 -0
  257. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/ruby/app/cubs/services/models/entertainment.rb +0 -0
  258. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/ruby/app/cubs/services/play.rb +0 -0
  259. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/ruby/app/giants/services/play.rb +0 -0
  260. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/fixtures/valid_project_with_overrides/ruby/app/rockies/services/play.rb +0 -0
  261. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/invalid_project_structure_test.rs +0 -0
  262. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/multiple_directory_owners_test.rs +0 -0
  263. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/untracked_files_test.rs +0 -0
  264. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tests/valid_project_with_overrides_test.rs +0 -0
  265. /data/ext/cargo-vendor/{codeowners-0.3.0 → codeowners-0.3.3}/tmp/.gitkeep +0 -0
@@ -49,17 +49,37 @@ impl FileGenerator {
49
49
  }
50
50
 
51
51
  pub fn compare_lines(a: &String, b: &String) -> Ordering {
52
- if let Some((prefix, _)) = a.split_once("**")
53
- && b.starts_with(prefix)
54
- {
55
- return Ordering::Less;
52
+ let path_a = extract_path(a);
53
+ let path_b = extract_path(b);
54
+
55
+ let mut comps_a = path_a.split('/');
56
+ let mut comps_b = path_b.split('/');
57
+
58
+ loop {
59
+ match (comps_a.next(), comps_b.next()) {
60
+ (None, None) => return a.cmp(b),
61
+ (None, Some(_)) => return Ordering::Less,
62
+ (Some(_), None) => return Ordering::Greater,
63
+ (Some(ca), Some(cb)) => match compare_component(ca, cb) {
64
+ Ordering::Equal => continue,
65
+ ord => return ord,
66
+ },
67
+ }
56
68
  }
57
- if let Some((prefix, _)) = b.split_once("**")
58
- && a.starts_with(prefix)
59
- {
60
- return Ordering::Greater;
69
+ }
70
+
71
+ fn extract_path(line: &str) -> &str {
72
+ let stripped = line.strip_prefix("# ").unwrap_or(line);
73
+ stripped.split_once(' ').map(|(p, _)| p).unwrap_or(stripped)
74
+ }
75
+
76
+ fn compare_component(a: &str, b: &str) -> Ordering {
77
+ match (a == "**", b == "**") {
78
+ (true, true) => Ordering::Equal,
79
+ (true, false) => Ordering::Less,
80
+ (false, true) => Ordering::Greater,
81
+ (false, false) => a.cmp(b),
61
82
  }
62
- a.cmp(b)
63
83
  }
64
84
 
65
85
  #[cfg(test)]
@@ -181,6 +201,96 @@ mod tests {
181
201
  assert_eq!(sorted, vec!["/directory/owner1/** @foo", "/directory/owner2/** @bar"]);
182
202
  }
183
203
 
204
+ #[test]
205
+ fn test_compare_lines_is_antisymmetric_for_shared_double_star_prefix() {
206
+ let a = "/foo/**/*bar* @org/example-team".to_string();
207
+ let b = "/foo/**/baz/**/* @org/example-team".to_string();
208
+
209
+ assert_eq!(compare_lines(&a, &b), Ordering::Less);
210
+ assert_eq!(compare_lines(&b, &a), Ordering::Greater);
211
+
212
+ let mut forward = vec![a.clone(), b.clone()];
213
+ let mut reverse = vec![b.clone(), a.clone()];
214
+ forward.sort_by(compare_lines);
215
+ reverse.sort_by(compare_lines);
216
+ assert_eq!(forward, reverse);
217
+ assert_eq!(forward, vec![a, b]);
218
+ }
219
+
220
+ fn special_character_set() -> Vec<String> {
221
+ vec![
222
+ "/directory/** @bop".to_string(),
223
+ "/directory/owner/** @bar".to_string(),
224
+ "/directory/owner/(my_folder)/**/** @foo".to_string(),
225
+ "/directory/owner/(my_folder)/without_glob @zoo".to_string(),
226
+ "/directory/owner/my_folder/** @baz".to_string(),
227
+ ]
228
+ }
229
+
230
+ #[test]
231
+ fn test_compare_lines_is_antisymmetric_across_special_character_set() {
232
+ let lines = special_character_set();
233
+ for x in &lines {
234
+ assert_eq!(compare_lines(x, x), Ordering::Equal);
235
+ for y in &lines {
236
+ if x == y {
237
+ continue;
238
+ }
239
+ let xy = compare_lines(x, y);
240
+ let yx = compare_lines(y, x);
241
+ assert_eq!(xy.reverse(), yx, "asymmetric for {x:?} vs {y:?}");
242
+ }
243
+ }
244
+ }
245
+
246
+ #[test]
247
+ fn test_compare_lines_is_transitive_across_special_character_set() {
248
+ let lines = special_character_set();
249
+ for x in &lines {
250
+ for y in &lines {
251
+ for z in &lines {
252
+ let xy = compare_lines(x, y);
253
+ let yz = compare_lines(y, z);
254
+ let xz = compare_lines(x, z);
255
+ if xy != Ordering::Greater && yz != Ordering::Greater {
256
+ assert_ne!(xz, Ordering::Greater, "transitivity broken: {x:?} <= {y:?} <= {z:?} but x > z");
257
+ }
258
+ if xy != Ordering::Less && yz != Ordering::Less {
259
+ assert_ne!(xz, Ordering::Less, "transitivity broken: {x:?} >= {y:?} >= {z:?} but x < z");
260
+ }
261
+ }
262
+ }
263
+ }
264
+ }
265
+
266
+ #[test]
267
+ fn test_compare_lines_orders_shorter_path_before_longer_extension() {
268
+ let shorter = "/foo @a".to_string();
269
+ let longer = "/foo/bar @b".to_string();
270
+
271
+ assert_eq!(compare_lines(&shorter, &longer), Ordering::Less);
272
+ assert_eq!(compare_lines(&longer, &shorter), Ordering::Greater);
273
+ }
274
+
275
+ #[test]
276
+ fn test_compare_lines_falls_back_to_full_line_when_paths_equal() {
277
+ let a = "/foo/** @a".to_string();
278
+ let b = "/foo/** @b".to_string();
279
+
280
+ assert_eq!(compare_lines(&a, &b), Ordering::Less);
281
+ assert_eq!(compare_lines(&b, &a), Ordering::Greater);
282
+ }
283
+
284
+ #[test]
285
+ fn test_compare_lines_ignores_disabled_comment_prefix() {
286
+ let enabled = "/foo/owner/** @bar".to_string();
287
+ let disabled = "# /foo/owner/** @bar".to_string();
288
+ let other = "/foo/owner/(extra)/file @baz".to_string();
289
+
290
+ assert_eq!(compare_lines(&enabled, &other), compare_lines(&disabled, &other));
291
+ assert_eq!(compare_lines(&other, &enabled), compare_lines(&other, &disabled));
292
+ }
293
+
184
294
  #[test]
185
295
  fn test_sorting_with_special_characters() {
186
296
  let entries = vec![
@@ -116,7 +116,7 @@ fn load_teams(project_root: &Path, team_file_globs: &[String]) -> std::result::R
116
116
  match Team::from_team_file_path(path.clone()) {
117
117
  Ok(team) => teams.push(team),
118
118
  Err(e) => {
119
- eprintln!("Error parsing team file: {}, path: {}", e, path.display());
119
+ eprintln!("Error parsing team file: {e:?}, path: {}", path.display());
120
120
  continue;
121
121
  }
122
122
  }
@@ -293,6 +293,8 @@ mod tests {
293
293
  vendored_gems_path: vendored_path.to_string(),
294
294
  cache_directory: "tmp/cache/codeowners".to_string(),
295
295
  ignore_dirs: vec![],
296
+ executable_name: "codeowners".to_string(),
297
+ codeowners_path: ".github".to_string(),
296
298
  }
297
299
  }
298
300
 
@@ -95,7 +95,7 @@ impl PackageMapper {
95
95
  let packages: Vec<&Package> = packages.iter().filter(|package| &package.package_type == package_type).collect();
96
96
 
97
97
  // Nested packs can create a duplicate ownership false positive.
98
- // We avoid it by treating nested packs as a single top level pack for the purpose of validations
98
+ // We avoid it by treating nested packs as a single top-level pack for the purposes of validation.
99
99
  let packages = remove_nested_packages(&packages);
100
100
 
101
101
  for package in packages {
@@ -147,7 +147,7 @@ mod tests {
147
147
  use std::{error::Error, path::Path};
148
148
  #[test]
149
149
  fn test_remove_nested_packages() {
150
- let packages = vec![
150
+ let packages = [
151
151
  Package {
152
152
  path: Path::new("packs/a/package.yml").to_owned(),
153
153
  package_type: PackageType::Ruby,
@@ -48,7 +48,7 @@ impl Display for Source {
48
48
  Source::TeamGem => write!(f, "Owner specified in Team YML's `owned_gems`"),
49
49
  Source::TeamGlob(glob) => write!(f, "Owner specified in Team YML as an owned_glob `{}`", glob),
50
50
  Source::Package(package_path, glob) => {
51
- write!(f, "Owner defined in `{}` with implicity owned glob: `{}`", package_path, glob)
51
+ write!(f, "Owner defined in `{}` with implicitly owned glob: `{}`", package_path, glob)
52
52
  }
53
53
  Source::TeamYml => write!(f, "Teams own their configuration files"),
54
54
  }
@@ -208,7 +208,7 @@ mod tests {
208
208
  );
209
209
  assert_eq!(
210
210
  Source::Package("packs/bam/packag.yml".to_string(), "packs/bam/**/**".to_string()).to_string(),
211
- "Owner defined in `packs/bam/packag.yml` with implicity owned glob: `packs/bam/**/**`"
211
+ "Owner defined in `packs/bam/packag.yml` with implicitly owned glob: `packs/bam/**/**`"
212
212
  );
213
213
  assert_eq!(Source::TeamYml.to_string(), "Teams own their configuration files");
214
214
  }
@@ -9,6 +9,7 @@ use error_stack::Context;
9
9
  use itertools::Itertools;
10
10
  use rayon::prelude::IntoParallelRefIterator;
11
11
  use rayon::prelude::ParallelIterator;
12
+ use similar::{ChangeTag, TextDiff};
12
13
  use tracing::debug;
13
14
  use tracing::instrument;
14
15
 
@@ -21,6 +22,7 @@ pub struct Validator {
21
22
  pub project: Arc<Project>,
22
23
  pub mappers: Vec<Box<dyn Mapper>>,
23
24
  pub file_generator: FileGenerator,
25
+ pub executable_name: String,
24
26
  }
25
27
 
26
28
  #[derive(Debug)]
@@ -28,7 +30,7 @@ enum Error {
28
30
  InvalidTeam { name: String, path: PathBuf },
29
31
  FileWithoutOwner { path: PathBuf },
30
32
  FileWithMultipleOwners { path: PathBuf, owners: Vec<Owner> },
31
- CodeownershipFileIsStale,
33
+ CodeownershipFileIsStale { executable_name: String, diff: String },
32
34
  }
33
35
 
34
36
  #[derive(Debug)]
@@ -126,16 +128,15 @@ impl Validator {
126
128
 
127
129
  fn validate_codeowners_file(&self) -> Vec<Error> {
128
130
  let generated_file = self.file_generator.generate_file();
131
+ let current_file = self.project.get_codeowners_file().unwrap_or_default();
129
132
 
130
- match self.project.get_codeowners_file() {
131
- Ok(current_file) => {
132
- if generated_file != current_file {
133
- vec![Error::CodeownershipFileIsStale]
134
- } else {
135
- vec![]
136
- }
137
- }
138
- Err(_) => vec![Error::CodeownershipFileIsStale], // Treat any read error as stale file
133
+ if generated_file == current_file {
134
+ vec![]
135
+ } else {
136
+ vec![Error::CodeownershipFileIsStale {
137
+ executable_name: self.executable_name.to_string(),
138
+ diff: codeowners_diff(&current_file, &generated_file),
139
+ }]
139
140
  }
140
141
  }
141
142
 
@@ -158,16 +159,36 @@ impl Validator {
158
159
  }
159
160
  }
160
161
 
162
+ /// Builds a line-oriented diff between the current (on-disk) CODEOWNERS file and the
163
+ /// freshly generated one, so that validation failures explain *what* is out of date
164
+ /// rather than just *that* it is. Only changed lines are emitted: removals (present
165
+ /// on disk but no longer expected) are prefixed with `-` and additions (expected but
166
+ /// missing) are prefixed with `+`.
167
+ fn codeowners_diff(current: &str, generated: &str) -> String {
168
+ let diff = TextDiff::from_lines(current, generated);
169
+
170
+ diff.iter_all_changes()
171
+ .filter_map(|change| {
172
+ let line = change.value().trim_end_matches('\n');
173
+ match change.tag() {
174
+ ChangeTag::Delete => Some(format!("-{line}")),
175
+ ChangeTag::Insert => Some(format!("+{line}")),
176
+ ChangeTag::Equal => None,
177
+ }
178
+ })
179
+ .join("\n")
180
+ }
181
+
161
182
  impl Error {
162
183
  pub fn category(&self) -> String {
163
184
  match self {
164
- Error::FileWithoutOwner { path: _ } => "Some files are missing ownership".to_owned(),
165
- Error::FileWithMultipleOwners { path: _, owners: _ } => "Code ownership should only be defined for each file in one way. The following files have declared ownership in multiple ways".to_owned(),
166
- Error::CodeownershipFileIsStale => {
167
- "CODEOWNERS out of date. Run `codeowners generate` to update the CODEOWNERS file".to_owned()
185
+ Error::FileWithoutOwner { path: _ } => "Some files are missing ownership".to_owned(),
186
+ Error::FileWithMultipleOwners { path: _, owners: _ } => "Code ownership should only be defined for each file in one way. The following files have declared ownership in multiple ways".to_owned(),
187
+ Error::CodeownershipFileIsStale { executable_name, diff: _ } => {
188
+ format!("CODEOWNERS out of date. Run `{}` to update the CODEOWNERS file", executable_name)
189
+ }
190
+ Error::InvalidTeam { name: _, path: _ } => "Found invalid team annotations".to_owned(),
168
191
  }
169
- Error::InvalidTeam { name: _, path: _ } => "Found invalid team annotations".to_owned(),
170
- }
171
192
  }
172
193
 
173
194
  pub fn messages(&self) -> Vec<String> {
@@ -187,7 +208,13 @@ impl Error {
187
208
 
188
209
  vec![messages.join("\n")]
189
210
  }
190
- Error::CodeownershipFileIsStale => vec![],
211
+ Error::CodeownershipFileIsStale { executable_name: _, diff } => {
212
+ if diff.is_empty() {
213
+ vec![]
214
+ } else {
215
+ vec![format!("The following changes are required (- current, + expected):\n{diff}")]
216
+ }
217
+ }
191
218
  Error::InvalidTeam { name, path } => vec![format!("- {} is referencing an invalid team - '{}'", path.to_string_lossy(), name)],
192
219
  }
193
220
  }
@@ -216,3 +243,43 @@ impl Display for Errors {
216
243
  }
217
244
 
218
245
  impl Context for Errors {}
246
+
247
+ #[cfg(test)]
248
+ mod tests {
249
+ use super::*;
250
+ use indoc::indoc;
251
+
252
+ #[test]
253
+ fn test_codeowners_diff_reports_added_and_removed_lines() {
254
+ let current = indoc! {"
255
+ # Team A
256
+ /app/a.rb @TeamA
257
+ /app/old.rb @TeamA
258
+ "};
259
+ let generated = indoc! {"
260
+ # Team A
261
+ /app/a.rb @TeamA
262
+ /app/b.rb @TeamB
263
+ "};
264
+
265
+ let diff = codeowners_diff(current, generated);
266
+
267
+ assert_eq!(diff, "-/app/old.rb @TeamA\n+/app/b.rb @TeamB");
268
+ }
269
+
270
+ #[test]
271
+ fn test_codeowners_diff_against_empty_file_is_all_additions() {
272
+ let generated = "# Team A\n/app/a.rb @TeamA\n";
273
+
274
+ let diff = codeowners_diff("", generated);
275
+
276
+ assert_eq!(diff, "+# Team A\n+/app/a.rb @TeamA");
277
+ }
278
+
279
+ #[test]
280
+ fn test_codeowners_diff_is_empty_when_identical() {
281
+ let file = "# Team A\n/app/a.rb @TeamA\n";
282
+
283
+ assert_eq!(codeowners_diff(file, file), "");
284
+ }
285
+ }
@@ -123,6 +123,7 @@ impl Ownership {
123
123
  project: self.project.clone(),
124
124
  mappers: self.mappers(),
125
125
  file_generator: FileGenerator { mappers: self.mappers() },
126
+ executable_name: self.project.executable_name.clone(),
126
127
  };
127
128
 
128
129
  validator.validate()
@@ -17,6 +17,7 @@ pub struct Project {
17
17
  pub codeowners_file_path: PathBuf,
18
18
  pub directory_codeowner_files: Vec<DirectoryCodeownersFile>,
19
19
  pub teams_by_name: HashMap<String, Team>,
20
+ pub executable_name: String,
20
21
  }
21
22
 
22
23
  #[derive(Clone, Debug)]
@@ -111,6 +112,7 @@ pub mod deserializers {
111
112
  #[derive(Deserialize)]
112
113
  pub struct RubyPackage {
113
114
  pub owner: Option<String>,
115
+ pub metadata: Option<Metadata>,
114
116
  }
115
117
 
116
118
  #[derive(Deserialize)]
@@ -219,6 +221,7 @@ mod tests {
219
221
  codeowners_file_path: PathBuf::from(".github/CODEOWNERS"),
220
222
  directory_codeowner_files: vec![],
221
223
  teams_by_name: HashMap::new(),
224
+ executable_name: "codeowners generate".to_string(),
222
225
  };
223
226
 
224
227
  let map = project.vendored_gem_by_name();
@@ -8,7 +8,7 @@ use error_stack::{Report, Result, ResultExt};
8
8
  use fast_glob::glob_match;
9
9
  use ignore::{DirEntry, WalkBuilder, WalkParallel, WalkState};
10
10
  use rayon::iter::{IntoParallelIterator, ParallelIterator};
11
- use tracing::{instrument, warn};
11
+ use tracing::instrument;
12
12
 
13
13
  use crate::{
14
14
  cache::Cache,
@@ -178,9 +178,17 @@ impl<'a> ProjectBuilder<'a> {
178
178
  }
179
179
 
180
180
  fn build_project_from_entry_types(&mut self, entry_types: Vec<EntryType>) -> Result<Project, Error> {
181
- let (project_files, packages, vendored_gems, directory_codeowners, teams): (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = entry_types
181
+ type Accumulator = (
182
+ Vec<ProjectFile>,
183
+ Vec<Package>,
184
+ Vec<VendoredGem>,
185
+ Vec<DirectoryCodeownersFile>,
186
+ Vec<Team>,
187
+ );
188
+
189
+ let (project_files, packages, vendored_gems, directory_codeowners, teams): Accumulator = entry_types
182
190
  .into_par_iter()
183
- .fold(
191
+ .try_fold(
184
192
  || {
185
193
  (
186
194
  Vec::<ProjectFile>::with_capacity(INITIAL_VECTOR_CAPACITY),
@@ -197,18 +205,20 @@ impl<'a> ProjectBuilder<'a> {
197
205
  }
198
206
  EntryType::Directory(absolute_path, relative_path) => {
199
207
  if relative_path.parent() == Some(Path::new(&self.config.vendored_gems_path)) {
200
- if let Some(file_name) = relative_path.file_name() {
201
- gems.push(VendoredGem {
202
- path: absolute_path,
203
- name: file_name.to_string_lossy().to_string(),
204
- });
205
- } else {
206
- warn!("Vendored gem path without file name: {:?}", relative_path);
207
- }
208
+ let file_name = relative_path.file_name().ok_or_else(|| {
209
+ error_stack::report!(Error::Io)
210
+ .attach_printable(format!("Vendored gem path has no file name: {}", relative_path.display()))
211
+ })?;
212
+ gems.push(VendoredGem {
213
+ path: absolute_path,
214
+ name: file_name.to_string_lossy().to_string(),
215
+ });
208
216
  }
209
217
  }
210
218
  EntryType::RubyPackage(absolute_path, relative_path) => {
211
- match ruby_package_owner(&absolute_path) {
219
+ match ruby_package_owner(&absolute_path)
220
+ .attach_printable_lazy(|| format!("Failed to read ruby package: {}", absolute_path.display()))
221
+ {
212
222
  Ok(Some(owner)) => {
213
223
  pkgs.push(Package {
214
224
  path: relative_path.clone(),
@@ -217,13 +227,13 @@ impl<'a> ProjectBuilder<'a> {
217
227
  });
218
228
  }
219
229
  Ok(None) => { /* No owner, do nothing */ }
220
- Err(e) => {
221
- warn!("Error reading ruby package owner for {:?}: {:?}", absolute_path, e);
222
- }
230
+ Err(e) => return Err(e),
223
231
  }
224
232
  }
225
233
  EntryType::JavascriptPackage(absolute_path, relative_path) => {
226
- match javascript_package_owner(&absolute_path) {
234
+ match javascript_package_owner(&absolute_path)
235
+ .attach_printable_lazy(|| format!("Failed to read javascript package: {}", absolute_path.display()))
236
+ {
227
237
  Ok(Some(owner)) => {
228
238
  pkgs.push(Package {
229
239
  path: relative_path.clone(),
@@ -232,37 +242,31 @@ impl<'a> ProjectBuilder<'a> {
232
242
  });
233
243
  }
234
244
  Ok(None) => { /* No owner, do nothing */ }
235
- Err(e) => {
236
- warn!("Error reading javascript package owner for {:?}: {:?}", absolute_path, e);
237
- }
245
+ Err(e) => return Err(e),
238
246
  }
239
247
  }
240
- EntryType::CodeownerFile(absolute_path, relative_path) => match std::fs::read_to_string(&absolute_path) {
241
- Ok(owner) => {
242
- let owner = owner.trim().to_owned();
243
- codeowners.push(DirectoryCodeownersFile {
244
- path: relative_path.clone(),
245
- owner,
246
- });
247
- }
248
- Err(e) => {
249
- warn!("Error reading codeowner file for {:?}: {:?}", absolute_path, e);
250
- }
251
- },
252
- EntryType::TeamFile(absolute_path, _relative_path) => match Team::from_team_file_path(absolute_path) {
253
- Ok(team) => {
254
- team_files.push(team);
255
- }
256
- Err(e) => {
257
- warn!("Error building team from team file path: {}", e);
258
- }
259
- },
248
+ EntryType::CodeownerFile(absolute_path, relative_path) => {
249
+ let owner = std::fs::read_to_string(&absolute_path)
250
+ .change_context(Error::Io)
251
+ .attach_printable_lazy(|| format!("Failed to read codeowner file: {}", absolute_path.display()))?;
252
+ let owner = owner.trim().to_owned();
253
+ codeowners.push(DirectoryCodeownersFile {
254
+ path: relative_path.clone(),
255
+ owner,
256
+ });
257
+ }
258
+ EntryType::TeamFile(absolute_path, _relative_path) => {
259
+ let team = Team::from_team_file_path(absolute_path.clone())
260
+ .change_context(Error::Io)
261
+ .attach_printable_lazy(|| format!("Failed to read team file: {}", absolute_path.display()))?;
262
+ team_files.push(team);
263
+ }
260
264
  EntryType::NullEntry() => {}
261
265
  }
262
- (project_files, pkgs, gems, codeowners, team_files)
266
+ Ok((project_files, pkgs, gems, codeowners, team_files))
263
267
  },
264
268
  )
265
- .reduce(
269
+ .try_reduce(
266
270
  || (Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new()),
267
271
  |mut acc, item| {
268
272
  acc.0.extend(item.0);
@@ -270,9 +274,9 @@ impl<'a> ProjectBuilder<'a> {
270
274
  acc.2.extend(item.2);
271
275
  acc.3.extend(item.3);
272
276
  acc.4.extend(item.4);
273
- acc
277
+ Ok(acc)
274
278
  },
275
- );
279
+ )?;
276
280
  let teams_by_name = teams
277
281
  .iter()
278
282
  .flat_map(|team| vec![(team.name.clone(), team.clone()), (team.github_team.clone(), team.clone())])
@@ -294,6 +298,7 @@ impl<'a> ProjectBuilder<'a> {
294
298
  codeowners_file_path: self.codeowners_file_path.to_path_buf(),
295
299
  directory_codeowner_files: directory_codeowners,
296
300
  teams_by_name,
301
+ executable_name: self.config.executable_name.clone(),
297
302
  })
298
303
  }
299
304
  }
@@ -309,7 +314,19 @@ fn ruby_package_owner(path: &Path) -> Result<Option<String>, Error> {
309
314
  let file = File::open(path).change_context(Error::Io)?;
310
315
  let deserializer: deserializers::RubyPackage = serde_yaml::from_reader(file).change_context(Error::SerdeYaml)?;
311
316
 
312
- Ok(deserializer.owner)
317
+ let top_level_owner = deserializer.owner;
318
+ let metadata_owner = deserializer.metadata.and_then(|metadata| metadata.owner);
319
+
320
+ // Error if both are present with different values
321
+ match (top_level_owner.as_ref(), metadata_owner.as_ref()) {
322
+ (Some(top), Some(meta)) if top != meta => Err(error_stack::report!(Error::Io).attach_printable(format!(
323
+ "Package at {} has conflicting owners: 'owner: {}' vs 'metadata.owner: {}'. Please use only one.",
324
+ path.display(),
325
+ top,
326
+ meta
327
+ ))),
328
+ _ => Ok(top_level_owner.or(metadata_owner)),
329
+ }
313
330
  }
314
331
 
315
332
  fn javascript_package_owner(path: &Path) -> Result<Option<String>, Error> {
@@ -334,4 +351,54 @@ mod tests {
334
351
  fn test_glob_match() {
335
352
  assert!(glob_match(OWNED_GLOB, "script/.eslintrc.js"));
336
353
  }
354
+
355
+ #[test]
356
+ fn test_ruby_package_owner_top_level() {
357
+ let yaml = "owner: TeamA\n";
358
+ let temp_file = tempfile::NamedTempFile::new().unwrap();
359
+ std::fs::write(temp_file.path(), yaml).unwrap();
360
+
361
+ let owner = ruby_package_owner(temp_file.path()).unwrap();
362
+ assert_eq!(owner, Some("TeamA".to_string()));
363
+ }
364
+
365
+ #[test]
366
+ fn test_ruby_package_owner_metadata() {
367
+ let yaml = "metadata:\n owner: TeamB\n";
368
+ let temp_file = tempfile::NamedTempFile::new().unwrap();
369
+ std::fs::write(temp_file.path(), yaml).unwrap();
370
+
371
+ let owner = ruby_package_owner(temp_file.path()).unwrap();
372
+ assert_eq!(owner, Some("TeamB".to_string()));
373
+ }
374
+
375
+ #[test]
376
+ fn test_ruby_package_owner_errors_when_both_present_and_different() {
377
+ let yaml = "owner: TeamA\nmetadata:\n owner: TeamB\n";
378
+ let temp_file = tempfile::NamedTempFile::new().unwrap();
379
+ std::fs::write(temp_file.path(), yaml).unwrap();
380
+
381
+ let result = ruby_package_owner(temp_file.path());
382
+ assert!(result.is_err());
383
+ }
384
+
385
+ #[test]
386
+ fn test_ruby_package_owner_allows_both_when_same() {
387
+ let yaml = "owner: TeamA\nmetadata:\n owner: TeamA\n";
388
+ let temp_file = tempfile::NamedTempFile::new().unwrap();
389
+ std::fs::write(temp_file.path(), yaml).unwrap();
390
+
391
+ let owner = ruby_package_owner(temp_file.path()).unwrap();
392
+ assert_eq!(owner, Some("TeamA".to_string()));
393
+ }
394
+
395
+ #[test]
396
+ fn test_ruby_package_owner_no_owner() {
397
+ let yaml = "name: my_package\n";
398
+ let temp_file = tempfile::NamedTempFile::new().unwrap();
399
+ std::fs::write(temp_file.path(), yaml).unwrap();
400
+
401
+ let owner = ruby_package_owner(temp_file.path()).unwrap();
402
+ assert_eq!(owner, None);
403
+ }
337
404
  }
@@ -1,9 +1,9 @@
1
1
  use std::collections::HashMap;
2
2
 
3
- use crate::ownership::FileOwner;
4
3
  use crate::project::Team;
4
+ use crate::{ownership::FileOwner, runner::config_from_run_config};
5
5
 
6
- use super::{Error, ForFileResult, RunConfig, RunResult, config_from_path, run};
6
+ use super::{Error, ForFileResult, RunConfig, RunResult, run};
7
7
 
8
8
  pub fn for_file(run_config: &RunConfig, file_path: &str, from_codeowners: bool, json: bool) -> RunResult {
9
9
  if from_codeowners {
@@ -38,7 +38,7 @@ pub fn crosscheck_owners(run_config: &RunConfig) -> RunResult {
38
38
 
39
39
  // Returns all owners for a file without creating a Runner (performance optimized)
40
40
  pub fn owners_for_file(run_config: &RunConfig, file_path: &str) -> error_stack::Result<Vec<FileOwner>, Error> {
41
- let config = config_from_path(&run_config.config_path)?;
41
+ let config = config_from_run_config(run_config)?;
42
42
  use crate::ownership::file_owner_resolver::find_file_owners;
43
43
  let owners = find_file_owners(&run_config.project_root, &config, std::path::Path::new(file_path)).map_err(Error::Io)?;
44
44
  Ok(owners)
@@ -60,10 +60,11 @@ pub fn teams_for_files_from_codeowners(
60
60
  run_config: &RunConfig,
61
61
  file_paths: &[String],
62
62
  ) -> error_stack::Result<HashMap<String, Option<Team>>, Error> {
63
- let config = config_from_path(&run_config.config_path)?;
63
+ let config = config_from_run_config(run_config)?;
64
+ let codeowners_file_path = super::resolve_codeowners_file_path(run_config, &config);
64
65
  let res = crate::ownership::codeowners_query::teams_for_files_from_codeowners(
65
66
  &run_config.project_root,
66
- &run_config.codeowners_file_path,
67
+ &codeowners_file_path,
67
68
  &config.team_file_glob,
68
69
  file_paths,
69
70
  )
@@ -80,7 +81,7 @@ pub fn team_for_file_from_codeowners(run_config: &RunConfig, file_path: &str) ->
80
81
 
81
82
  // Fast path that avoids creating a full Runner for single file queries
82
83
  fn for_file_optimized(run_config: &RunConfig, file_path: &str, json: bool) -> RunResult {
83
- let config = match config_from_path(&run_config.config_path) {
84
+ let config = match config_from_run_config(run_config) {
84
85
  Ok(c) => c,
85
86
  Err(err) => {
86
87
  return RunResult::from_io_error(Error::Io(err.to_string()), json);
@@ -14,9 +14,10 @@ pub struct RunResult {
14
14
  #[derive(Debug, Clone)]
15
15
  pub struct RunConfig {
16
16
  pub project_root: PathBuf,
17
- pub codeowners_file_path: PathBuf,
17
+ pub codeowners_file_path: Option<PathBuf>,
18
18
  pub config_path: PathBuf,
19
19
  pub no_cache: bool,
20
+ pub executable_name: Option<String>,
20
21
  }
21
22
 
22
23
  #[derive(Debug, Serialize)]