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,357 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tempfile'
4
+ require 'git/commands/diff'
5
+ require 'git/commands/merge/start'
6
+ require 'git/commands/merge_base'
7
+ require 'git/commands/revert/start'
8
+ require 'git/commands/show'
9
+ require 'git/repository/shared_private'
10
+
11
+ module Git
12
+ class Repository
13
+ # Facade methods for merge operations: merging branches into the current branch,
14
+ # and finding common ancestors between commits
15
+ #
16
+ # Included by {Git::Repository}.
17
+ #
18
+ # @api public
19
+ #
20
+ module Merging
21
+ # Option keys accepted by {#merge}
22
+ #
23
+ # Derived from the 4.x option map for `Git::Lib#merge`.
24
+ MERGE_ALLOWED_OPTS = %i[no_commit no_ff m message].freeze
25
+ private_constant :MERGE_ALLOWED_OPTS
26
+
27
+ # Option keys accepted by {#merge_base}
28
+ #
29
+ # Derived from the 4.x option map for `Git::Lib#merge_base`.
30
+ MERGE_BASE_ALLOWED_OPTS = %i[octopus independent fork_point all].freeze
31
+ private_constant :MERGE_BASE_ALLOWED_OPTS
32
+
33
+ # Merge one or more branches into the current branch
34
+ #
35
+ # The merge commit message may be given by the message positional argument, the
36
+ # `:message` option, or the `:m` option; if more than one is provided, the
37
+ # precedence is positional argument > `:message` > `:m`.
38
+ #
39
+ # @example Merge a single branch
40
+ # repo.merge('feature')
41
+ #
42
+ # @example Merge a branch with a no-fast-forward commit message
43
+ # repo.merge('feature', 'Merge feature into main', no_ff: true)
44
+ #
45
+ # @example Octopus merge of multiple branches
46
+ # repo.merge(%w[feature-a feature-b])
47
+ #
48
+ # @example Merge without committing
49
+ # repo.merge('feature', nil, no_commit: true)
50
+ #
51
+ # @param branch [String, Array<String>, #to_s] the branch or branches to merge
52
+ # into the current branch
53
+ #
54
+ # When an Array is given, an octopus merge is performed; a {Git::Branch}
55
+ # object is coerced to a String via `#to_s`.
56
+ #
57
+ # @param message [String, nil] optional commit message for the merge commit
58
+ #
59
+ # Translated to the `-m` flag internally. For fast-forward merges git ignores
60
+ # this value; use `no_ff: true` to ensure a merge commit is created and the
61
+ # message is recorded.
62
+ #
63
+ # @param opts [Hash] additional options forwarded to `git merge`
64
+ #
65
+ # @option opts [Boolean, nil] :no_commit (nil) stop before creating the merge commit
66
+ # (`--no-commit`)
67
+ #
68
+ # @option opts [Boolean, nil] :no_ff (nil) create a merge commit even when
69
+ # fast-forward is possible (`--no-ff`)
70
+ #
71
+ # @option opts [String] :message (nil) commit message
72
+ #
73
+ # Prefer the `:m` option instead of this one. Translated to the `-m` flag.
74
+ # Identical to the positional `message` argument and the `:m` option.
75
+ #
76
+ # @option opts [String] :m (nil) commit message (`-m` flag)
77
+ #
78
+ # @return [String] git's stdout from the merge command
79
+ #
80
+ # @raise [ArgumentError] when unsupported options are provided
81
+ #
82
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
83
+ #
84
+ def merge(branch, message = nil, opts = {})
85
+ SharedPrivate.assert_valid_opts!(MERGE_ALLOWED_OPTS, **opts)
86
+
87
+ # Dup so callers who reuse the same opts hash are not affected
88
+ opts = opts.dup
89
+
90
+ # Merge positional message into opts so the rest of the logic is uniform
91
+ opts[:message] = message if message
92
+
93
+ # git merge uses -m, not --message; translate the key
94
+ opts[:m] = opts.delete(:message) if opts.key?(:message)
95
+
96
+ branches = Array(branch).map(&:to_s)
97
+ Git::Commands::Merge::Start.new(@execution_context).call(*branches, no_edit: true, **opts).stdout
98
+ end
99
+
100
+ # Find common ancestor commit(s) for use in a merge
101
+ #
102
+ # @example Find the common ancestor of two branches
103
+ # repo.merge_base('main', 'feature') #=> ["abc123def456..."]
104
+ #
105
+ # @example Find all common ancestors of two branches
106
+ # repo.merge_base('branch-a', 'branch-b', all: true)
107
+ #
108
+ # @example Find the fork point of a branch (consults the reflog)
109
+ # repo.merge_base('main', 'feature', fork_point: true)
110
+ #
111
+ # @example Find independent commits not reachable from each other
112
+ # repo.merge_base('abc1234', 'main', 'feature', independent: true)
113
+ #
114
+ # @overload merge_base(*commits, options = {})
115
+ #
116
+ # @param commits [Array<String>] two or more commit SHAs, branch names,
117
+ # or refs to find the common ancestor(s) of
118
+ #
119
+ # @param options [Hash] merge-base options
120
+ #
121
+ # @option options [Boolean, nil] :octopus (nil) compute the best common
122
+ # ancestor for an n-way merge (intersection of all merge bases)
123
+ #
124
+ # @option options [Boolean, nil] :independent (nil) list commits not
125
+ # reachable from any other; useful for finding minimal merge points
126
+ #
127
+ # @option options [Boolean, nil] :fork_point (nil) find the fork point
128
+ # where a branch diverged from another, consulting the reflog
129
+ #
130
+ # @option options [Boolean, nil] :all (nil) output all merge bases instead
131
+ # of just the first when multiple equally good bases exist
132
+ #
133
+ # @return [Array<String>] commit SHAs of the common ancestor(s); empty
134
+ # when no common ancestor exists or `--fork-point` finds none
135
+ #
136
+ # @raise [ArgumentError] when unsupported options are provided
137
+ #
138
+ # @raise [Git::FailedError] when `git merge-base` exits outside the
139
+ # allowed range (exit code > 1)
140
+ #
141
+ def merge_base(*args)
142
+ opts = args.last.is_a?(Hash) ? args.pop : {}
143
+ SharedPrivate.assert_valid_opts!(MERGE_BASE_ALLOWED_OPTS, **opts)
144
+ result = Git::Commands::MergeBase.new(@execution_context).call(*args, **opts)
145
+ result.stdout.lines.map(&:strip).reject(&:empty?)
146
+ end
147
+
148
+ # Return the paths of files with unresolved merge conflicts
149
+ #
150
+ # @example List conflicting files after a failed merge
151
+ # paths = repo.unmerged
152
+ # # => ["config/settings.rb", "lib/git/base.rb"]
153
+ # paths.each { |path| puts "Conflict in #{path}" }
154
+ #
155
+ # @return [Array<String>] repository-relative paths of files with unresolved
156
+ # merge conflicts; empty array when the working tree has no conflicts
157
+ #
158
+ # @raise [Git::FailedError] if git exits outside the allowed range (exit code > 2)
159
+ #
160
+ # @see #each_conflict
161
+ #
162
+ def unmerged
163
+ Private.unmerged_paths(@execution_context)
164
+ end
165
+
166
+ # Iterate over files with merge conflicts, yielding conflict details for each
167
+ #
168
+ # For each unmerged file, the staged content for both sides of the conflict
169
+ # (stage 2 "ours" and stage 3 "theirs") is written to temporary files whose
170
+ # paths are yielded alongside the file path. The temporary files are deleted
171
+ # automatically when the block returns.
172
+ #
173
+ # @example Inspect conflicting files
174
+ # repo.each_conflict do |file, your_version, their_version|
175
+ # puts "Conflict in #{file}"
176
+ # puts "Your version:"
177
+ # puts File.read(your_version)
178
+ # puts "Their version:"
179
+ # puts File.read(their_version)
180
+ # end
181
+ #
182
+ # @return [Array<String>] the list of unmerged file paths
183
+ #
184
+ # @raise [Git::FailedError] when `git diff --cached` exits outside the
185
+ # allowed range (exit code > 2)
186
+ #
187
+ # @yield [file, your_version, their_version] passes conflict details for
188
+ # each unmerged file
189
+ #
190
+ # @yieldparam file [String] path to the conflicting file, relative to the
191
+ # working tree
192
+ #
193
+ # @yieldparam your_version [String] path to a temporary file containing the
194
+ # stage-2 (ours) content for the conflicting file
195
+ #
196
+ # @yieldparam their_version [String] path to a temporary file containing the
197
+ # stage-3 (theirs) content for the conflicting file
198
+ #
199
+ # @yieldreturn [void]
200
+ #
201
+ def each_conflict
202
+ Private.unmerged_paths(@execution_context).each do |file_path|
203
+ Private.write_staged_file(@execution_context, file_path, 2) do |your_file|
204
+ Private.write_staged_file(@execution_context, file_path, 3) do |their_file|
205
+ yield(file_path, your_file.path, their_file.path)
206
+ end
207
+ end
208
+ end
209
+ end
210
+
211
+ # Iterate over files with merge conflicts, yielding conflict details for each
212
+ #
213
+ # For each unmerged file, the staged content for both sides of the conflict
214
+ # (stage 2 "ours" and stage 3 "theirs") is written to temporary files whose
215
+ # paths are yielded alongside the file path. The temporary files are deleted
216
+ # automatically when the block returns.
217
+ #
218
+ # @example Inspect conflicting files
219
+ # repo.conflicts do |file, your_version, their_version|
220
+ # puts "Conflict in #{file}"
221
+ # puts File.read(your_version)
222
+ # puts File.read(their_version)
223
+ # end
224
+ #
225
+ # @return [Array<String>] the list of unmerged file paths
226
+ #
227
+ # @raise [Git::FailedError] when `git diff --cached` exits outside the
228
+ # allowed range (exit code > 2)
229
+ #
230
+ # @yield [file, your_version, their_version] passes conflict details for
231
+ # each unmerged file
232
+ #
233
+ # @yieldparam file [String] path to the conflicting file, relative to the
234
+ # working tree
235
+ #
236
+ # @yieldparam your_version [String] path to a temporary file containing the
237
+ # stage-2 (ours) content for the conflicting file
238
+ #
239
+ # @yieldparam their_version [String] path to a temporary file containing the
240
+ # stage-3 (theirs) content for the conflicting file
241
+ #
242
+ # @yieldreturn [void]
243
+ #
244
+ # @deprecated Use {#each_conflict} instead
245
+ #
246
+ def conflicts(&)
247
+ Git::Deprecation.warn(
248
+ 'Git::Repository#conflicts is deprecated and will be removed in a future version. ' \
249
+ 'Use Git::Repository#each_conflict instead.'
250
+ )
251
+ each_conflict(&)
252
+ end
253
+
254
+ # Option keys accepted by {#revert}
255
+ #
256
+ # Derived from the 4.x option map for `Git::Lib#revert`.
257
+ REVERT_ALLOWED_OPTS = %i[no_edit].freeze
258
+ private_constant :REVERT_ALLOWED_OPTS
259
+
260
+ # Revert one or more existing commits by creating new commits that undo
261
+ # the changes those commits introduced
262
+ #
263
+ # The working tree must be clean before calling this method. By default
264
+ # the editor is suppressed (`--no-edit`) so the commit message is taken
265
+ # from git's default revert message without prompting.
266
+ #
267
+ # @example Revert the most recent commit
268
+ # repo.revert('HEAD')
269
+ #
270
+ # @example Revert a specific commit by SHA
271
+ # repo.revert('abc1234')
272
+ #
273
+ # @example Revert a range of commits
274
+ # repo.revert('HEAD~3..HEAD~1')
275
+ #
276
+ # @example Revert without suppressing the editor
277
+ # repo.revert('HEAD', no_edit: false)
278
+ #
279
+ # @param commitish [String, nil] the commit, ref, or rev range to revert;
280
+ # see `gitrevisions(7)` for accepted forms; defaults to `'HEAD'` when
281
+ # `nil`
282
+ #
283
+ # @param opts [Hash] additional options forwarded to `git revert`
284
+ #
285
+ # @option opts [Boolean, nil] :no_edit (true) suppress the commit-message
286
+ # editor (`--no-edit`); pass `false` to open the editor
287
+ #
288
+ # @return [String] git's stdout from the revert command
289
+ #
290
+ # @raise [ArgumentError] when unsupported options are provided
291
+ #
292
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
293
+ #
294
+ def revert(commitish = nil, opts = {})
295
+ commitish = 'HEAD' if commitish.nil?
296
+ SharedPrivate.assert_valid_opts!(REVERT_ALLOWED_OPTS, **opts)
297
+ opts = { no_edit: true }.merge(opts)
298
+ Git::Commands::Revert::Start.new(@execution_context).call(commitish, **opts).stdout
299
+ end
300
+
301
+ # Private helpers local to {Git::Repository::Merging}
302
+ #
303
+ # @api private
304
+ module Private
305
+ # Tempfile name prefixes for staged content, keyed by git stage index
306
+ STAGE_PREFIXES = { 2 => 'YOUR-', 3 => 'THEIR-' }.freeze
307
+
308
+ module_function
309
+
310
+ # Returns the list of file paths with unresolved merge conflicts
311
+ #
312
+ # @param execution_context [Git::ExecutionContext] the execution context
313
+ # used to run git commands
314
+ #
315
+ # @return [Array<String>] unmerged file paths
316
+ #
317
+ # @api private
318
+ #
319
+ def unmerged_paths(execution_context)
320
+ result = Git::Commands::Diff.new(execution_context).call(cached: true)
321
+ result.stdout.split("\n").filter_map do |line|
322
+ ::Regexp.last_match(1) if line =~ /^\* Unmerged path (.*)/
323
+ end
324
+ end
325
+
326
+ # Creates a Tempfile with the staged content for `file_path` at `stage`
327
+ # and yields the open IO object to the block
328
+ #
329
+ # @param execution_context [Git::ExecutionContext] the execution context
330
+ # used to run git commands
331
+ #
332
+ # @param file_path [String] repository-relative path to the conflicting file
333
+ #
334
+ # @param stage [Integer] git stage index (2 = ours, 3 = theirs)
335
+ #
336
+ # @yield [f] yields the open Tempfile containing the staged content
337
+ #
338
+ # @yieldparam f [Tempfile] open IO object for the staged content
339
+ #
340
+ # @yieldreturn [void]
341
+ #
342
+ # @return [void]
343
+ #
344
+ # @api private
345
+ #
346
+ def write_staged_file(execution_context, file_path, stage)
347
+ Tempfile.create([STAGE_PREFIXES[stage], File.basename(file_path)]) do |f|
348
+ Git::Commands::Show.new(execution_context).call(":#{stage}:#{file_path}", out: f)
349
+ f.flush
350
+ yield f
351
+ end
352
+ end
353
+ end
354
+ private_constant :Private
355
+ end
356
+ end
357
+ end