git 5.6.0 → 6.0.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +132 -16
  3. data/CONTRIBUTING.md +44 -31
  4. data/README.md +135 -67
  5. data/UPGRADING.md +915 -268
  6. data/git.gemspec +6 -8
  7. data/lib/git/author_info.rb +1 -2
  8. data/lib/git/commands/base.rb +22 -2
  9. data/lib/git/commands/cat_file/raw.rb +0 -68
  10. data/lib/git/commands/maintenance/register.rb +0 -4
  11. data/lib/git/commands/maintenance/run.rb +0 -4
  12. data/lib/git/commands/maintenance/start.rb +0 -4
  13. data/lib/git/commands/maintenance/stop.rb +0 -4
  14. data/lib/git/commands/maintenance/unregister.rb +0 -4
  15. data/lib/git/commands/maintenance.rb +0 -2
  16. data/lib/git/commands/show_ref/exclude_existing.rb +1 -1
  17. data/lib/git/commands/show_ref/exists.rb +0 -5
  18. data/lib/git/commands/show_ref/list.rb +1 -1
  19. data/lib/git/commands/show_ref/verify.rb +1 -1
  20. data/lib/git/commands/show_ref.rb +1 -1
  21. data/lib/git/commands/worktree/repair.rb +0 -5
  22. data/lib/git/diff_path_status.rb +2 -2
  23. data/lib/git/encoding_utils.rb +25 -0
  24. data/lib/git/errors.rb +23 -5
  25. data/lib/git/factories.rb +361 -125
  26. data/lib/git/log.rb +3 -75
  27. data/lib/git/object.rb +4 -185
  28. data/lib/git/path_resolver.rb +104 -36
  29. data/lib/git/repository/branching.rb +21 -189
  30. data/lib/git/repository/committing.rb +0 -25
  31. data/lib/git/repository/context_helpers.rb +249 -155
  32. data/lib/git/repository/diffing.rb +2 -44
  33. data/lib/git/repository/merging.rb +32 -54
  34. data/lib/git/repository/object_operations.rb +121 -338
  35. data/lib/git/repository/remote_operations.rb +7 -219
  36. data/lib/git/repository/staging.rb +6 -184
  37. data/lib/git/repository/stashing.rb +126 -130
  38. data/lib/git/repository/status_operations.rb +1 -54
  39. data/lib/git/repository/worktree_operations.rb +31 -118
  40. data/lib/git/repository.rb +20 -299
  41. data/lib/git/system_call_guard.rb +95 -0
  42. data/lib/git/version.rb +1 -1
  43. data/lib/git.rb +47 -364
  44. metadata +25 -19
  45. data/lib/git/author.rb +0 -46
  46. data/lib/git/branch.rb +0 -693
  47. data/lib/git/branches.rb +0 -194
  48. data/lib/git/remote.rb +0 -191
  49. data/lib/git/stash.rb +0 -124
  50. data/lib/git/stashes.rb +0 -198
  51. data/lib/git/status.rb +0 -384
  52. data/lib/git/worktree.rb +0 -180
  53. data/lib/git/worktrees.rb +0 -152
data/lib/git/branch.rb DELETED
@@ -1,693 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'branch_info'
4
-
5
- module Git
6
- # Represents a Git branch
7
- #
8
- # Branch objects provide access to branch metadata and operations like checkout,
9
- # delete, and merge. They should be obtained via {Git::Repository#branch} or
10
- # {Git::Repository#branches}, not constructed directly.
11
- #
12
- # @example Getting a branch
13
- # git = Git.open('.')
14
- # branch = git.branch('main')
15
- # branch.checkout
16
- #
17
- # @example Listing branches
18
- # git.branches.each { |b| puts b.name }
19
- #
20
- # @deprecated Use {Git::Repository::Branching#branch_list} and the
21
- # name-based branch operations on {Git::Repository} instead
22
- #
23
- # {Git::Repository::Branching#branch_list} returns immutable
24
- # {Git::BranchInfo} value objects. Operations that lived on this class are
25
- # called on the repository with the branch name instead (for example
26
- # {Git::Repository::Branching#checkout} and
27
- # {Git::Repository::Branching#branch_delete}). Every operation on a
28
- # `Git::Branch` emits a deprecation warning; the `full`, `name`, `remote`,
29
- # `to_s`, and `to_a` readers do not.
30
- #
31
- # @api public
32
- #
33
- class Branch
34
- # The full refname of this branch
35
- #
36
- # For local branches this is the short name (e.g. `'main'`). For
37
- # remote-tracking branches obtained via {Git::Repository#branches} this includes
38
- # the `remotes/` prefix (e.g. `'remotes/origin/main'`). Branches constructed
39
- # by {Git::Remote#branch} use the `<remote>/<branch>` form (e.g.
40
- # `'origin/main'`) which does **not** populate {#remote}.
41
- #
42
- # @example Local and remote-tracking branch full refnames
43
- # git.branch('main').full #=> 'main'
44
- # git.branch('remotes/origin/main').full #=> 'remotes/origin/main'
45
- #
46
- # @return [String] the full refname
47
- #
48
- attr_accessor :full
49
-
50
- # The remote for this branch, or `nil` for local or bare-name remote-tracking branches
51
- #
52
- # Set to a {Git::Remote} object only when this branch was initialized with a
53
- # `remotes/<remote>/` or `refs/remotes/<remote>/` prefix. `nil` for local
54
- # branches and for remote-tracking branches in `<remote>/<branch>` form
55
- # (such as those returned by {Git::Remote#branch}).
56
- #
57
- # @example Local and remote-tracking branches
58
- # git.branch('main').remote #=> nil
59
- # git.branch('remotes/origin/main').remote #=> #<Git::Remote 'origin'>
60
- # git.remote('origin').branch('main').remote #=> nil # uses 'origin/main' form
61
- #
62
- # @return [Git::Remote, nil] the remote object, or `nil`
63
- #
64
- attr_accessor :remote
65
-
66
- # The short branch name without the remote prefix
67
- #
68
- # For both local and remote-tracking branches this is the bare branch
69
- # name (e.g. `'main'` rather than `'remotes/origin/main'`).
70
- #
71
- # @example Local and remote-tracking branch short names
72
- # git.branch('main').name #=> 'main'
73
- # git.branch('remotes/origin/main').name #=> 'main'
74
- #
75
- # @return [String] the short branch name
76
- #
77
- attr_accessor :name
78
-
79
- # Initialize a new Branch object
80
- #
81
- # @param base [Git::Repository] the git repository
82
- #
83
- # @param branch_info_or_name [Git::BranchInfo, String] branch info object or name string
84
- #
85
- # Passing a BranchInfo is preferred; String support is for backward compatibility.
86
- #
87
- # @note Use {Git::Repository#branch} or {Git::Repository#branches} instead of constructing directly
88
- #
89
- # @api private
90
- #
91
- def initialize(base, branch_info_or_name)
92
- @base = base
93
- @gcommit = nil
94
- @stashes = nil
95
-
96
- initialize_from_argument(branch_info_or_name)
97
- end
98
-
99
- # Returns the commit at the tip of this branch
100
- #
101
- # The result is memoized after the first call.
102
- #
103
- # @example Get the tip commit
104
- # git.branch('main').gcommit #=> #<Git::Object ...>
105
- #
106
- # @return [Git::Object] the commit at the tip of this branch
107
- #
108
- # @deprecated Use {Git::Repository::ObjectOperations#gcommit} with the branch name instead
109
- #
110
- # Pass the branch name for a local branch, or `"remotes/#{remote}/#{name}"`
111
- # (the value of {#full}) for a remote-tracking branch; the shorter
112
- # `"#{remote}/#{name}"` can resolve a local branch of that name.
113
- #
114
- # @see Git::Repository::ObjectOperations#gcommit
115
- #
116
- def gcommit
117
- Git::Deprecation.warn(
118
- 'Git::Branch#gcommit is deprecated and will be removed in v6.0.0. ' \
119
- 'Use Git::Repository#gcommit(name) or, for a remote-tracking branch, ' \
120
- 'Git::Repository#gcommit("remotes/remote/name") instead.'
121
- )
122
- @gcommit ||= branch_repository.gcommit(@full)
123
- @gcommit
124
- end
125
-
126
- # Returns the stash list for this repository
127
- #
128
- # This method ignores the branch receiver and returns every stash in the
129
- # repository, so `git.branch('feature').stashes` and
130
- # `git.branch('main').stashes` return the same entries. It is deprecated and
131
- # will be removed in v6.0.0.
132
- #
133
- # The result is memoized after the first call.
134
- #
135
- # @example Iterate over stash entries (deprecated)
136
- # git.branch('main').stashes.each { |s| puts s }
137
- #
138
- # @example The replacement
139
- # repo.stash_infos.each { |info| puts info.message }
140
- #
141
- # @return [Git::Stashes] the stash list
142
- #
143
- # @deprecated Use {Git::Repository#stash_infos} instead
144
- #
145
- # @see Git::Repository#stash_infos
146
- #
147
- def stashes
148
- Git::Deprecation.warn(
149
- 'Git::Branch#stashes is deprecated and will be removed in v6.0.0. ' \
150
- 'It ignores the branch and returns all repository stashes. ' \
151
- 'Use Git::Repository#stash_infos instead.'
152
- )
153
- # Git::Stashes is deprecated too; silence it so one stashes call emits one warning
154
- @stashes ||= Git::Deprecation.silence { Git::Stashes.new(branch_repository) }
155
- end
156
-
157
- # Checks out this branch, attempting to create it first if it does not already exist
158
- #
159
- # Branch creation is attempted via {#check_if_create}; any error from that
160
- # step is silently ignored and the checkout proceeds regardless.
161
- #
162
- # **Note:** for remote-tracking branches (where {#remote} is not `nil`),
163
- # `check_if_create` will attempt to create a *local* branch named {#name}
164
- # as a side-effect before checking out {#full} (which typically results in
165
- # a detached HEAD). This is a known limitation; see
166
- # [ruby-git#1280](https://github.com/ruby-git/ruby-git/issues/1280).
167
- #
168
- # @example Check out a branch
169
- # git = Git.open('.')
170
- # git.branch('main').checkout
171
- #
172
- # @return [String] git's stdout from the checkout
173
- #
174
- # @raise [Git::FailedError] if git exits with a non-zero exit status
175
- #
176
- # @deprecated Use {Git::Repository::Branching#checkout} with the branch name instead
177
- #
178
- # {Git::Repository::Branching#checkout} does not create a missing local
179
- # branch, apart from the guess git makes on its own: with no `:no_guess`
180
- # option, git creates a tracking branch when exactly one remote has a
181
- # branch of that name. To reproduce the create-or-checkout behavior of
182
- # this method, call {Git::Repository::Branching#branch_new} when
183
- # {Git::Repository::Branching#local_branch?} is false, then
184
- # {Git::Repository::Branching#checkout}. Pass `"remotes/#{remote}/#{name}"`
185
- # (the value of {#full}) for a remote-tracking branch; the shorter
186
- # `"#{remote}/#{name}"` can resolve a local branch of that name.
187
- #
188
- # @see Git::Repository::Branching#checkout
189
- #
190
- # @see Git::Repository::Branching#branch_new
191
- #
192
- def checkout
193
- Git::Deprecation.warn(
194
- 'Git::Branch#checkout is deprecated and will be removed in v6.0.0. ' \
195
- 'Use Git::Repository#checkout(name) or, for a remote-tracking branch, ' \
196
- 'Git::Repository#checkout("remotes/remote/name") instead. Git::Repository#checkout does not ' \
197
- 'create a missing local branch (beyond the guess git makes from a unique remote-tracking ' \
198
- 'branch); call Git::Repository#branch_new first unless Git::Repository#local_branch? is true.'
199
- )
200
- check_if_create
201
- branch_repository.checkout(@full)
202
- end
203
-
204
- # Archives this branch and writes the result to a file
205
- #
206
- # @example Archive to a tar file
207
- # git.branch('main').archive('/tmp/main.tar', format: 'tar')
208
- #
209
- # @example Archive to a zip file
210
- # git.branch('main').archive('/tmp/main.zip', format: 'zip')
211
- #
212
- # @example Archive a remote branch to a tgz file
213
- # git.remote('origin').branch('main').archive('/tmp/main.tgz', format: 'tgz')
214
- #
215
- # @param file [String] path to the destination archive file
216
- #
217
- # @param opts [Hash] archive options (see {Git::Repository#archive})
218
- #
219
- # @option opts [String] :format ('zip') archive format for this wrapper:
220
- # `'tar'`, `'zip'`, or `'tgz'`
221
- #
222
- # @option opts [String] :prefix (nil) prefix prepended to every filename
223
- # in the archive
224
- #
225
- # @option opts [String] :path (nil) path within the tree to include in the
226
- # archive
227
- #
228
- # @option opts [String] :remote (nil) retrieve the archive from a remote
229
- # repository
230
- #
231
- # @option opts [Boolean, nil] :add_gzip (nil) apply gzip compression after
232
- # writing the archive; set automatically when `format: 'tgz'` is given
233
- #
234
- # @return [String] the path to the written archive file
235
- #
236
- # @raise [ArgumentError] when archive options or destination path are invalid
237
- #
238
- # @raise [Git::FailedError] if `git archive` fails
239
- #
240
- # @deprecated Use {Git::Repository::ObjectOperations#archive} with the branch name instead
241
- #
242
- # Pass the branch name for a local branch, or `"remotes/#{remote}/#{name}"`
243
- # (the value of {#full}) for a remote-tracking branch; the shorter
244
- # `"#{remote}/#{name}"` can resolve a local branch of that name.
245
- #
246
- # @see Git::Repository::ObjectOperations#archive
247
- #
248
- def archive(file, opts = {})
249
- Git::Deprecation.warn(
250
- 'Git::Branch#archive is deprecated and will be removed in v6.0.0. ' \
251
- 'Use Git::Repository#archive(name, file, opts) or, for a remote-tracking branch, ' \
252
- 'Git::Repository#archive("remotes/remote/name", file, opts) instead.'
253
- )
254
- branch_repository.archive(@full, file, opts)
255
- end
256
-
257
- # Checks out this branch for the duration of a block, then restores the original branch
258
- #
259
- # If the block returns a truthy value, all pending changes are committed with the
260
- # given message before switching back to the original branch. If the block returns
261
- # a falsy value, a hard reset is performed before switching back.
262
- #
263
- # **Note:** the restore checkout is not wrapped in `ensure`. If the block,
264
- # the commit, or the reset raises an exception, the repository will be left
265
- # checked out on this branch rather than restored to the original.
266
- #
267
- # @example Commit a new file on a feature branch
268
- # git.branch('feature').in_branch('Add README') do
269
- # File.write('README.md', '# Hello')
270
- # git.add('README.md')
271
- # true # commit and return to original branch
272
- # end
273
- #
274
- # @param message [String] commit message used when the block returns truthy
275
- #
276
- # @return [String] git's stdout from the final checkout back to the original branch
277
- #
278
- # @raise [Git::FailedError] if any of the underlying git operations (checkout, commit, reset) fail
279
- #
280
- # @yield Executes the block with this branch checked out
281
- #
282
- # @yieldreturn [Object] return a truthy value to commit all changes, a falsy value to hard-reset
283
- #
284
- # @deprecated Use {Git::Repository::Branching#in_branch} with the branch name instead
285
- #
286
- # {Git::Repository::Branching#in_branch} does not create the branch and
287
- # restores a detached HEAD to its original commit.
288
- # It takes an existing local branch, so a remote-tracking `Git::Branch` has
289
- # no direct replacement: this method checked out the remote-tracking ref,
290
- # detaching HEAD. Create a local branch from that ref with
291
- # {Git::Repository::Branching#branch_new} first.
292
- #
293
- # @see Git::Repository::Branching#in_branch
294
- #
295
- def in_branch(message = 'in branch work')
296
- Git::Deprecation.warn(
297
- 'Git::Branch#in_branch is deprecated and will be removed in v6.0.0. ' \
298
- 'Use Git::Repository#in_branch(name, message) instead. It takes an existing local ' \
299
- 'branch; for a remote-tracking branch, create a local branch from it first.'
300
- )
301
- old_current = branch_repository.current_branch
302
- # checkout is deprecated too; silence it so one in_branch call emits one warning
303
- Git::Deprecation.silence { checkout }
304
- yield ? branch_repository.commit_all(message) : branch_repository.reset(nil, hard: true)
305
- branch_repository.checkout(old_current)
306
- end
307
-
308
- # Creates this branch if it does not already exist
309
- #
310
- # Silently ignores any error raised during branch creation (including the case
311
- # where the branch already exists).
312
- #
313
- # @example Create a new branch
314
- # git.branch('feature').create
315
- #
316
- # @return [nil]
317
- #
318
- # @deprecated Use {Git::Repository::Branching#branch_new} instead
319
- #
320
- # {Git::Repository::Branching#branch_new} raises {Git::FailedError} when
321
- # the branch already exists rather than ignoring the error.
322
- #
323
- # @see Git::Repository::Branching#branch_new
324
- #
325
- def create
326
- Git::Deprecation.warn(
327
- 'Git::Branch#create is deprecated and will be removed in v6.0.0. ' \
328
- 'Use Git::Repository#branch_new instead.'
329
- )
330
- check_if_create
331
- end
332
-
333
- # Deletes this branch
334
- #
335
- # Remote-tracking branches (one where {#remote} is not `nil`) delete the
336
- # local remote-tracking ref; they do not push a deletion to the remote.
337
- #
338
- # @example Delete a local branch
339
- # git.branch('old-feature').delete
340
- #
341
- # @return [String] git's deletion output
342
- #
343
- # @raise [Git::Error] if the branch cannot be deleted
344
- #
345
- # @deprecated Use {Git::Repository::Branching#branch_delete} instead
346
- #
347
- # Pass the branch name for a local branch, or `"#{remote}/#{name}"` with
348
- # `remotes: true` for a remote-tracking branch.
349
- #
350
- # @see Git::Repository::Branching#branch_delete
351
- #
352
- def delete
353
- Git::Deprecation.warn(
354
- 'Git::Branch#delete is deprecated and will be removed in v6.0.0. ' \
355
- 'Use Git::Repository#branch_delete(name) or, for a remote-tracking branch, ' \
356
- 'Git::Repository#branch_delete("remote/name", remotes: true) instead.'
357
- )
358
- if @remote
359
- branch_repository.branch_delete("#{@remote.name}/#{@name}", remotes: true)
360
- else
361
- branch_repository.branch_delete(@name)
362
- end
363
- end
364
-
365
- # Returns true if this is the currently checked-out branch
366
- #
367
- # **Note:** this compares the current branch's short name against {#name}.
368
- # For a remote-tracking branch (where {#remote} is not `nil`), {#name} is
369
- # still the bare short name (e.g. `'main'`), so this will return `true`
370
- # whenever the *local* branch with that name is checked out — not the
371
- # remote-tracking ref itself.
372
- #
373
- # @example Check whether currently on main
374
- # git.branch('main').current #=> true
375
- #
376
- # @return [Boolean] whether this branch is currently checked out
377
- #
378
- # @raise [Git::FailedError] if git exits with a non-zero exit status
379
- #
380
- # @deprecated Compare {Git::Repository::Branching#current_branch} with the
381
- # branch name instead
382
- #
383
- # @see Git::Repository::Branching#current_branch
384
- #
385
- def current # rubocop:disable Naming/PredicateMethod
386
- Git::Deprecation.warn(
387
- 'Git::Branch#current is deprecated and will be removed in v6.0.0. ' \
388
- 'Use Git::Repository#current_branch == name instead.'
389
- )
390
- branch_repository.current_branch == @name
391
- end
392
-
393
- # Returns true if this branch contains the given commit
394
- #
395
- # **Note:** this queries local branches by short name. For a remote-tracking
396
- # branch (where {#remote} is not `nil`), it checks the *local* branch with
397
- # the same {#name} rather than the remote-tracking ref, which may give an
398
- # inaccurate result.
399
- #
400
- # @example Check if a commit is reachable from this branch
401
- # git.branch('main').contains?('abc1234') #=> true
402
- #
403
- # @param commit [String] the commit SHA or ref to check
404
- #
405
- # @return [Boolean] whether this branch contains the given commit
406
- #
407
- # @raise [Git::FailedError] if git exits with a non-zero exit status
408
- #
409
- # @deprecated Use {Git::Repository::Branching#branch_contains} with the
410
- # commit and branch name instead
411
- #
412
- # {Git::Repository::Branching#branch_contains} returns the matching
413
- # branch names as a String; test it with `empty?`.
414
- #
415
- # @see Git::Repository::Branching#branch_contains
416
- #
417
- def contains?(commit)
418
- Git::Deprecation.warn(
419
- 'Git::Branch#contains? is deprecated and will be removed in v6.0.0. ' \
420
- 'Use !Git::Repository#branch_contains(commit, name).empty? instead.'
421
- )
422
- !branch_repository.branch_contains(commit, name).empty?
423
- end
424
-
425
- # Merges a branch into this branch, or merges this branch into the current branch
426
- #
427
- # @overload merge(branch, message = nil)
428
- #
429
- # Temporarily checks out this branch, merges the given branch into it,
430
- # then restores the original branch.
431
- #
432
- # **Note:** if `self` is a remote-tracking branch (where {#remote} is not
433
- # `nil`), this delegates to {#checkout} which has the detached-HEAD
434
- # side-effect described there. The remote-tracking ref will not be updated.
435
- #
436
- # @example Merge a feature branch into main
437
- # git.branch('main').merge('feature')
438
- #
439
- # @param branch [String] the name of the branch to merge into this one
440
- #
441
- # @param message [String, nil] commit message for the merge commit
442
- #
443
- # @return [String] git's stdout from the final checkout back to the original branch
444
- #
445
- # @overload merge()
446
- #
447
- # Merges this branch into the currently checked-out branch.
448
- #
449
- # @example Merge main into the current branch
450
- # git.branch('main').merge
451
- #
452
- # @return [String] git's stdout from the merge command
453
- #
454
- # @raise [Git::FailedError] if git exits with a non-zero exit status
455
- #
456
- # @deprecated Use {Git::Repository::Merging#merge_into} in place of
457
- # `merge(branch)` and {Git::Repository::Merging#merge} with the branch
458
- # name in place of `merge()`
459
- #
460
- # {Git::Repository::Merging#merge_into} returns the merge's stdout, does
461
- # not hard-reset after the merge, and restores a detached HEAD to its
462
- # original commit.
463
- # It takes an existing local branch, so a remote-tracking `Git::Branch` has
464
- # no direct replacement: `merge(branch)` checked out the remote-tracking ref,
465
- # detaching HEAD. Create a local branch from that ref with
466
- # {Git::Repository::Branching#branch_new} first.
467
- #
468
- # @see Git::Repository::Merging#merge_into
469
- #
470
- # @see Git::Repository::Merging#merge
471
- #
472
- def merge(branch = nil, message = nil)
473
- if branch
474
- merge_into_this_branch(branch, message)
475
- else
476
- merge_into_current_branch
477
- end
478
- end
479
-
480
- # Updates the git ref for this branch to point to the given commit
481
- #
482
- # The target ref depends on whether {#remote} is set:
483
- # - When {#remote} is not `nil` (i.e. the branch was initialized with a
484
- # `remotes/<remote>/` or `refs/remotes/<remote>/` prefix), updates
485
- # `refs/remotes/<remote>/<name>`.
486
- # - Otherwise updates `refs/heads/<name>`. Note that branches in the
487
- # `<remote>/<branch>` form (e.g. those returned by {Git::Remote#branch})
488
- # have `remote == nil` and therefore update `refs/heads/<remote>/<name>`,
489
- # **not** `refs/remotes/...`.
490
- #
491
- # @example Advance a local branch to a new commit
492
- # git.branch('feature').update_ref('abc1234def5678')
493
- #
494
- # @param commit [String] the commit SHA to point this branch at
495
- #
496
- # @return [Git::CommandLine::Result] the result of calling `git update-ref`
497
- #
498
- # @raise [Git::FailedError] if git exits with a non-zero exit status
499
- #
500
- # @deprecated Use {Git::Repository::Branching#update_ref} instead
501
- #
502
- # Pass the branch name for a local branch, or
503
- # `"remotes/#{remote}/#{name}"` for a remote-tracking branch.
504
- #
505
- # @see Git::Repository::Branching#update_ref
506
- #
507
- def update_ref(commit)
508
- Git::Deprecation.warn(
509
- 'Git::Branch#update_ref is deprecated and will be removed in v6.0.0. ' \
510
- 'Use Git::Repository#update_ref(name, commit) or, for a remote-tracking branch, ' \
511
- 'Git::Repository#update_ref("remotes/remote/name", commit) instead.'
512
- )
513
- if @remote
514
- branch_repository.update_ref("remotes/#{@remote.name}/#{@name}", commit)
515
- else
516
- branch_repository.update_ref(@name, commit)
517
- end
518
- end
519
-
520
- # Returns this branch as a single-element array containing its full refname
521
- #
522
- # @example Get branch as array
523
- # git.branch('main').to_a #=> ['main']
524
- #
525
- # @return [Array<String>] a single-element array containing the full refname
526
- #
527
- def to_a
528
- [@full]
529
- end
530
-
531
- # Returns the full refname of this branch as a string
532
- #
533
- # @example Get branch as string
534
- # git.branch('main').to_s #=> 'main'
535
- #
536
- # @return [String] the full refname
537
- #
538
- def to_s
539
- @full
540
- end
541
-
542
- # Regular expression for parsing branch refnames
543
- #
544
- # Matches full and short refnames, capturing an optional remote name and the
545
- # branch name. Used internally to identify remote-tracking branches.
546
- #
547
- # @note This legacy string-constructor path does not resolve remote names
548
- # containing `/`. Use {Git::Repository#branch_list} to build branch objects
549
- # from remote-aware {Git::BranchInfo} values.
550
- #
551
- # @api private
552
- #
553
- BRANCH_NAME_REGEXP = %r{
554
- ^
555
- # Optional 'remotes/' or 'refs/remotes/' at the beginning to specify a remote tracking branch
556
- # with a <remote_name>. <remote_name> is nil if not present.
557
- (?:
558
- (?:(?:refs/)?remotes/)(?<remote_name>[^/]+)/
559
- )?
560
- (?<branch_name>.*)
561
- $
562
- }x
563
-
564
- private
565
-
566
- # Dispatches initialization to the appropriate strategy
567
- #
568
- # @param branch_info_or_name [Git::BranchInfo, String] branch info or name string
569
- #
570
- # @return [nil]
571
- #
572
- # @api private
573
- #
574
- def initialize_from_argument(branch_info_or_name)
575
- if branch_info_or_name.is_a?(Git::BranchInfo)
576
- initialize_from_branch_info(branch_info_or_name)
577
- else
578
- initialize_from_name(branch_info_or_name)
579
- end
580
- end
581
-
582
- # Initialize from a BranchInfo object (preferred path)
583
- #
584
- # @param branch_info [Git::BranchInfo] the branch info
585
- #
586
- # @return [nil]
587
- #
588
- def initialize_from_branch_info(branch_info)
589
- @name = branch_info.short_name
590
- remote_name = branch_info.remote_name
591
- # Git::Remote is deprecated too; silence it so one Git::Branch call emits one warning
592
- @remote = remote_name ? Git::Deprecation.silence { Git::Remote.new(@base, remote_name) } : nil
593
- @full = @remote ? "remotes/#{@remote.name}/#{@name}" : @name
594
- end
595
-
596
- # Initialize from a string name (legacy path for backward compatibility)
597
- #
598
- # @param name [String] the branch name
599
- #
600
- # @return [nil]
601
- #
602
- def initialize_from_name(name)
603
- @full = name
604
- @remote, @name = parse_name(name)
605
- end
606
-
607
- # Parses a full branch name into remote and short branch name components
608
- #
609
- # Strips an optional `remotes/` or `refs/remotes/` prefix. Only inputs that
610
- # begin with one of those prefixes yield a remote object; all other inputs
611
- # (including `'origin/master'`) are treated as local branch names with a
612
- # `nil` remote.
613
- #
614
- # @example Local branches
615
- # parse_name('master') #=> [nil, 'master']
616
- # parse_name('origin/master') #=> [nil, 'origin/master']
617
- #
618
- # @example Remote-tracking branches
619
- # parse_name('remotes/origin/master') #=> [#<Git::Remote 'origin'>, 'master']
620
- # parse_name('refs/remotes/origin/master') #=> [#<Git::Remote 'origin'>, 'master']
621
- #
622
- # @param name [String] the full branch name to parse
623
- #
624
- # @return [Array(Git::Remote, String)] a two-element array; the first element is
625
- # a {Git::Remote} for remote-tracking branches or `nil` for local branches,
626
- # and the second element is the short branch name
627
- #
628
- def parse_name(name)
629
- # Expect this will always match
630
- match = name.match(BRANCH_NAME_REGEXP)
631
- remote_name = match[:remote_name]
632
- # Git::Remote is deprecated too; silence it so one Git::Branch call emits one warning
633
- remote = remote_name ? Git::Deprecation.silence { Git::Remote.new(@base, remote_name) } : nil
634
- branch_name = match[:branch_name]
635
- [remote, branch_name]
636
- end
637
-
638
- # Merges the given branch into this branch, then restores the original branch
639
- #
640
- # @param branch [String] the name of the branch to merge into this one
641
- #
642
- # @param message [String, nil] commit message for the merge commit
643
- #
644
- # @return [String] git's stdout from the final checkout back to the original branch
645
- #
646
- # @api private
647
- #
648
- def merge_into_this_branch(branch, message)
649
- Git::Deprecation.warn(
650
- 'Git::Branch#merge(branch) is deprecated and will be removed in v6.0.0. ' \
651
- 'Use Git::Repository#merge_into(name, branch, message) instead. It takes an existing ' \
652
- 'local branch; for a remote-tracking branch, create a local branch from it first.'
653
- )
654
- # in_branch is deprecated too; silence it so one merge call emits one warning.
655
- # The falsy block value makes in_branch hard-reset instead of committing.
656
- Git::Deprecation.silence do
657
- in_branch { branch_repository.merge(branch, message) && false }
658
- end
659
- end
660
-
661
- # Merges this branch into the currently checked-out branch
662
- #
663
- # @return [String] git's stdout from the merge command
664
- #
665
- # @api private
666
- #
667
- def merge_into_current_branch
668
- Git::Deprecation.warn(
669
- 'Git::Branch#merge with no arguments is deprecated and will be removed in v6.0.0. ' \
670
- 'Use Git::Repository#merge(name) instead.'
671
- )
672
- branch_repository.merge(@name)
673
- end
674
-
675
- # Creates the branch if it does not already exist, ignoring errors
676
- #
677
- # @return [nil]
678
- #
679
- def check_if_create
680
- branch_repository.branch_new(@name)
681
- rescue StandardError
682
- nil
683
- end
684
-
685
- # @return [Git::Repository]
686
- #
687
- # @api private
688
- #
689
- def branch_repository
690
- @base
691
- end
692
- end
693
- end