git 1.19.1 → 5.5.0

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/.yardopts +5 -1
  3. data/AI_POLICY.md +24 -0
  4. data/CHANGELOG.md +689 -0
  5. data/CODE_OF_CONDUCT.md +25 -0
  6. data/CONTRIBUTING.md +1175 -97
  7. data/GOVERNANCE.md +106 -0
  8. data/LICENSE +1 -1
  9. data/MAINTAINERS.md +17 -4
  10. data/README.md +476 -320
  11. data/UPGRADING.md +1138 -0
  12. data/git.gemspec +124 -36
  13. data/lib/git/author.rb +39 -7
  14. data/lib/git/author_info.rb +66 -0
  15. data/lib/git/branch.rb +615 -65
  16. data/lib/git/branch_delete_failure.rb +34 -0
  17. data/lib/git/branch_delete_result.rb +66 -0
  18. data/lib/git/branch_info.rb +237 -0
  19. data/lib/git/branches.rb +167 -44
  20. data/lib/git/command_line/base.rb +247 -0
  21. data/lib/git/command_line/capturing.rb +308 -0
  22. data/lib/git/command_line/result.rb +88 -0
  23. data/lib/git/command_line/streaming.rb +236 -0
  24. data/lib/git/command_line.rb +52 -0
  25. data/lib/git/commands/add.rb +139 -0
  26. data/lib/git/commands/am/abort.rb +43 -0
  27. data/lib/git/commands/am/apply.rb +263 -0
  28. data/lib/git/commands/am/continue.rb +43 -0
  29. data/lib/git/commands/am/quit.rb +43 -0
  30. data/lib/git/commands/am/retry.rb +49 -0
  31. data/lib/git/commands/am/show_current_patch.rb +64 -0
  32. data/lib/git/commands/am/skip.rb +42 -0
  33. data/lib/git/commands/am.rb +33 -0
  34. data/lib/git/commands/apply.rb +242 -0
  35. data/lib/git/commands/archive/list_formats.rb +46 -0
  36. data/lib/git/commands/archive.rb +145 -0
  37. data/lib/git/commands/arguments.rb +4521 -0
  38. data/lib/git/commands/base.rb +502 -0
  39. data/lib/git/commands/branch/copy.rb +102 -0
  40. data/lib/git/commands/branch/create.rb +177 -0
  41. data/lib/git/commands/branch/delete.rb +88 -0
  42. data/lib/git/commands/branch/list.rb +178 -0
  43. data/lib/git/commands/branch/move.rb +102 -0
  44. data/lib/git/commands/branch/set_upstream.rb +86 -0
  45. data/lib/git/commands/branch/show_current.rb +49 -0
  46. data/lib/git/commands/branch/unset_upstream.rb +53 -0
  47. data/lib/git/commands/branch.rb +34 -0
  48. data/lib/git/commands/cat_file/batch.rb +385 -0
  49. data/lib/git/commands/cat_file/filtered.rb +105 -0
  50. data/lib/git/commands/cat_file/raw.rb +271 -0
  51. data/lib/git/commands/cat_file.rb +49 -0
  52. data/lib/git/commands/checkout/branch.rb +153 -0
  53. data/lib/git/commands/checkout/files.rb +116 -0
  54. data/lib/git/commands/checkout.rb +38 -0
  55. data/lib/git/commands/checkout_index.rb +106 -0
  56. data/lib/git/commands/clean.rb +102 -0
  57. data/lib/git/commands/clone.rb +241 -0
  58. data/lib/git/commands/commit.rb +273 -0
  59. data/lib/git/commands/commit_tree.rb +101 -0
  60. data/lib/git/commands/config_option_syntax/add.rb +86 -0
  61. data/lib/git/commands/config_option_syntax/get.rb +121 -0
  62. data/lib/git/commands/config_option_syntax/get_all.rb +118 -0
  63. data/lib/git/commands/config_option_syntax/get_color.rb +95 -0
  64. data/lib/git/commands/config_option_syntax/get_color_bool.rb +96 -0
  65. data/lib/git/commands/config_option_syntax/get_regexp.rb +119 -0
  66. data/lib/git/commands/config_option_syntax/get_urlmatch.rb +111 -0
  67. data/lib/git/commands/config_option_syntax/list.rb +111 -0
  68. data/lib/git/commands/config_option_syntax/remove_section.rb +79 -0
  69. data/lib/git/commands/config_option_syntax/rename_section.rb +83 -0
  70. data/lib/git/commands/config_option_syntax/replace_all.rb +109 -0
  71. data/lib/git/commands/config_option_syntax/set.rb +119 -0
  72. data/lib/git/commands/config_option_syntax/unset.rb +92 -0
  73. data/lib/git/commands/config_option_syntax/unset_all.rb +94 -0
  74. data/lib/git/commands/config_option_syntax.rb +56 -0
  75. data/lib/git/commands/describe.rb +156 -0
  76. data/lib/git/commands/diff.rb +657 -0
  77. data/lib/git/commands/diff_files.rb +519 -0
  78. data/lib/git/commands/diff_index.rb +499 -0
  79. data/lib/git/commands/fetch.rb +354 -0
  80. data/lib/git/commands/fsck.rb +138 -0
  81. data/lib/git/commands/gc.rb +134 -0
  82. data/lib/git/commands/grep.rb +339 -0
  83. data/lib/git/commands/init.rb +101 -0
  84. data/lib/git/commands/log.rb +634 -0
  85. data/lib/git/commands/ls_files.rb +195 -0
  86. data/lib/git/commands/ls_remote.rb +161 -0
  87. data/lib/git/commands/ls_tree.rb +135 -0
  88. data/lib/git/commands/maintenance/register.rb +77 -0
  89. data/lib/git/commands/maintenance/run.rb +109 -0
  90. data/lib/git/commands/maintenance/start.rb +71 -0
  91. data/lib/git/commands/maintenance/stop.rb +60 -0
  92. data/lib/git/commands/maintenance/unregister.rb +84 -0
  93. data/lib/git/commands/maintenance.rb +31 -0
  94. data/lib/git/commands/merge/abort.rb +44 -0
  95. data/lib/git/commands/merge/continue.rb +44 -0
  96. data/lib/git/commands/merge/quit.rb +46 -0
  97. data/lib/git/commands/merge/start.rb +250 -0
  98. data/lib/git/commands/merge.rb +28 -0
  99. data/lib/git/commands/merge_base.rb +91 -0
  100. data/lib/git/commands/mv.rb +82 -0
  101. data/lib/git/commands/name_rev.rb +119 -0
  102. data/lib/git/commands/pull.rb +382 -0
  103. data/lib/git/commands/push.rb +251 -0
  104. data/lib/git/commands/read_tree.rb +154 -0
  105. data/lib/git/commands/remote/add.rb +96 -0
  106. data/lib/git/commands/remote/get_url.rb +68 -0
  107. data/lib/git/commands/remote/list.rb +56 -0
  108. data/lib/git/commands/remote/prune.rb +63 -0
  109. data/lib/git/commands/remote/remove.rb +52 -0
  110. data/lib/git/commands/remote/rename.rb +76 -0
  111. data/lib/git/commands/remote/set_branches.rb +70 -0
  112. data/lib/git/commands/remote/set_head.rb +89 -0
  113. data/lib/git/commands/remote/set_url.rb +78 -0
  114. data/lib/git/commands/remote/set_url_add.rb +70 -0
  115. data/lib/git/commands/remote/set_url_delete.rb +71 -0
  116. data/lib/git/commands/remote/show.rb +77 -0
  117. data/lib/git/commands/remote/update.rb +79 -0
  118. data/lib/git/commands/remote.rb +42 -0
  119. data/lib/git/commands/repack.rb +281 -0
  120. data/lib/git/commands/reset.rb +154 -0
  121. data/lib/git/commands/rev_parse.rb +304 -0
  122. data/lib/git/commands/revert/abort.rb +45 -0
  123. data/lib/git/commands/revert/continue.rb +62 -0
  124. data/lib/git/commands/revert/quit.rb +47 -0
  125. data/lib/git/commands/revert/skip.rb +44 -0
  126. data/lib/git/commands/revert/start.rb +158 -0
  127. data/lib/git/commands/revert.rb +29 -0
  128. data/lib/git/commands/rm.rb +113 -0
  129. data/lib/git/commands/show.rb +632 -0
  130. data/lib/git/commands/show_ref/exclude_existing.rb +119 -0
  131. data/lib/git/commands/show_ref/exists.rb +80 -0
  132. data/lib/git/commands/show_ref/list.rb +149 -0
  133. data/lib/git/commands/show_ref/verify.rb +122 -0
  134. data/lib/git/commands/show_ref.rb +42 -0
  135. data/lib/git/commands/stash/apply.rb +81 -0
  136. data/lib/git/commands/stash/branch.rb +67 -0
  137. data/lib/git/commands/stash/clear.rb +43 -0
  138. data/lib/git/commands/stash/create.rb +60 -0
  139. data/lib/git/commands/stash/drop.rb +73 -0
  140. data/lib/git/commands/stash/list.rb +43 -0
  141. data/lib/git/commands/stash/pop.rb +87 -0
  142. data/lib/git/commands/stash/push.rb +112 -0
  143. data/lib/git/commands/stash/show.rb +158 -0
  144. data/lib/git/commands/stash/store.rb +72 -0
  145. data/lib/git/commands/stash.rb +38 -0
  146. data/lib/git/commands/status.rb +174 -0
  147. data/lib/git/commands/symbolic_ref/delete.rb +72 -0
  148. data/lib/git/commands/symbolic_ref/read.rb +99 -0
  149. data/lib/git/commands/symbolic_ref/update.rb +79 -0
  150. data/lib/git/commands/symbolic_ref.rb +38 -0
  151. data/lib/git/commands/tag/create.rb +142 -0
  152. data/lib/git/commands/tag/delete.rb +57 -0
  153. data/lib/git/commands/tag/list.rb +146 -0
  154. data/lib/git/commands/tag/verify.rb +71 -0
  155. data/lib/git/commands/tag.rb +26 -0
  156. data/lib/git/commands/update_ref/batch.rb +145 -0
  157. data/lib/git/commands/update_ref/delete.rb +90 -0
  158. data/lib/git/commands/update_ref/update.rb +103 -0
  159. data/lib/git/commands/update_ref.rb +42 -0
  160. data/lib/git/commands/version.rb +60 -0
  161. data/lib/git/commands/worktree/add.rb +139 -0
  162. data/lib/git/commands/worktree/list.rb +64 -0
  163. data/lib/git/commands/worktree/lock.rb +58 -0
  164. data/lib/git/commands/worktree/management_base.rb +51 -0
  165. data/lib/git/commands/worktree/move.rb +66 -0
  166. data/lib/git/commands/worktree/prune.rb +67 -0
  167. data/lib/git/commands/worktree/remove.rb +63 -0
  168. data/lib/git/commands/worktree/repair.rb +76 -0
  169. data/lib/git/commands/worktree/unlock.rb +47 -0
  170. data/lib/git/commands/worktree.rb +43 -0
  171. data/lib/git/commands/write_tree.rb +68 -0
  172. data/lib/git/commands.rb +88 -0
  173. data/lib/git/config.rb +72 -5
  174. data/lib/git/config_entry_info.rb +106 -0
  175. data/lib/git/configuring.rb +795 -0
  176. data/lib/git/detached_head_info.rb +57 -0
  177. data/lib/git/diff.rb +437 -86
  178. data/lib/git/diff_file_numstat_info.rb +31 -0
  179. data/lib/git/diff_file_patch_info.rb +136 -0
  180. data/lib/git/diff_file_raw_info.rb +129 -0
  181. data/lib/git/diff_info.rb +162 -0
  182. data/lib/git/diff_path_status.rb +107 -0
  183. data/lib/git/diff_result.rb +34 -0
  184. data/lib/git/diff_stats.rb +111 -0
  185. data/lib/git/dirstat_info.rb +102 -0
  186. data/lib/git/encoding_utils.rb +32 -1
  187. data/lib/git/errors.rb +285 -0
  188. data/lib/git/escaped_path.rb +57 -5
  189. data/lib/git/execution_context/global.rb +31 -0
  190. data/lib/git/execution_context/repository.rb +151 -0
  191. data/lib/git/execution_context.rb +559 -0
  192. data/lib/git/factories.rb +813 -0
  193. data/lib/git/file_ref.rb +77 -0
  194. data/lib/git/fsck_object.rb +56 -0
  195. data/lib/git/fsck_result.rb +132 -0
  196. data/lib/git/log.rb +306 -90
  197. data/lib/git/object.rb +563 -141
  198. data/lib/git/parsers/branch.rb +240 -0
  199. data/lib/git/parsers/cat_file.rb +111 -0
  200. data/lib/git/parsers/config_entry.rb +110 -0
  201. data/lib/git/parsers/diff.rb +792 -0
  202. data/lib/git/parsers/fsck.rb +144 -0
  203. data/lib/git/parsers/grep.rb +42 -0
  204. data/lib/git/parsers/ls_remote.rb +79 -0
  205. data/lib/git/parsers/ls_tree.rb +58 -0
  206. data/lib/git/parsers/remote.rb +162 -0
  207. data/lib/git/parsers/stash.rb +292 -0
  208. data/lib/git/parsers/status.rb +251 -0
  209. data/lib/git/parsers/tag.rb +341 -0
  210. data/lib/git/parsers/worktree.rb +185 -0
  211. data/lib/git/path_resolver.rb +206 -0
  212. data/lib/git/remote.rb +165 -12
  213. data/lib/git/remote_info.rb +203 -0
  214. data/lib/git/repository/branching.rb +964 -0
  215. data/lib/git/repository/committing.rb +246 -0
  216. data/lib/git/repository/context_helpers.rb +293 -0
  217. data/lib/git/repository/diffing.rb +785 -0
  218. data/lib/git/repository/inspecting.rb +252 -0
  219. data/lib/git/repository/logging.rb +410 -0
  220. data/lib/git/repository/maintenance.rb +65 -0
  221. data/lib/git/repository/merging.rb +451 -0
  222. data/lib/git/repository/object_operations.rb +1551 -0
  223. data/lib/git/repository/remote_operations.rb +984 -0
  224. data/lib/git/repository/shared_private.rb +120 -0
  225. data/lib/git/repository/staging.rb +587 -0
  226. data/lib/git/repository/stashing.rb +623 -0
  227. data/lib/git/repository/status_operations.rb +249 -0
  228. data/lib/git/repository/worktree_operations.rb +339 -0
  229. data/lib/git/repository.rb +484 -2
  230. data/lib/git/stash.rb +109 -12
  231. data/lib/git/stash_info.rb +102 -0
  232. data/lib/git/stashes.rb +169 -26
  233. data/lib/git/status.rb +308 -122
  234. data/lib/git/status_file_info.rb +258 -0
  235. data/lib/git/status_info.rb +189 -0
  236. data/lib/git/tag_delete_failure.rb +34 -0
  237. data/lib/git/tag_delete_result.rb +66 -0
  238. data/lib/git/tag_info.rb +99 -0
  239. data/lib/git/url.rb +15 -8
  240. data/lib/git/version.rb +113 -2
  241. data/lib/git/version_constraint.rb +85 -0
  242. data/lib/git/worktree.rb +150 -8
  243. data/lib/git/worktree_info.rb +128 -0
  244. data/lib/git/worktrees.rb +118 -13
  245. data/lib/git.rb +632 -234
  246. metadata +369 -54
  247. data/.github/stale.yml +0 -25
  248. data/.github/workflows/continuous_integration.yml +0 -49
  249. data/.gitignore +0 -10
  250. data/Dockerfile.changelog-rs +0 -12
  251. data/Gemfile +0 -5
  252. data/ISSUE_TEMPLATE.md +0 -15
  253. data/PULL_REQUEST_TEMPLATE.md +0 -9
  254. data/RELEASING.md +0 -70
  255. data/Rakefile +0 -60
  256. data/lib/git/base/factory.rb +0 -99
  257. data/lib/git/base.rb +0 -711
  258. data/lib/git/command_line_result.rb +0 -86
  259. data/lib/git/failed_error.rb +0 -53
  260. data/lib/git/git_execute_error.rb +0 -7
  261. data/lib/git/index.rb +0 -5
  262. data/lib/git/lib.rb +0 -1328
  263. data/lib/git/path.rb +0 -31
  264. data/lib/git/signaled_error.rb +0 -50
  265. data/lib/git/working_directory.rb +0 -4
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git/commands/commit'
4
+ require 'git/commands/commit_tree'
5
+ require 'git/commands/write_tree'
6
+ require 'git/repository/shared_private'
7
+
8
+ module Git
9
+ class Repository
10
+ # Facade methods for committing operations: recording commits, manipulating
11
+ # tree objects, and building commit objects outside the working tree
12
+ #
13
+ # Included by {Git::Repository}.
14
+ #
15
+ # @api private
16
+ #
17
+ module Committing
18
+ # Option keys accepted by {#commit}
19
+ COMMIT_ALLOWED_OPTS = %i[
20
+ all amend allow_empty allow_empty_message author date
21
+ gpg_sign no_gpg_sign no_verify
22
+ ].freeze
23
+ private_constant :COMMIT_ALLOWED_OPTS
24
+
25
+ # Option keys accepted by {#commit_tree}
26
+ COMMIT_TREE_ALLOWED_OPTS = %i[parent p parents message m].freeze
27
+ private_constant :COMMIT_TREE_ALLOWED_OPTS
28
+
29
+ # Record staged changes as a new commit
30
+ #
31
+ # @example Commit with a message
32
+ # repo.commit('Add README')
33
+ #
34
+ # @example Amend the previous commit, reusing its message
35
+ # repo.commit(nil, amend: true)
36
+ #
37
+ # @example Stage all modified files and commit
38
+ # repo.commit('Cleanup', all: true)
39
+ #
40
+ # @param message [String, nil] the commit message; pass `nil` to omit
41
+ # (e.g. when using `:amend` to reuse the previous message)
42
+ #
43
+ # @param opts [Hash] options for the commit command
44
+ #
45
+ # @option opts [Boolean, nil] :all (nil) automatically stage modified and
46
+ # deleted files before committing
47
+ #
48
+ # @option opts [Boolean, nil] :amend (nil) replace the tip of the current
49
+ # branch with a new commit
50
+ #
51
+ # @option opts [Boolean, nil] :allow_empty (nil) allow committing with no
52
+ # changes
53
+ #
54
+ # @option opts [Boolean, nil] :allow_empty_message (nil) allow committing
55
+ # with an empty message
56
+ #
57
+ # @option opts [String] :author (nil) override the commit author in
58
+ # `A U Thor <author@example.com>` format
59
+ #
60
+ # @option opts [String] :date (nil) override the author date
61
+ #
62
+ # @option opts [Boolean, String, nil] :gpg_sign (nil) GPG-sign the commit
63
+ #
64
+ # @option opts [Boolean, nil] :no_gpg_sign (nil) disable GPG signing
65
+ #
66
+ # @option opts [Boolean, nil] :no_verify (nil) bypass the pre-commit and
67
+ # commit-msg hooks
68
+ #
69
+ # @return [String] git's stdout from the commit
70
+ #
71
+ # @raise [ArgumentError] when unsupported options are provided
72
+ #
73
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
74
+ #
75
+ def commit(message, opts = {})
76
+ opts = normalize_commit_options(opts)
77
+ SharedPrivate.assert_valid_opts!(COMMIT_ALLOWED_OPTS, **opts)
78
+
79
+ call_opts = { no_edit: true }
80
+ call_opts[:message] = message if message
81
+
82
+ Git::Commands::Commit.new(@execution_context).call(**call_opts, **opts).stdout
83
+ end
84
+
85
+ # Commit all modified tracked files without explicitly staging them first
86
+ #
87
+ # Equivalent to calling {#commit} with `all: true` merged into `opts`.
88
+ #
89
+ # @example Commit all changes with a message
90
+ # repo.commit_all('Update everything')
91
+ #
92
+ # @param message [String, nil] the commit message; pass `nil` to omit
93
+ # (e.g. when using `:amend` to reuse the previous message)
94
+ #
95
+ # @param opts [Hash] additional options forwarded to {#commit}
96
+ #
97
+ # @option opts [Boolean, nil] :all (nil) ignored because this method
98
+ # always commits with `all: true`
99
+ #
100
+ # @option opts [Boolean, nil] :amend (nil) replace the tip of the current
101
+ # branch with a new commit
102
+ #
103
+ # @option opts [Boolean, nil] :allow_empty (nil) allow committing with no
104
+ # changes
105
+ #
106
+ # @option opts [Boolean, nil] :allow_empty_message (nil) allow committing
107
+ # with an empty message
108
+ #
109
+ # @option opts [String] :author (nil) override the commit author in
110
+ # `A U Thor <author@example.com>` format
111
+ #
112
+ # @option opts [String] :date (nil) override the author date
113
+ #
114
+ # @option opts [Boolean, String, nil] :gpg_sign (nil) GPG-sign the commit
115
+ #
116
+ # @option opts [Boolean, nil] :no_gpg_sign (nil) disable GPG signing
117
+ #
118
+ # @option opts [Boolean, nil] :no_verify (nil) bypass the pre-commit and
119
+ # commit-msg hooks
120
+ #
121
+ # @return [String] git's stdout from the commit
122
+ #
123
+ # @raise [ArgumentError] when unsupported options are provided
124
+ #
125
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
126
+ #
127
+ def commit_all(message, opts = {})
128
+ commit(message, opts.merge(all: true))
129
+ end
130
+
131
+ # Create a commit object from a tree SHA without moving HEAD
132
+ #
133
+ # Unlike {#commit}, this does not read the index; it directly wraps
134
+ # `git commit-tree`.
135
+ #
136
+ # @example Commit a tree with a parent
137
+ # repo.commit_tree('deadbeef', message: 'snapshot', parent: 'HEAD')
138
+ #
139
+ # @param tree [String, nil] the tree SHA to commit; defaults to `nil`
140
+ #
141
+ # @param opts [Hash] options for the commit-tree command
142
+ #
143
+ # @option opts [String, Array<String>] :m (nil) the commit message
144
+ # paragraph(s) (short form)
145
+ #
146
+ # @option opts [String, Array<String>] :message (nil) the commit message
147
+ # paragraph(s) (normalized to `:m` before passing to the command)
148
+ #
149
+ # @option opts [String, Array<String>] :p (nil) parent commit SHA(s)
150
+ #
151
+ # @option opts [String] :parent (nil) a single parent commit SHA
152
+ # (normalized to `:p`)
153
+ #
154
+ # @option opts [Array<String>] :parents (nil) multiple parent commit
155
+ # SHAs (normalized to `:p`)
156
+ #
157
+ # @return [String] the SHA of the newly created commit object
158
+ #
159
+ # @raise [ArgumentError] when unsupported options are provided
160
+ #
161
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
162
+ #
163
+ def commit_tree(tree = nil, opts = {}) # rubocop:disable Metrics/AbcSize
164
+ SharedPrivate.assert_valid_opts!(COMMIT_TREE_ALLOWED_OPTS, **opts)
165
+
166
+ opts = opts.dup
167
+ opts[:p] = opts.delete(:parents) if opts.key?(:parents)
168
+ opts[:p] = opts.delete(:parent) if opts.key?(:parent)
169
+ opts[:m] = opts.delete(:message) if opts.key?(:message)
170
+ opts[:m] = "commit tree #{tree}" unless opts[:m]
171
+
172
+ Git::Commands::CommitTree.new(@execution_context).call(tree, **opts).stdout
173
+ end
174
+
175
+ # Write the current index to a tree object in the object store
176
+ #
177
+ # @example Get the tree SHA of the current index
178
+ # tree_sha = repo.write_tree
179
+ #
180
+ # @return [String] the SHA of the tree object written
181
+ #
182
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
183
+ #
184
+ def write_tree
185
+ Git::Commands::WriteTree.new(@execution_context).call.stdout
186
+ end
187
+
188
+ # Write the current index to a tree object and immediately commit it
189
+ #
190
+ # Combines {#write_tree} and {#commit_tree} in a single call.
191
+ #
192
+ # @example Commit the current index as a snapshot
193
+ # commit_sha = repo.write_and_commit_tree(message: 'snapshot')
194
+ #
195
+ # @param opts [Hash] options forwarded to {#commit_tree}
196
+ #
197
+ # @option opts [String, Array<String>] :m (nil) the commit message
198
+ # paragraph(s) (short form)
199
+ #
200
+ # @option opts [String, Array<String>] :message (nil) the commit message
201
+ # paragraph(s) (normalized to `:m` before passing to the command)
202
+ #
203
+ # @option opts [String, Array<String>] :p (nil) parent commit SHA(s)
204
+ #
205
+ # @option opts [String] :parent (nil) a single parent commit SHA
206
+ # (normalized to `:p`)
207
+ #
208
+ # @option opts [Array<String>] :parents (nil) multiple parent commit
209
+ # SHAs (normalized to `:p`)
210
+ #
211
+ # @return [String] the SHA of the newly created commit object
212
+ #
213
+ # @raise [ArgumentError] when unsupported options are provided
214
+ #
215
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
216
+ #
217
+ def write_and_commit_tree(opts = {})
218
+ commit_tree(write_tree, opts)
219
+ end
220
+
221
+ private
222
+
223
+ # Normalizes deprecated commit options into their supported form
224
+ #
225
+ # @param opts [Hash] caller-provided commit options
226
+ #
227
+ # @option opts [Boolean, nil] :add_all (nil) deprecated alias for `:all`
228
+ #
229
+ # @return [Hash] options with deprecated aliases translated
230
+ #
231
+ # @api private
232
+ #
233
+ def normalize_commit_options(opts)
234
+ opts = opts.dup
235
+ return opts unless opts.key?(:add_all)
236
+
237
+ Git::Deprecation.warn(
238
+ 'The :add_all option for #commit is deprecated and will be removed in v6.0.0. ' \
239
+ 'Use :all instead.'
240
+ )
241
+ opts[:all] = opts.delete(:add_all)
242
+ opts
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,293 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'pathname'
5
+ require 'tmpdir'
6
+ require 'git/execution_context/repository'
7
+
8
+ module Git
9
+ class Repository
10
+ # Facade methods for block-based directory and index context helpers
11
+ #
12
+ # These helpers allow callers to temporarily change the working directory,
13
+ # the git index, or both, restoring the original state unconditionally when
14
+ # the block exits — even if the block raises an exception.
15
+ #
16
+ # Included by {Git::Repository}.
17
+ #
18
+ # @api private
19
+ #
20
+ module ContextHelpers
21
+ # Changes the current working directory to the repository working directory
22
+ # for the duration of the block
23
+ #
24
+ # @example Write a file inside the repository working directory
25
+ # repo.chdir do |dir|
26
+ # File.write('hello.txt', 'Hello, world!')
27
+ # repo.add('hello.txt')
28
+ # end
29
+ #
30
+ # @return [Object] the value returned by the block
31
+ #
32
+ # @raise [ArgumentError] if the repository has no working directory (bare
33
+ # repository)
34
+ #
35
+ # @yield [dir] the repository working directory
36
+ #
37
+ # @yieldparam dir [Pathname] the working directory path
38
+ #
39
+ # @yieldreturn [Object] returned as the method's return value
40
+ #
41
+ def chdir
42
+ raise ArgumentError, 'cannot chdir: repository has no working directory (bare repository)' if dir.nil?
43
+
44
+ Dir.chdir(dir.to_s) { yield dir }
45
+ end
46
+
47
+ # Temporarily switches the git index to `new_index` for the duration of
48
+ # the block
49
+ #
50
+ # Rebuilds the repository execution context to point to the new index file,
51
+ # yields `self`, then unconditionally restores the original execution
52
+ # context — even if the block raises an exception.
53
+ #
54
+ # @example Read a tree into a custom index
55
+ # repo.with_index('/tmp/custom.index') do
56
+ # repo.read_tree('HEAD')
57
+ # end
58
+ #
59
+ # @param new_index [String, Pathname] path to the replacement index file
60
+ #
61
+ # @return [Object] the value returned by the block
62
+ #
63
+ # @yield [repo] the repository instance with the new index active
64
+ #
65
+ # @yieldparam repo [Git::Repository] `self`
66
+ #
67
+ # @yieldreturn [Object] returned as the method's return value
68
+ #
69
+ def with_index(new_index) # :yields: self
70
+ old_context = @execution_context
71
+ set_index(new_index, must_exist: false)
72
+ yield self
73
+ ensure
74
+ @execution_context = old_context
75
+ end
76
+
77
+ # Temporarily switches the git index to a new temporary file for the
78
+ # duration of the block, then removes the file
79
+ #
80
+ # The temporary index file does not exist until git creates it on first
81
+ # write. A unique temporary directory is created to hold the index path,
82
+ # avoiding the risk of presenting an empty file to git (which git would
83
+ # reject as a corrupt index). The directory — and any files inside it —
84
+ # are removed unconditionally after the block exits, even if the block
85
+ # raises an exception.
86
+ #
87
+ # @example Stage changes using a temporary index
88
+ # repo.with_temp_index do
89
+ # repo.read_tree('HEAD')
90
+ # repo.write_tree
91
+ # end
92
+ #
93
+ # @return [Object] the value returned by the block
94
+ #
95
+ # @yield [repo] the repository instance with the temporary index active
96
+ #
97
+ # @yieldparam repo [Git::Repository] `self`
98
+ #
99
+ # @yieldreturn [Object] returned as the method's return value
100
+ #
101
+ def with_temp_index(&) # :yields: self
102
+ # Use a unique temp directory so the index file path is collision-free
103
+ # and does not exist until git writes it. An existing empty file would
104
+ # be treated as a corrupt index by git.
105
+ temp_dir = Dir.mktmpdir('git-temp-index-')
106
+ begin
107
+ with_index(File.join(temp_dir, 'index'), &)
108
+ ensure
109
+ FileUtils.remove_entry(temp_dir, true)
110
+ end
111
+ end
112
+
113
+ # Temporarily switches the git working directory to `work_dir` for the
114
+ # duration of the block
115
+ #
116
+ # Rebuilds the repository execution context to point to the new working
117
+ # directory, changes the process working directory via `Dir.chdir`, yields
118
+ # `self`, then unconditionally restores the original execution context —
119
+ # even if the block raises an exception.
120
+ #
121
+ # @example Commit changes from a different worktree path
122
+ # repo.with_working('/path/to/worktree') do
123
+ # repo.add('.')
124
+ # repo.commit('chore: automated update')
125
+ # end
126
+ #
127
+ # @param work_dir [String, Pathname] path to the replacement working
128
+ # directory
129
+ #
130
+ # @return [Object] the value returned by the block
131
+ #
132
+ # @raise [ArgumentError] if `work_dir` does not exist on disk
133
+ #
134
+ # @yield [repo] the repository instance with the new working directory
135
+ # active
136
+ #
137
+ # @yieldparam repo [Git::Repository] `self`
138
+ #
139
+ # @yieldreturn [Object] returned as the method's return value
140
+ #
141
+ def with_working(work_dir) # :yields: self
142
+ old_context = @execution_context
143
+ set_working(work_dir)
144
+ Dir.chdir(dir.to_s) { yield self }
145
+ ensure
146
+ @execution_context = old_context
147
+ end
148
+
149
+ # Temporarily switches the git working directory to a new temporary
150
+ # directory for the duration of the block, then removes the directory and
151
+ # its contents
152
+ #
153
+ # The temporary directory is removed unconditionally after the block
154
+ # exits, even if the block raises an exception.
155
+ #
156
+ # @example Write files in an isolated temporary working directory
157
+ # repo.with_temp_working do
158
+ # File.write('scratch.txt', 'temporary content')
159
+ # end
160
+ #
161
+ # @return [Object] the value returned by the block
162
+ #
163
+ # @yield [repo] the repository instance with the temporary working
164
+ # directory active
165
+ #
166
+ # @yieldparam repo [Git::Repository] `self`
167
+ #
168
+ # @yieldreturn [Object] returned as the method's return value
169
+ #
170
+ def with_temp_working(&block) # :yields: self
171
+ Dir.mktmpdir('temp-workdir') { |temp_dir| with_working(temp_dir, &block) }
172
+ end
173
+
174
+ # Sets the git index to `index_file` and rebuilds the execution context
175
+ #
176
+ # By default raises if `index_file` does not exist. Pass `must_exist:
177
+ # false` to skip the existence check (useful when the index will be
178
+ # created by git later).
179
+ #
180
+ # @example Set the index to a custom path
181
+ # repo.set_index('/path/to/custom.index')
182
+ #
183
+ # @param index_file [String, Pathname] path to the new index file
184
+ #
185
+ # @param check [Boolean, nil] deprecated positional argument — use
186
+ # `must_exist:` instead; emits a deprecation warning when non-`nil`
187
+ #
188
+ # @param must_exist [Boolean, nil] when `true` (the default), raises
189
+ # `ArgumentError` if `index_file` does not exist on disk
190
+ #
191
+ # @return [void]
192
+ #
193
+ # @raise [ArgumentError] if `must_exist: true` (the default) and
194
+ # `index_file` does not exist
195
+ #
196
+ def set_index(index_file, check = nil, must_exist: nil)
197
+ must_exist = context_helpers_deprecate_check_argument(check, must_exist)
198
+ new_path = context_helpers_validate_path(index_file, must_exist)
199
+ context_helpers_rebuild_context(git_index_file: new_path.to_s)
200
+ nil
201
+ end
202
+
203
+ # Sets the git working directory to `work_dir` and rebuilds the execution
204
+ # context
205
+ #
206
+ # By default raises if `work_dir` does not exist. Pass `must_exist:
207
+ # false` to skip the existence check.
208
+ #
209
+ # @example Set the working directory to a custom path
210
+ # repo.set_working('/path/to/working')
211
+ #
212
+ # @param work_dir [String, Pathname] path to the new working directory
213
+ #
214
+ # @param check [Boolean, nil] deprecated positional argument — use
215
+ # `must_exist:` instead; emits a deprecation warning when non-`nil`
216
+ #
217
+ # @param must_exist [Boolean, nil] when `true` (the default), raises
218
+ # `ArgumentError` if `work_dir` does not exist on disk
219
+ #
220
+ # @return [void]
221
+ #
222
+ # @raise [ArgumentError] if `must_exist: true` (the default) and
223
+ # `work_dir` does not exist
224
+ #
225
+ def set_working(work_dir, check = nil, must_exist: nil)
226
+ must_exist = context_helpers_deprecate_check_argument(check, must_exist)
227
+ new_path = context_helpers_validate_path(work_dir, must_exist)
228
+ context_helpers_rebuild_context(git_work_dir: new_path.to_s)
229
+ nil
230
+ end
231
+
232
+ private
233
+
234
+ # Resolves deprecated `check` argument semantics with `must_exist:`
235
+ #
236
+ # @param check [Boolean, nil] deprecated positional existence-check value
237
+ #
238
+ # @param must_exist [Boolean, nil] keyword existence-check override
239
+ #
240
+ # @return [Boolean] whether path existence must be enforced
241
+ #
242
+ def context_helpers_deprecate_check_argument(check, must_exist)
243
+ if !check.nil? && defined?(Git::Deprecation)
244
+ Git::Deprecation.warn(
245
+ 'The "check" argument is deprecated and will be removed in v6.0.0. ' \
246
+ 'Use "must_exist:" instead.'
247
+ )
248
+ end
249
+ # Preserve the original Git::Base semantics: when both the deprecated
250
+ # positional `check` and the new `must_exist:` keyword are given, OR
251
+ # them so the more restrictive value wins.
252
+ #
253
+ # NilClass#| is defined in Ruby: nil | false → false, nil | true → true.
254
+ # This means single-argument callers (check only, or must_exist: only)
255
+ # are handled correctly without any nil-special-casing.
256
+ return true if must_exist.nil? && check.nil?
257
+
258
+ must_exist | check
259
+ end
260
+
261
+ # Expands `path` and validates existence when required
262
+ #
263
+ # @param path [String, Pathname] path to normalize and validate
264
+ #
265
+ # @param must_exist [Boolean] whether the expanded path must already exist
266
+ #
267
+ # @return [Pathname] the expanded absolute path
268
+ #
269
+ # @raise [ArgumentError] if `must_exist` is `true` and the path does not exist
270
+ #
271
+ def context_helpers_validate_path(path, must_exist)
272
+ Pathname.new(File.expand_path(path.to_s)).tap do |expanded_path|
273
+ raise ArgumentError, "path does not exist: #{expanded_path}" if must_exist && !expanded_path.exist?
274
+ end
275
+ end
276
+
277
+ # Rebuilds the repository execution context with selected overrides
278
+ #
279
+ # @param overrides [Hash] execution-context attributes to override
280
+ #
281
+ # @option overrides [String, nil] :git_index_file replacement index file path
282
+ #
283
+ # @option overrides [String, nil] :git_work_dir replacement working directory
284
+ # path
285
+ #
286
+ # @return [void]
287
+ #
288
+ def context_helpers_rebuild_context(**overrides)
289
+ @execution_context = @execution_context.dup_with(**overrides)
290
+ end
291
+ end
292
+ end
293
+ end