git 4.3.2 → 5.0.0.beta.2

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 (297) hide show
  1. checksums.yaml +4 -4
  2. data/.github/copilot-instructions.md +73 -2705
  3. data/.github/prompts/iteratively-address-copilot-reviews.prompt.md +188 -0
  4. data/.github/pull_request_template.md +3 -1
  5. data/.github/skills/breaking-change-analysis/SKILL.md +102 -0
  6. data/.github/skills/ci-cd-troubleshooting/SKILL.md +264 -0
  7. data/.github/skills/command-implementation/REFERENCE.md +993 -0
  8. data/.github/skills/command-implementation/SKILL.md +229 -0
  9. data/.github/skills/command-test-conventions/SKILL.md +660 -0
  10. data/.github/skills/command-yard-documentation/SKILL.md +426 -0
  11. data/.github/skills/dependency-management/SKILL.md +72 -0
  12. data/.github/skills/development-workflow/SKILL.md +506 -0
  13. data/.github/skills/extract-command-from-lib/SKILL.md +487 -0
  14. data/.github/skills/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +22 -0
  15. data/.github/skills/extract-facade-from-base-lib/SKILL.md +600 -0
  16. data/.github/skills/facade-implementation/REFERENCE.md +840 -0
  17. data/.github/skills/facade-implementation/SKILL.md +274 -0
  18. data/.github/skills/facade-test-conventions/SKILL.md +394 -0
  19. data/.github/skills/facade-yard-documentation/SKILL.md +429 -0
  20. data/.github/skills/make-skill-template/SKILL.md +176 -0
  21. data/.github/skills/pr-readiness-review/SKILL.md +185 -0
  22. data/.github/skills/project-context/SKILL.md +313 -0
  23. data/.github/skills/pull-request-review/SKILL.md +168 -0
  24. data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +131 -0
  25. data/.github/skills/release-management/SKILL.md +125 -0
  26. data/.github/skills/review-arguments-dsl/CHECKLIST.md +788 -0
  27. data/.github/skills/review-arguments-dsl/SKILL.md +214 -0
  28. data/.github/skills/review-backward-compatibility/SKILL.md +275 -0
  29. data/.github/skills/review-cross-command-consistency/SKILL.md +139 -0
  30. data/.github/skills/reviewing-skills/SKILL.md +189 -0
  31. data/.github/skills/rspec-unit-testing-standards/SKILL.md +639 -0
  32. data/.github/skills/tdd-refactor-step/SKILL.md +236 -0
  33. data/.github/skills/test-debugging/SKILL.md +160 -0
  34. data/.github/skills/yard-documentation/SKILL.md +793 -0
  35. data/.github/workflows/continuous_integration.yml +3 -2
  36. data/.github/workflows/enforce_conventional_commits.yml +1 -1
  37. data/.github/workflows/experimental_continuous_integration.yml +2 -2
  38. data/.github/workflows/release.yml +3 -4
  39. data/.gitignore +8 -0
  40. data/.husky/pre-commit +13 -0
  41. data/.release-please-manifest.json +1 -1
  42. data/.rspec +3 -0
  43. data/.rubocop.yml +12 -3
  44. data/.rubocop_todo.yml +23 -5
  45. data/.yardopts +1 -0
  46. data/CHANGELOG.md +0 -40
  47. data/CONTRIBUTING.md +694 -53
  48. data/README.md +67 -15
  49. data/Rakefile +61 -9
  50. data/UPGRADING.md +141 -0
  51. data/commitlint.test +4 -0
  52. data/git.gemspec +18 -7
  53. data/lib/git/args_builder.rb +0 -8
  54. data/lib/git/branch.rb +369 -43
  55. data/lib/git/branch_delete_failure.rb +31 -0
  56. data/lib/git/branch_delete_result.rb +63 -0
  57. data/lib/git/branch_info.rb +178 -0
  58. data/lib/git/branches.rb +122 -24
  59. data/lib/git/command_line/base.rb +255 -0
  60. data/lib/git/command_line/capturing.rb +251 -0
  61. data/lib/git/command_line/result.rb +96 -0
  62. data/lib/git/command_line/streaming.rb +196 -0
  63. data/lib/git/command_line.rb +43 -322
  64. data/lib/git/command_line_result.rb +4 -88
  65. data/lib/git/commands/add.rb +131 -0
  66. data/lib/git/commands/am/abort.rb +43 -0
  67. data/lib/git/commands/am/apply.rb +252 -0
  68. data/lib/git/commands/am/continue.rb +43 -0
  69. data/lib/git/commands/am/quit.rb +43 -0
  70. data/lib/git/commands/am/retry.rb +47 -0
  71. data/lib/git/commands/am/show_current_patch.rb +64 -0
  72. data/lib/git/commands/am/skip.rb +42 -0
  73. data/lib/git/commands/am.rb +33 -0
  74. data/lib/git/commands/apply.rb +237 -0
  75. data/lib/git/commands/archive/list_formats.rb +46 -0
  76. data/lib/git/commands/archive.rb +140 -0
  77. data/lib/git/commands/arguments.rb +3510 -0
  78. data/lib/git/commands/base.rb +404 -0
  79. data/lib/git/commands/branch/copy.rb +94 -0
  80. data/lib/git/commands/branch/create.rb +173 -0
  81. data/lib/git/commands/branch/delete.rb +80 -0
  82. data/lib/git/commands/branch/list.rb +162 -0
  83. data/lib/git/commands/branch/move.rb +94 -0
  84. data/lib/git/commands/branch/set_upstream.rb +86 -0
  85. data/lib/git/commands/branch/show_current.rb +49 -0
  86. data/lib/git/commands/branch/unset_upstream.rb +57 -0
  87. data/lib/git/commands/branch.rb +34 -0
  88. data/lib/git/commands/cat_file/batch.rb +369 -0
  89. data/lib/git/commands/cat_file/filtered.rb +105 -0
  90. data/lib/git/commands/cat_file/raw.rb +216 -0
  91. data/lib/git/commands/cat_file.rb +49 -0
  92. data/lib/git/commands/checkout/branch.rb +151 -0
  93. data/lib/git/commands/checkout/files.rb +115 -0
  94. data/lib/git/commands/checkout.rb +38 -0
  95. data/lib/git/commands/checkout_index.rb +105 -0
  96. data/lib/git/commands/clean.rb +100 -0
  97. data/lib/git/commands/clone.rb +240 -0
  98. data/lib/git/commands/commit.rb +272 -0
  99. data/lib/git/commands/commit_tree.rb +100 -0
  100. data/lib/git/commands/config_option_syntax/add.rb +83 -0
  101. data/lib/git/commands/config_option_syntax/get.rb +117 -0
  102. data/lib/git/commands/config_option_syntax/get_all.rb +115 -0
  103. data/lib/git/commands/config_option_syntax/get_color.rb +91 -0
  104. data/lib/git/commands/config_option_syntax/get_color_bool.rb +93 -0
  105. data/lib/git/commands/config_option_syntax/get_regexp.rb +115 -0
  106. data/lib/git/commands/config_option_syntax/get_urlmatch.rb +107 -0
  107. data/lib/git/commands/config_option_syntax/list.rb +107 -0
  108. data/lib/git/commands/config_option_syntax/remove_section.rb +74 -0
  109. data/lib/git/commands/config_option_syntax/rename_section.rb +78 -0
  110. data/lib/git/commands/config_option_syntax/replace_all.rb +104 -0
  111. data/lib/git/commands/config_option_syntax/set.rb +114 -0
  112. data/lib/git/commands/config_option_syntax/unset.rb +89 -0
  113. data/lib/git/commands/config_option_syntax/unset_all.rb +89 -0
  114. data/lib/git/commands/config_option_syntax.rb +56 -0
  115. data/lib/git/commands/describe.rb +155 -0
  116. data/lib/git/commands/diff.rb +656 -0
  117. data/lib/git/commands/diff_files.rb +518 -0
  118. data/lib/git/commands/diff_index.rb +496 -0
  119. data/lib/git/commands/fetch.rb +352 -0
  120. data/lib/git/commands/fsck.rb +136 -0
  121. data/lib/git/commands/gc.rb +132 -0
  122. data/lib/git/commands/grep.rb +338 -0
  123. data/lib/git/commands/init.rb +99 -0
  124. data/lib/git/commands/log.rb +632 -0
  125. data/lib/git/commands/ls_files.rb +191 -0
  126. data/lib/git/commands/ls_remote.rb +155 -0
  127. data/lib/git/commands/ls_tree.rb +131 -0
  128. data/lib/git/commands/maintenance/register.rb +75 -0
  129. data/lib/git/commands/maintenance/run.rb +104 -0
  130. data/lib/git/commands/maintenance/start.rb +66 -0
  131. data/lib/git/commands/maintenance/stop.rb +55 -0
  132. data/lib/git/commands/maintenance/unregister.rb +79 -0
  133. data/lib/git/commands/maintenance.rb +31 -0
  134. data/lib/git/commands/merge/abort.rb +44 -0
  135. data/lib/git/commands/merge/continue.rb +44 -0
  136. data/lib/git/commands/merge/quit.rb +46 -0
  137. data/lib/git/commands/merge/start.rb +245 -0
  138. data/lib/git/commands/merge.rb +28 -0
  139. data/lib/git/commands/merge_base.rb +86 -0
  140. data/lib/git/commands/mv.rb +77 -0
  141. data/lib/git/commands/name_rev.rb +114 -0
  142. data/lib/git/commands/pull.rb +377 -0
  143. data/lib/git/commands/push.rb +246 -0
  144. data/lib/git/commands/read_tree.rb +149 -0
  145. data/lib/git/commands/remote/add.rb +91 -0
  146. data/lib/git/commands/remote/get_url.rb +66 -0
  147. data/lib/git/commands/remote/list.rb +54 -0
  148. data/lib/git/commands/remote/prune.rb +61 -0
  149. data/lib/git/commands/remote/remove.rb +52 -0
  150. data/lib/git/commands/remote/rename.rb +69 -0
  151. data/lib/git/commands/remote/set_branches.rb +63 -0
  152. data/lib/git/commands/remote/set_head.rb +82 -0
  153. data/lib/git/commands/remote/set_url.rb +71 -0
  154. data/lib/git/commands/remote/set_url_add.rb +61 -0
  155. data/lib/git/commands/remote/set_url_delete.rb +64 -0
  156. data/lib/git/commands/remote/show.rb +71 -0
  157. data/lib/git/commands/remote/update.rb +72 -0
  158. data/lib/git/commands/remote.rb +42 -0
  159. data/lib/git/commands/repack.rb +277 -0
  160. data/lib/git/commands/reset.rb +147 -0
  161. data/lib/git/commands/rev_parse.rb +297 -0
  162. data/lib/git/commands/revert/abort.rb +45 -0
  163. data/lib/git/commands/revert/continue.rb +57 -0
  164. data/lib/git/commands/revert/quit.rb +47 -0
  165. data/lib/git/commands/revert/skip.rb +44 -0
  166. data/lib/git/commands/revert/start.rb +153 -0
  167. data/lib/git/commands/revert.rb +29 -0
  168. data/lib/git/commands/rm.rb +114 -0
  169. data/lib/git/commands/show.rb +632 -0
  170. data/lib/git/commands/show_ref/exclude_existing.rb +120 -0
  171. data/lib/git/commands/show_ref/exists.rb +78 -0
  172. data/lib/git/commands/show_ref/list.rb +145 -0
  173. data/lib/git/commands/show_ref/verify.rb +120 -0
  174. data/lib/git/commands/show_ref.rb +42 -0
  175. data/lib/git/commands/stash/apply.rb +75 -0
  176. data/lib/git/commands/stash/branch.rb +65 -0
  177. data/lib/git/commands/stash/clear.rb +41 -0
  178. data/lib/git/commands/stash/create.rb +58 -0
  179. data/lib/git/commands/stash/drop.rb +67 -0
  180. data/lib/git/commands/stash/list.rb +39 -0
  181. data/lib/git/commands/stash/pop.rb +78 -0
  182. data/lib/git/commands/stash/push.rb +103 -0
  183. data/lib/git/commands/stash/show.rb +149 -0
  184. data/lib/git/commands/stash/store.rb +63 -0
  185. data/lib/git/commands/stash.rb +38 -0
  186. data/lib/git/commands/status.rb +169 -0
  187. data/lib/git/commands/symbolic_ref/delete.rb +68 -0
  188. data/lib/git/commands/symbolic_ref/read.rb +95 -0
  189. data/lib/git/commands/symbolic_ref/update.rb +76 -0
  190. data/lib/git/commands/symbolic_ref.rb +38 -0
  191. data/lib/git/commands/tag/create.rb +139 -0
  192. data/lib/git/commands/tag/delete.rb +55 -0
  193. data/lib/git/commands/tag/list.rb +143 -0
  194. data/lib/git/commands/tag/verify.rb +71 -0
  195. data/lib/git/commands/tag.rb +26 -0
  196. data/lib/git/commands/update_ref/batch.rb +140 -0
  197. data/lib/git/commands/update_ref/delete.rb +92 -0
  198. data/lib/git/commands/update_ref/update.rb +106 -0
  199. data/lib/git/commands/update_ref.rb +42 -0
  200. data/lib/git/commands/version.rb +57 -0
  201. data/lib/git/commands/worktree/add.rb +140 -0
  202. data/lib/git/commands/worktree/list.rb +64 -0
  203. data/lib/git/commands/worktree/lock.rb +58 -0
  204. data/lib/git/commands/worktree/management_base.rb +51 -0
  205. data/lib/git/commands/worktree/move.rb +66 -0
  206. data/lib/git/commands/worktree/prune.rb +67 -0
  207. data/lib/git/commands/worktree/remove.rb +63 -0
  208. data/lib/git/commands/worktree/repair.rb +76 -0
  209. data/lib/git/commands/worktree/unlock.rb +47 -0
  210. data/lib/git/commands/worktree.rb +43 -0
  211. data/lib/git/commands/write_tree.rb +68 -0
  212. data/lib/git/commands.rb +87 -0
  213. data/lib/git/config.rb +17 -0
  214. data/lib/git/config_entry_info.rb +106 -0
  215. data/lib/git/configuring.rb +665 -0
  216. data/lib/git/deprecation.rb +9 -0
  217. data/lib/git/detached_head_info.rb +54 -0
  218. data/lib/git/diff.rb +293 -7
  219. data/lib/git/diff_file_numstat_info.rb +29 -0
  220. data/lib/git/diff_file_patch_info.rb +134 -0
  221. data/lib/git/diff_file_raw_info.rb +127 -0
  222. data/lib/git/diff_info.rb +169 -0
  223. data/lib/git/diff_path_status.rb +67 -19
  224. data/lib/git/diff_result.rb +32 -0
  225. data/lib/git/diff_stats.rb +51 -14
  226. data/lib/git/dirstat_info.rb +86 -0
  227. data/lib/git/errors.rb +65 -2
  228. data/lib/git/execution_context/global.rb +31 -0
  229. data/lib/git/execution_context/repository.rb +136 -0
  230. data/lib/git/execution_context.rb +501 -0
  231. data/lib/git/file_ref.rb +74 -0
  232. data/lib/git/fsck_object.rb +9 -9
  233. data/lib/git/fsck_result.rb +1 -1
  234. data/lib/git/log.rb +9 -2
  235. data/lib/git/object.rb +90 -27
  236. data/lib/git/parsers/branch.rb +224 -0
  237. data/lib/git/parsers/cat_file.rb +111 -0
  238. data/lib/git/parsers/config_entry.rb +110 -0
  239. data/lib/git/parsers/diff.rb +585 -0
  240. data/lib/git/parsers/fsck.rb +133 -0
  241. data/lib/git/parsers/grep.rb +42 -0
  242. data/lib/git/parsers/ls_remote.rb +79 -0
  243. data/lib/git/parsers/ls_tree.rb +58 -0
  244. data/lib/git/parsers/stash.rb +208 -0
  245. data/lib/git/parsers/tag.rb +257 -0
  246. data/lib/git/remote.rb +120 -9
  247. data/lib/git/repository/branching.rb +743 -0
  248. data/lib/git/repository/committing.rb +187 -0
  249. data/lib/git/repository/configuring.rb +351 -0
  250. data/lib/git/repository/context_helpers.rb +264 -0
  251. data/lib/git/repository/diffing.rb +775 -0
  252. data/lib/git/repository/factories.rb +682 -0
  253. data/lib/git/repository/inspecting.rb +252 -0
  254. data/lib/git/repository/logging.rb +247 -0
  255. data/lib/git/repository/maintenance.rb +65 -0
  256. data/lib/git/repository/merging.rb +357 -0
  257. data/lib/git/repository/object_operations.rb +1199 -0
  258. data/lib/git/repository/path_resolver.rb +207 -0
  259. data/lib/git/repository/remote_operations.rb +898 -0
  260. data/lib/git/repository/shared_private.rb +51 -0
  261. data/lib/git/repository/staging.rb +554 -0
  262. data/lib/git/repository/stashing.rb +143 -0
  263. data/lib/git/repository/status_operations.rb +201 -0
  264. data/lib/git/repository/worktree_operations.rb +159 -0
  265. data/lib/git/repository.rb +203 -1
  266. data/lib/git/stash.rb +78 -4
  267. data/lib/git/stash_info.rb +104 -0
  268. data/lib/git/stashes.rb +125 -13
  269. data/lib/git/status.rb +215 -18
  270. data/lib/git/tag_delete_failure.rb +31 -0
  271. data/lib/git/tag_delete_result.rb +63 -0
  272. data/lib/git/tag_info.rb +105 -0
  273. data/lib/git/version.rb +109 -2
  274. data/lib/git/version_constraint.rb +81 -0
  275. data/lib/git/worktree.rb +108 -6
  276. data/lib/git/worktrees.rb +94 -7
  277. data/lib/git.rb +260 -61
  278. data/redesign/1_architecture_existing.md +54 -18
  279. data/redesign/2_architecture_redesign.md +365 -46
  280. data/redesign/3_architecture_implementation.md +1497 -63
  281. data/redesign/Phase 4 - Step A.md +360 -0
  282. data/redesign/beta_release.md +107 -0
  283. data/redesign/c1c2_audit.md +566 -0
  284. data/redesign/c1c2_bucket6_lib_orphans.md +626 -0
  285. data/redesign/config_design.rb +501 -0
  286. data/tasks/gem_tasks.rake +4 -0
  287. data/tasks/npm_tasks.rake +7 -0
  288. data/tasks/rspec.rake +48 -0
  289. data/tasks/test.rake +13 -1
  290. data/tasks/yard.rake +34 -7
  291. metadata +365 -22
  292. data/lib/git/base.rb +0 -1128
  293. data/lib/git/index.rb +0 -6
  294. data/lib/git/lib.rb +0 -2277
  295. data/lib/git/path.rb +0 -38
  296. data/lib/git/working_directory.rb +0 -6
  297. /data/{release-please-config.json → .release-please-config.json} +0 -0
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ class Repository
5
+ # Internal helpers shared by `Git::Repository::*` topic modules
6
+ #
7
+ # Methods defined here use `module_function` so they are callable as
8
+ # `SharedPrivate.foo(...)` from any topic module within `Git::Repository`
9
+ # without being added to `Git::Repository`'s instance namespace via `include`.
10
+ #
11
+ # The constant is declared `private_constant` so it is inaccessible from
12
+ # outside the `Git::Repository` class body; callers inside topic modules use
13
+ # the short unqualified form `SharedPrivate.foo(...)`.
14
+ #
15
+ # @api private
16
+ #
17
+ module SharedPrivate
18
+ module_function
19
+
20
+ # Validate that `options` contains only keys listed in `allowed`
21
+ #
22
+ # Used by facade methods to enforce that only documented options (those
23
+ # named in `@option` tags) are accepted, even when the underlying command
24
+ # class would accept more keys. This prevents silent expansion of the
25
+ # facade's public contract.
26
+ #
27
+ # @example Reject an undocumented option
28
+ # ADD_ALLOWED_OPTS = %i[all force].freeze
29
+ #
30
+ # SharedPrivate.assert_valid_opts!(ADD_ALLOWED_OPTS, bogus: true)
31
+ # #=> raises ArgumentError: Unknown options: bogus
32
+ #
33
+ # @param allowed [Array<Symbol>] the keys permitted by the facade method
34
+ #
35
+ # @param options [Hash] the options hash provided by the caller
36
+ #
37
+ # @return [void]
38
+ #
39
+ # @raise [ArgumentError] when `options` contains any key not in `allowed`
40
+ #
41
+ def assert_valid_opts!(allowed, **options)
42
+ unknown = options.keys - allowed
43
+ return if unknown.empty?
44
+
45
+ raise ArgumentError, "Unknown options: #{unknown.join(', ')}"
46
+ end
47
+ end
48
+
49
+ private_constant :SharedPrivate
50
+ end
51
+ end
@@ -0,0 +1,554 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git/commands/add'
4
+ require 'git/commands/am/apply'
5
+ require 'git/commands/apply'
6
+ require 'git/commands/clean'
7
+ require 'git/commands/ls_files'
8
+ require 'git/commands/mv'
9
+ require 'git/commands/read_tree'
10
+ require 'git/commands/reset'
11
+ require 'git/commands/rm'
12
+ require 'git/escaped_path'
13
+ require 'git/repository/shared_private'
14
+
15
+ module Git
16
+ class Repository
17
+ # Facade methods for staging-area operations: adding, resetting, moving,
18
+ # removing, and cleaning files
19
+ #
20
+ # Included by {Git::Repository}.
21
+ #
22
+ # @api public
23
+ #
24
+ module Staging
25
+ # Option keys accepted by {#add}
26
+ ADD_ALLOWED_OPTS = %i[all force].freeze
27
+ private_constant :ADD_ALLOWED_OPTS
28
+
29
+ # Update the index with the current content found in the working tree
30
+ #
31
+ # @overload add(paths = '.', **options)
32
+ #
33
+ # @example Stage all changed files
34
+ # repo.add
35
+ #
36
+ # @example Stage a specific file
37
+ # repo.add('README.md')
38
+ #
39
+ # @example Stage all changes including deletions
40
+ # repo.add(all: true)
41
+ #
42
+ # @param paths [String, Array<String>] a file or files to add (relative to
43
+ # the worktree root); defaults to `'.'` (all files)
44
+ #
45
+ # @param options [Hash] options for the add command
46
+ #
47
+ # @option options [Boolean, nil] :all (nil) add, modify, and remove index
48
+ # entries to match the worktree
49
+ #
50
+ # @option options [Boolean, nil] :force (nil) allow adding otherwise ignored
51
+ # files
52
+ #
53
+ # @return [String] git's stdout from the add
54
+ #
55
+ # @raise [ArgumentError] when unsupported options are provided
56
+ #
57
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
58
+ #
59
+ def add(paths = '.', **)
60
+ SharedPrivate.assert_valid_opts!(ADD_ALLOWED_OPTS, **)
61
+ Git::Commands::Add.new(@execution_context).call(*Array(paths), **).stdout
62
+ end
63
+
64
+ # Option keys accepted by {#reset}
65
+ RESET_ALLOWED_OPTS = %i[hard].freeze
66
+ private_constant :RESET_ALLOWED_OPTS
67
+
68
+ # Reset the current HEAD to a specified state
69
+ #
70
+ # @example Reset the index and working tree to HEAD
71
+ # repo.reset
72
+ #
73
+ # @example Hard reset to a specific commit
74
+ # repo.reset('HEAD~1', hard: true)
75
+ #
76
+ # @param commitish [String, nil] the commit or tree-ish to reset to;
77
+ # defaults to HEAD when `nil`
78
+ #
79
+ # @param opts [Hash] options for the reset command
80
+ #
81
+ # @option opts [Boolean, nil] :hard (nil) reset the index and working
82
+ # tree; discards all tracked changes
83
+ #
84
+ # @return [String] git's stdout from the reset
85
+ #
86
+ # @raise [ArgumentError] when unsupported options are provided
87
+ #
88
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
89
+ #
90
+ def reset(commitish = nil, opts = {})
91
+ SharedPrivate.assert_valid_opts!(RESET_ALLOWED_OPTS, **opts)
92
+ Git::Commands::Reset.new(@execution_context).call(commitish, **opts).stdout
93
+ end
94
+
95
+ # Reset the current HEAD to a specified state with `--hard`
96
+ #
97
+ # @deprecated Use {#reset} with `hard: true` instead.
98
+ #
99
+ # @example Hard reset to HEAD
100
+ # repo.reset_hard
101
+ #
102
+ # @example Hard reset to a specific commit
103
+ # repo.reset_hard('HEAD~1')
104
+ #
105
+ # @param commitish [String, nil] the commit or tree-ish to reset to;
106
+ # defaults to HEAD when `nil`
107
+ #
108
+ # @param opts [Hash] options passed through to {#reset}
109
+ #
110
+ # @return [String] git's stdout from the reset
111
+ #
112
+ # @raise [ArgumentError] when unsupported options are provided
113
+ #
114
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
115
+ #
116
+ def reset_hard(commitish = nil, opts = {})
117
+ Git::Deprecation.warn(
118
+ 'Git::Repository::Staging#reset_hard is deprecated and will be removed in a future version. ' \
119
+ 'Use #reset(commitish, hard: true) instead.'
120
+ )
121
+ reset(commitish, **opts, hard: true)
122
+ end
123
+
124
+ # Apply a patch file to the working tree
125
+ #
126
+ # Reads the unified diff in `file` and applies it to the working tree via
127
+ # `git apply`. If `file` does not exist, the method returns `nil` without
128
+ # calling git — preserving the 4.x `Git::Base#apply` no-op contract.
129
+ #
130
+ # @example Apply a patch to the working tree
131
+ # repo.apply('fix.patch')
132
+ #
133
+ # @param file [String] path to the patch file to apply
134
+ #
135
+ # @return [String] git's stdout (usually empty on success)
136
+ #
137
+ # @return [nil] when `file` does not exist
138
+ #
139
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
140
+ #
141
+ def apply(file)
142
+ return unless File.exist?(file)
143
+
144
+ Git::Commands::Apply.new(@execution_context).call(file, chdir: @execution_context.git_work_dir).stdout
145
+ end
146
+
147
+ # Apply a series of patches from a mailbox file to the current branch
148
+ #
149
+ # Reads the mbox-format file in `file` and applies the patches via
150
+ # `git am`. If `file` does not exist, the method returns `nil` without
151
+ # calling git — preserving the 4.x `Git::Base#apply_mail` no-op contract.
152
+ #
153
+ # @example Apply patches from a mailbox
154
+ # repo.apply_mail('patches.mbox')
155
+ #
156
+ # @param file [String] path to the mbox patch file to apply
157
+ #
158
+ # @return [String] git's stdout (usually empty on success)
159
+ #
160
+ # @return [nil] when `file` does not exist
161
+ #
162
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
163
+ #
164
+ def apply_mail(file)
165
+ return unless File.exist?(file)
166
+
167
+ Git::Commands::Am::Apply.new(@execution_context).call(file, chdir: @execution_context.git_work_dir).stdout
168
+ end
169
+
170
+ # Option keys accepted by {#read_tree}
171
+ READ_TREE_ALLOWED_OPTS = %i[prefix].freeze
172
+ private_constant :READ_TREE_ALLOWED_OPTS
173
+
174
+ # Read tree information into the index
175
+ #
176
+ # Reads the named tree object into the index. This is a low-level plumbing
177
+ # operation used to stage the contents of a tree without updating the
178
+ # working tree. Typically called before {#checkout_index} or as part of
179
+ # custom merge flows.
180
+ #
181
+ # @example Read HEAD into the index
182
+ # repo.read_tree('HEAD')
183
+ #
184
+ # @example Read a tree under a prefix directory
185
+ # repo.read_tree('HEAD', { prefix: 'subdir/' })
186
+ #
187
+ # @param treeish [String] the tree-ish to read into the index
188
+ #
189
+ # @param opts [Hash] options for the read-tree command
190
+ #
191
+ # @option opts [String] :prefix (nil) keep the current index contents and
192
+ # read the named tree-ish under the directory at the given prefix
193
+ # (`--prefix=<prefix>`)
194
+ #
195
+ # @return [String] git's stdout (usually empty on success)
196
+ #
197
+ # @raise [ArgumentError] when unsupported options are provided
198
+ #
199
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
200
+ #
201
+ def read_tree(treeish, opts = {})
202
+ SharedPrivate.assert_valid_opts!(READ_TREE_ALLOWED_OPTS, **opts)
203
+ Git::Commands::ReadTree.new(@execution_context).call(treeish, **opts).stdout
204
+ end
205
+
206
+ # Option keys accepted by {#rm}
207
+ RM_ALLOWED_OPTS = %i[
208
+ force f dry_run n r cached ignore_unmatch sparse quiet q
209
+ pathspec_from_file pathspec_file_nul
210
+ ].freeze
211
+ private_constant :RM_ALLOWED_OPTS
212
+
213
+ # Remove file(s) from the working tree and the index
214
+ #
215
+ # @example Remove a single file
216
+ # repo.rm('obsolete.txt', { force: true })
217
+ #
218
+ # @example Remove a directory recursively
219
+ # repo.rm('build', { r: true })
220
+ #
221
+ # @example Remove from the index only, keeping the working tree copy
222
+ # repo.rm('keep_me.txt', { cached: true })
223
+ #
224
+ # @param path [String, Array<String>] a file or files to remove (relative to
225
+ # the worktree root); defaults to `'.'` (all files)
226
+ #
227
+ # @param opts [Hash] options for the rm command
228
+ #
229
+ # @option opts [Boolean, nil] :force (nil) override the up-to-date check and
230
+ # remove files with local modifications (alias: `:f`)
231
+ #
232
+ # @option opts [Boolean, nil] :f (nil) alias for `:force`
233
+ #
234
+ # @option opts [Boolean, nil] :dry_run (nil) do not actually remove any files;
235
+ # only show what would be removed (alias: `:n`)
236
+ #
237
+ # @option opts [Boolean, nil] :n (nil) alias for `:dry_run`
238
+ #
239
+ # @option opts [Boolean, nil] :r (nil) allow recursive removal when a leading
240
+ # directory name is given
241
+ #
242
+ # @option opts [Boolean, nil] :cached (nil) only remove from the index, keeping
243
+ # the working tree files
244
+ #
245
+ # @option opts [Boolean, nil] :ignore_unmatch (nil) exit with a zero status even
246
+ # if no files matched
247
+ #
248
+ # @option opts [Boolean, nil] :sparse (nil) allow updating index entries outside
249
+ # of the sparse-checkout cone
250
+ #
251
+ # @option opts [Boolean, nil] :quiet (nil) suppress the one-line-per-file output
252
+ # (alias: `:q`)
253
+ #
254
+ # @option opts [Boolean, nil] :q (nil) alias for `:quiet`
255
+ #
256
+ # @option opts [String] :pathspec_from_file (nil) read pathspec from the given
257
+ # file, one pathspec element per line; pass `-` to read from standard input
258
+ #
259
+ # @option opts [Boolean, nil] :pathspec_file_nul (nil) when used with
260
+ # `:pathspec_from_file`, separate pathspec elements with NUL instead of newlines
261
+ #
262
+ # @return [String] git's stdout from the rm
263
+ #
264
+ # @raise [ArgumentError] when unsupported options are provided
265
+ #
266
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
267
+ #
268
+ def rm(path = '.', opts = {})
269
+ SharedPrivate.assert_valid_opts!(RM_ALLOWED_OPTS, **opts)
270
+ Git::Commands::Rm.new(@execution_context).call(*Array(path), **opts).stdout
271
+ end
272
+
273
+ alias remove rm
274
+
275
+ # Option keys accepted by {#mv}
276
+ MV_ALLOWED_OPTS = %i[force f dry_run n k].freeze
277
+ private_constant :MV_ALLOWED_OPTS
278
+
279
+ # Move or rename a file, directory, or symlink in the working tree
280
+ #
281
+ # Updates the index after successful completion, but the change must still
282
+ # be committed.
283
+ #
284
+ # @example Move a single file
285
+ # repo.mv('old.rb', 'new.rb')
286
+ #
287
+ # @example Move multiple files to a directory
288
+ # repo.mv(['file1.rb', 'file2.rb'], 'destination_dir/')
289
+ #
290
+ # @example Force overwrite if destination exists
291
+ # repo.mv('source.rb', 'dest.rb', force: true)
292
+ #
293
+ # @param source [String, Array<String>] one or more source file(s),
294
+ # directory(ies), or symlink(s) to move (relative to the worktree root)
295
+ #
296
+ # @param destination [String] the destination file or directory
297
+ #
298
+ # @param options [Hash] options for the mv command
299
+ #
300
+ # @option options [Boolean, nil] :force (nil) force renaming or moving even
301
+ # if the destination exists (alias: `:f`)
302
+ #
303
+ # @option options [Boolean, nil] :f (nil) alias for `:force`
304
+ #
305
+ # @option options [Boolean, nil] :dry_run (nil) do not actually move any
306
+ # files; only show what would happen (alias: `:n`)
307
+ #
308
+ # @option options [Boolean, nil] :n (nil) alias for `:dry_run`
309
+ #
310
+ # @option options [Boolean, nil] :k (nil) skip move or rename actions which
311
+ # would lead to an error
312
+ #
313
+ # @return [String] git's stdout from the mv command
314
+ #
315
+ # @raise [ArgumentError] when unsupported options are provided
316
+ #
317
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
318
+ #
319
+ def mv(source, destination, options = {})
320
+ SharedPrivate.assert_valid_opts!(MV_ALLOWED_OPTS, **options)
321
+ Git::Commands::Mv.new(@execution_context).call(*Array(source), destination, verbose: true, **options).stdout
322
+ end
323
+
324
+ # Option keys accepted by {#clean}
325
+ #
326
+ # The deprecated `:ff` and `:force_force` keys are handled by
327
+ # {Git::Repository::Staging::Private.migrate_clean_legacy_options} before this
328
+ # whitelist is enforced, so they are intentionally absent here.
329
+ CLEAN_ALLOWED_OPTS = %i[d force f dry_run n quiet q exclude e x X pathspec].freeze
330
+ private_constant :CLEAN_ALLOWED_OPTS
331
+
332
+ # Remove untracked files from the working tree
333
+ #
334
+ # @example Remove untracked files
335
+ # repo.clean({ force: true })
336
+ #
337
+ # @example Remove untracked files and directories
338
+ # repo.clean({ force: true, d: true })
339
+ #
340
+ # @example Remove untracked and ignored files
341
+ # repo.clean({ force: true, x: true })
342
+ #
343
+ # @param opts [Hash] options for the clean command
344
+ #
345
+ # @option opts [Boolean, nil] :d (nil) recurse into untracked directories
346
+ #
347
+ # @option opts [Boolean, Integer, nil] :force (nil) force the removal of
348
+ # untracked files; pass `2` to also remove untracked nested git repositories
349
+ # (alias: `:f`)
350
+ #
351
+ # @option opts [Boolean, Integer, nil] :f (nil) alias for `:force`
352
+ #
353
+ # @option opts [Boolean, nil] :dry_run (nil) do not actually remove anything,
354
+ # just show what would be done (alias: `:n`)
355
+ #
356
+ # @option opts [Boolean, nil] :n (nil) alias for `:dry_run`
357
+ #
358
+ # @option opts [Boolean, nil] :quiet (nil) be quiet, only report errors
359
+ # (alias: `:q`)
360
+ #
361
+ # @option opts [Boolean, nil] :q (nil) alias for `:quiet`
362
+ #
363
+ # @option opts [String, Array<String>] :exclude (nil) use the given exclude
364
+ # pattern in addition to the standard ignore rules (alias: `:e`)
365
+ #
366
+ # @option opts [String, Array<String>] :e (nil) alias for `:exclude`
367
+ #
368
+ # @option opts [Boolean, nil] :x (nil) don't use the standard ignore rules
369
+ #
370
+ # @option opts [Boolean, nil] :X (nil) remove only files ignored by git
371
+ #
372
+ # @option opts [String, Array<String>] :pathspec (nil) limit cleaning to files
373
+ # matching the given pathspec(s)
374
+ #
375
+ # @return [String] git's stdout from the clean
376
+ #
377
+ # @raise [ArgumentError] when unsupported options are provided, or when a
378
+ # deprecated `:ff`/`:force_force` value is not `true`, `false`, or `nil`
379
+ #
380
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
381
+ #
382
+ def clean(opts = {})
383
+ opts = Private.migrate_clean_legacy_options(opts)
384
+ SharedPrivate.assert_valid_opts!(CLEAN_ALLOWED_OPTS, **opts)
385
+ Git::Commands::Clean.new(@execution_context).call(**opts).stdout
386
+ end
387
+
388
+ # List the files in the working tree that are ignored by git
389
+ #
390
+ # Runs `git ls-files --others --ignored --exclude-standard` and returns the
391
+ # ignored files as repository-relative paths.
392
+ #
393
+ # @example List ignored files
394
+ # repo.ignored_files #=> ["coverage/index.html", "tmp/cache.db"]
395
+ #
396
+ # @example No ignored files
397
+ # repo.ignored_files #=> []
398
+ #
399
+ # @return [Array<String>] repository-relative paths of ignored files; empty
400
+ # when there are none
401
+ #
402
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
403
+ #
404
+ def ignored_files
405
+ Git::Commands::LsFiles.new(@execution_context).call(
406
+ others: true, ignored: true, exclude_standard: true
407
+ ).stdout.split("\n").map { |f| Private.unescape_quoted_path(f) }
408
+ end
409
+
410
+ # Private helpers local to {Git::Repository::Staging}
411
+ #
412
+ # @api private
413
+ #
414
+ module Private
415
+ module_function
416
+
417
+ # Translate deprecated `git clean` option keys into their modern form
418
+ #
419
+ # Maps the legacy `:ff` and `:force_force` boolean options onto the
420
+ # `:force` option, emitting a deprecation warning for each.
421
+ #
422
+ # @param opts [Hash] the caller-provided clean options
423
+ #
424
+ # @return [Hash] a new options hash with deprecated keys translated
425
+ #
426
+ # @raise [ArgumentError] when a deprecated value is not `true`, `false`, or `nil`
427
+ #
428
+ def migrate_clean_legacy_options(opts)
429
+ opts = deprecate_clean_option(opts, :ff, ':ff option is deprecated. Use force: 2 instead.')
430
+ deprecate_clean_option(opts, :force_force, ':force_force option is deprecated. Use force: 2 instead.')
431
+ end
432
+
433
+ # Translate a single deprecated clean option key onto `:force`
434
+ #
435
+ # @param opts [Hash] the clean options
436
+ #
437
+ # @param key [Symbol] the deprecated option key (`:ff` or `:force_force`)
438
+ #
439
+ # @param message [String] the deprecation message to emit
440
+ #
441
+ # @return [Hash] a new options hash with the deprecated key removed
442
+ #
443
+ # @raise [ArgumentError] when the deprecated value is not `true`, `false`,
444
+ # or `nil`
445
+ #
446
+ def deprecate_clean_option(opts, key, message)
447
+ return opts unless opts.key?(key)
448
+
449
+ opts = opts.dup
450
+ deprecated_value = opts.delete(key)
451
+ validate_deprecated_clean_option_value!(key, deprecated_value)
452
+
453
+ Git::Deprecation.warn(message)
454
+ return opts unless deprecated_value
455
+
456
+ opts[:force] = merge_clean_force_option(opts[:force], force_specified: force_option_specified?(opts))
457
+ opts
458
+ end
459
+
460
+ # Whether the caller explicitly set a non-nil `:force` value
461
+ #
462
+ # @param opts [Hash] the clean options
463
+ #
464
+ # @return [Boolean] true if `:force` was set to a non-nil value, false
465
+ # otherwise
466
+ #
467
+ def force_option_specified?(opts)
468
+ opts.key?(:force) && !opts[:force].nil?
469
+ end
470
+
471
+ # Validate the value passed to a deprecated clean option
472
+ #
473
+ # @param key [Symbol] the deprecated option key
474
+ #
475
+ # @param value [Object] the value provided for the deprecated key
476
+ #
477
+ # @return [void]
478
+ #
479
+ # @raise [ArgumentError] when `value` is not `true`, `false`, or `nil`
480
+ #
481
+ def validate_deprecated_clean_option_value!(key, value)
482
+ return if value.nil? || value == true || value == false
483
+
484
+ raise ArgumentError, "#{key} option only accepts true, false, or nil"
485
+ end
486
+
487
+ # Merge a deprecated force request into the existing `:force` value
488
+ #
489
+ # @param existing_force [Boolean, Integer, nil] the caller's `:force` value
490
+ #
491
+ # @param force_specified [Boolean] whether the caller explicitly set `:force`
492
+ #
493
+ # @return [Integer] the resolved `:force` value
494
+ #
495
+ def merge_clean_force_option(existing_force, force_specified: false)
496
+ return 2 unless force_specified
497
+
498
+ normalized_force = normalize_clean_force_option(existing_force)
499
+
500
+ case normalized_force
501
+ when Integer then merge_integer_clean_force_option(normalized_force)
502
+ when false then 2
503
+ else normalized_force
504
+ end
505
+ end
506
+
507
+ # Merge an integer `:force` value with the deprecated force request
508
+ #
509
+ # @param normalized_force [Integer] the caller's normalized `:force` value
510
+ #
511
+ # @return [Integer] the resolved `:force` value
512
+ #
513
+ def merge_integer_clean_force_option(normalized_force)
514
+ return normalized_force if normalized_force < 1
515
+
516
+ [normalized_force, 2].max
517
+ end
518
+
519
+ # Normalize a `:force` value, coercing `true` to the integer `1`
520
+ #
521
+ # @param value [Boolean, Integer, nil] the `:force` value
522
+ #
523
+ # @return [Integer, Boolean, nil] the normalized value
524
+ #
525
+ def normalize_clean_force_option(value)
526
+ case value
527
+ when true then 1
528
+ else value
529
+ end
530
+ end
531
+
532
+ # Unescape a git-quoted path
533
+ #
534
+ # Git wraps paths containing non-ASCII or special characters in
535
+ # double-quotes and octal-escapes each byte. This method strips the
536
+ # surrounding quotes and delegates unescaping to {Git::EscapedPath}.
537
+ #
538
+ # @param path [String] the path as it appears in git output
539
+ #
540
+ # @return [String] the unescaped path
541
+ #
542
+ def unescape_quoted_path(path)
543
+ if path.start_with?('"') && path.end_with?('"')
544
+ Git::EscapedPath.new(path[1..-2]).unescape
545
+ else
546
+ path
547
+ end
548
+ end
549
+ end
550
+
551
+ private_constant :Private
552
+ end
553
+ end
554
+ end