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.
- checksums.yaml +4 -4
- data/.yardopts +5 -1
- data/AI_POLICY.md +24 -0
- data/CHANGELOG.md +689 -0
- data/CODE_OF_CONDUCT.md +25 -0
- data/CONTRIBUTING.md +1175 -97
- data/GOVERNANCE.md +106 -0
- data/LICENSE +1 -1
- data/MAINTAINERS.md +17 -4
- data/README.md +476 -320
- data/UPGRADING.md +1138 -0
- data/git.gemspec +124 -36
- data/lib/git/author.rb +39 -7
- data/lib/git/author_info.rb +66 -0
- data/lib/git/branch.rb +615 -65
- data/lib/git/branch_delete_failure.rb +34 -0
- data/lib/git/branch_delete_result.rb +66 -0
- data/lib/git/branch_info.rb +237 -0
- data/lib/git/branches.rb +167 -44
- data/lib/git/command_line/base.rb +247 -0
- data/lib/git/command_line/capturing.rb +308 -0
- data/lib/git/command_line/result.rb +88 -0
- data/lib/git/command_line/streaming.rb +236 -0
- data/lib/git/command_line.rb +52 -0
- data/lib/git/commands/add.rb +139 -0
- data/lib/git/commands/am/abort.rb +43 -0
- data/lib/git/commands/am/apply.rb +263 -0
- data/lib/git/commands/am/continue.rb +43 -0
- data/lib/git/commands/am/quit.rb +43 -0
- data/lib/git/commands/am/retry.rb +49 -0
- data/lib/git/commands/am/show_current_patch.rb +64 -0
- data/lib/git/commands/am/skip.rb +42 -0
- data/lib/git/commands/am.rb +33 -0
- data/lib/git/commands/apply.rb +242 -0
- data/lib/git/commands/archive/list_formats.rb +46 -0
- data/lib/git/commands/archive.rb +145 -0
- data/lib/git/commands/arguments.rb +4521 -0
- data/lib/git/commands/base.rb +502 -0
- data/lib/git/commands/branch/copy.rb +102 -0
- data/lib/git/commands/branch/create.rb +177 -0
- data/lib/git/commands/branch/delete.rb +88 -0
- data/lib/git/commands/branch/list.rb +178 -0
- data/lib/git/commands/branch/move.rb +102 -0
- data/lib/git/commands/branch/set_upstream.rb +86 -0
- data/lib/git/commands/branch/show_current.rb +49 -0
- data/lib/git/commands/branch/unset_upstream.rb +53 -0
- data/lib/git/commands/branch.rb +34 -0
- data/lib/git/commands/cat_file/batch.rb +385 -0
- data/lib/git/commands/cat_file/filtered.rb +105 -0
- data/lib/git/commands/cat_file/raw.rb +271 -0
- data/lib/git/commands/cat_file.rb +49 -0
- data/lib/git/commands/checkout/branch.rb +153 -0
- data/lib/git/commands/checkout/files.rb +116 -0
- data/lib/git/commands/checkout.rb +38 -0
- data/lib/git/commands/checkout_index.rb +106 -0
- data/lib/git/commands/clean.rb +102 -0
- data/lib/git/commands/clone.rb +241 -0
- data/lib/git/commands/commit.rb +273 -0
- data/lib/git/commands/commit_tree.rb +101 -0
- data/lib/git/commands/config_option_syntax/add.rb +86 -0
- data/lib/git/commands/config_option_syntax/get.rb +121 -0
- data/lib/git/commands/config_option_syntax/get_all.rb +118 -0
- data/lib/git/commands/config_option_syntax/get_color.rb +95 -0
- data/lib/git/commands/config_option_syntax/get_color_bool.rb +96 -0
- data/lib/git/commands/config_option_syntax/get_regexp.rb +119 -0
- data/lib/git/commands/config_option_syntax/get_urlmatch.rb +111 -0
- data/lib/git/commands/config_option_syntax/list.rb +111 -0
- data/lib/git/commands/config_option_syntax/remove_section.rb +79 -0
- data/lib/git/commands/config_option_syntax/rename_section.rb +83 -0
- data/lib/git/commands/config_option_syntax/replace_all.rb +109 -0
- data/lib/git/commands/config_option_syntax/set.rb +119 -0
- data/lib/git/commands/config_option_syntax/unset.rb +92 -0
- data/lib/git/commands/config_option_syntax/unset_all.rb +94 -0
- data/lib/git/commands/config_option_syntax.rb +56 -0
- data/lib/git/commands/describe.rb +156 -0
- data/lib/git/commands/diff.rb +657 -0
- data/lib/git/commands/diff_files.rb +519 -0
- data/lib/git/commands/diff_index.rb +499 -0
- data/lib/git/commands/fetch.rb +354 -0
- data/lib/git/commands/fsck.rb +138 -0
- data/lib/git/commands/gc.rb +134 -0
- data/lib/git/commands/grep.rb +339 -0
- data/lib/git/commands/init.rb +101 -0
- data/lib/git/commands/log.rb +634 -0
- data/lib/git/commands/ls_files.rb +195 -0
- data/lib/git/commands/ls_remote.rb +161 -0
- data/lib/git/commands/ls_tree.rb +135 -0
- data/lib/git/commands/maintenance/register.rb +77 -0
- data/lib/git/commands/maintenance/run.rb +109 -0
- data/lib/git/commands/maintenance/start.rb +71 -0
- data/lib/git/commands/maintenance/stop.rb +60 -0
- data/lib/git/commands/maintenance/unregister.rb +84 -0
- data/lib/git/commands/maintenance.rb +31 -0
- data/lib/git/commands/merge/abort.rb +44 -0
- data/lib/git/commands/merge/continue.rb +44 -0
- data/lib/git/commands/merge/quit.rb +46 -0
- data/lib/git/commands/merge/start.rb +250 -0
- data/lib/git/commands/merge.rb +28 -0
- data/lib/git/commands/merge_base.rb +91 -0
- data/lib/git/commands/mv.rb +82 -0
- data/lib/git/commands/name_rev.rb +119 -0
- data/lib/git/commands/pull.rb +382 -0
- data/lib/git/commands/push.rb +251 -0
- data/lib/git/commands/read_tree.rb +154 -0
- data/lib/git/commands/remote/add.rb +96 -0
- data/lib/git/commands/remote/get_url.rb +68 -0
- data/lib/git/commands/remote/list.rb +56 -0
- data/lib/git/commands/remote/prune.rb +63 -0
- data/lib/git/commands/remote/remove.rb +52 -0
- data/lib/git/commands/remote/rename.rb +76 -0
- data/lib/git/commands/remote/set_branches.rb +70 -0
- data/lib/git/commands/remote/set_head.rb +89 -0
- data/lib/git/commands/remote/set_url.rb +78 -0
- data/lib/git/commands/remote/set_url_add.rb +70 -0
- data/lib/git/commands/remote/set_url_delete.rb +71 -0
- data/lib/git/commands/remote/show.rb +77 -0
- data/lib/git/commands/remote/update.rb +79 -0
- data/lib/git/commands/remote.rb +42 -0
- data/lib/git/commands/repack.rb +281 -0
- data/lib/git/commands/reset.rb +154 -0
- data/lib/git/commands/rev_parse.rb +304 -0
- data/lib/git/commands/revert/abort.rb +45 -0
- data/lib/git/commands/revert/continue.rb +62 -0
- data/lib/git/commands/revert/quit.rb +47 -0
- data/lib/git/commands/revert/skip.rb +44 -0
- data/lib/git/commands/revert/start.rb +158 -0
- data/lib/git/commands/revert.rb +29 -0
- data/lib/git/commands/rm.rb +113 -0
- data/lib/git/commands/show.rb +632 -0
- data/lib/git/commands/show_ref/exclude_existing.rb +119 -0
- data/lib/git/commands/show_ref/exists.rb +80 -0
- data/lib/git/commands/show_ref/list.rb +149 -0
- data/lib/git/commands/show_ref/verify.rb +122 -0
- data/lib/git/commands/show_ref.rb +42 -0
- data/lib/git/commands/stash/apply.rb +81 -0
- data/lib/git/commands/stash/branch.rb +67 -0
- data/lib/git/commands/stash/clear.rb +43 -0
- data/lib/git/commands/stash/create.rb +60 -0
- data/lib/git/commands/stash/drop.rb +73 -0
- data/lib/git/commands/stash/list.rb +43 -0
- data/lib/git/commands/stash/pop.rb +87 -0
- data/lib/git/commands/stash/push.rb +112 -0
- data/lib/git/commands/stash/show.rb +158 -0
- data/lib/git/commands/stash/store.rb +72 -0
- data/lib/git/commands/stash.rb +38 -0
- data/lib/git/commands/status.rb +174 -0
- data/lib/git/commands/symbolic_ref/delete.rb +72 -0
- data/lib/git/commands/symbolic_ref/read.rb +99 -0
- data/lib/git/commands/symbolic_ref/update.rb +79 -0
- data/lib/git/commands/symbolic_ref.rb +38 -0
- data/lib/git/commands/tag/create.rb +142 -0
- data/lib/git/commands/tag/delete.rb +57 -0
- data/lib/git/commands/tag/list.rb +146 -0
- data/lib/git/commands/tag/verify.rb +71 -0
- data/lib/git/commands/tag.rb +26 -0
- data/lib/git/commands/update_ref/batch.rb +145 -0
- data/lib/git/commands/update_ref/delete.rb +90 -0
- data/lib/git/commands/update_ref/update.rb +103 -0
- data/lib/git/commands/update_ref.rb +42 -0
- data/lib/git/commands/version.rb +60 -0
- data/lib/git/commands/worktree/add.rb +139 -0
- data/lib/git/commands/worktree/list.rb +64 -0
- data/lib/git/commands/worktree/lock.rb +58 -0
- data/lib/git/commands/worktree/management_base.rb +51 -0
- data/lib/git/commands/worktree/move.rb +66 -0
- data/lib/git/commands/worktree/prune.rb +67 -0
- data/lib/git/commands/worktree/remove.rb +63 -0
- data/lib/git/commands/worktree/repair.rb +76 -0
- data/lib/git/commands/worktree/unlock.rb +47 -0
- data/lib/git/commands/worktree.rb +43 -0
- data/lib/git/commands/write_tree.rb +68 -0
- data/lib/git/commands.rb +88 -0
- data/lib/git/config.rb +72 -5
- data/lib/git/config_entry_info.rb +106 -0
- data/lib/git/configuring.rb +795 -0
- data/lib/git/detached_head_info.rb +57 -0
- data/lib/git/diff.rb +437 -86
- data/lib/git/diff_file_numstat_info.rb +31 -0
- data/lib/git/diff_file_patch_info.rb +136 -0
- data/lib/git/diff_file_raw_info.rb +129 -0
- data/lib/git/diff_info.rb +162 -0
- data/lib/git/diff_path_status.rb +107 -0
- data/lib/git/diff_result.rb +34 -0
- data/lib/git/diff_stats.rb +111 -0
- data/lib/git/dirstat_info.rb +102 -0
- data/lib/git/encoding_utils.rb +32 -1
- data/lib/git/errors.rb +285 -0
- data/lib/git/escaped_path.rb +57 -5
- data/lib/git/execution_context/global.rb +31 -0
- data/lib/git/execution_context/repository.rb +151 -0
- data/lib/git/execution_context.rb +559 -0
- data/lib/git/factories.rb +813 -0
- data/lib/git/file_ref.rb +77 -0
- data/lib/git/fsck_object.rb +56 -0
- data/lib/git/fsck_result.rb +132 -0
- data/lib/git/log.rb +306 -90
- data/lib/git/object.rb +563 -141
- data/lib/git/parsers/branch.rb +240 -0
- data/lib/git/parsers/cat_file.rb +111 -0
- data/lib/git/parsers/config_entry.rb +110 -0
- data/lib/git/parsers/diff.rb +792 -0
- data/lib/git/parsers/fsck.rb +144 -0
- data/lib/git/parsers/grep.rb +42 -0
- data/lib/git/parsers/ls_remote.rb +79 -0
- data/lib/git/parsers/ls_tree.rb +58 -0
- data/lib/git/parsers/remote.rb +162 -0
- data/lib/git/parsers/stash.rb +292 -0
- data/lib/git/parsers/status.rb +251 -0
- data/lib/git/parsers/tag.rb +341 -0
- data/lib/git/parsers/worktree.rb +185 -0
- data/lib/git/path_resolver.rb +206 -0
- data/lib/git/remote.rb +165 -12
- data/lib/git/remote_info.rb +203 -0
- data/lib/git/repository/branching.rb +964 -0
- data/lib/git/repository/committing.rb +246 -0
- data/lib/git/repository/context_helpers.rb +293 -0
- data/lib/git/repository/diffing.rb +785 -0
- data/lib/git/repository/inspecting.rb +252 -0
- data/lib/git/repository/logging.rb +410 -0
- data/lib/git/repository/maintenance.rb +65 -0
- data/lib/git/repository/merging.rb +451 -0
- data/lib/git/repository/object_operations.rb +1551 -0
- data/lib/git/repository/remote_operations.rb +984 -0
- data/lib/git/repository/shared_private.rb +120 -0
- data/lib/git/repository/staging.rb +587 -0
- data/lib/git/repository/stashing.rb +623 -0
- data/lib/git/repository/status_operations.rb +249 -0
- data/lib/git/repository/worktree_operations.rb +339 -0
- data/lib/git/repository.rb +484 -2
- data/lib/git/stash.rb +109 -12
- data/lib/git/stash_info.rb +102 -0
- data/lib/git/stashes.rb +169 -26
- data/lib/git/status.rb +308 -122
- data/lib/git/status_file_info.rb +258 -0
- data/lib/git/status_info.rb +189 -0
- data/lib/git/tag_delete_failure.rb +34 -0
- data/lib/git/tag_delete_result.rb +66 -0
- data/lib/git/tag_info.rb +99 -0
- data/lib/git/url.rb +15 -8
- data/lib/git/version.rb +113 -2
- data/lib/git/version_constraint.rb +85 -0
- data/lib/git/worktree.rb +150 -8
- data/lib/git/worktree_info.rb +128 -0
- data/lib/git/worktrees.rb +118 -13
- data/lib/git.rb +632 -234
- metadata +369 -54
- data/.github/stale.yml +0 -25
- data/.github/workflows/continuous_integration.yml +0 -49
- data/.gitignore +0 -10
- data/Dockerfile.changelog-rs +0 -12
- data/Gemfile +0 -5
- data/ISSUE_TEMPLATE.md +0 -15
- data/PULL_REQUEST_TEMPLATE.md +0 -9
- data/RELEASING.md +0 -70
- data/Rakefile +0 -60
- data/lib/git/base/factory.rb +0 -99
- data/lib/git/base.rb +0 -711
- data/lib/git/command_line_result.rb +0 -86
- data/lib/git/failed_error.rb +0 -53
- data/lib/git/git_execute_error.rb +0 -7
- data/lib/git/index.rb +0 -5
- data/lib/git/lib.rb +0 -1328
- data/lib/git/path.rb +0 -31
- data/lib/git/signaled_error.rb +0 -50
- data/lib/git/working_directory.rb +0 -4
|
@@ -0,0 +1,964 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pathname'
|
|
4
|
+
require 'git/branch'
|
|
5
|
+
require 'git/branch_info'
|
|
6
|
+
require 'git/branches'
|
|
7
|
+
require 'git/commands/branch/create'
|
|
8
|
+
require 'git/commands/branch/delete'
|
|
9
|
+
require 'git/commands/branch/list'
|
|
10
|
+
require 'git/commands/branch/show_current'
|
|
11
|
+
require 'git/commands/checkout/branch'
|
|
12
|
+
require 'git/commands/checkout/files'
|
|
13
|
+
require 'git/commands/checkout_index'
|
|
14
|
+
require 'git/commands/rev_parse'
|
|
15
|
+
require 'git/commands/update_ref/update'
|
|
16
|
+
require 'git/commands/symbolic_ref/update'
|
|
17
|
+
require 'git/parsers/branch'
|
|
18
|
+
require 'git/repository/shared_private'
|
|
19
|
+
|
|
20
|
+
module Git
|
|
21
|
+
class Repository
|
|
22
|
+
# Facade methods for branching operations: creating, checking out, querying,
|
|
23
|
+
# deleting, and updating branches
|
|
24
|
+
#
|
|
25
|
+
# Included by {Git::Repository}.
|
|
26
|
+
#
|
|
27
|
+
# @api private
|
|
28
|
+
#
|
|
29
|
+
module Branching # rubocop:disable Metrics/ModuleLength
|
|
30
|
+
# Represents the state of HEAD in a repository
|
|
31
|
+
#
|
|
32
|
+
# @api private
|
|
33
|
+
#
|
|
34
|
+
# @!attribute [r] state
|
|
35
|
+
# @return [Symbol] one of `:active`, `:unborn`, or `:detached`
|
|
36
|
+
#
|
|
37
|
+
# @!attribute [r] name
|
|
38
|
+
# @return [String] the branch name, or `'HEAD'` when detached
|
|
39
|
+
#
|
|
40
|
+
HeadState = Data.define(:state, :name)
|
|
41
|
+
|
|
42
|
+
# Option keys accepted by {#checkout}
|
|
43
|
+
#
|
|
44
|
+
CHECKOUT_ALLOWED_OPTS = %i[force f new_branch b start_point orphan].freeze
|
|
45
|
+
private_constant :CHECKOUT_ALLOWED_OPTS
|
|
46
|
+
|
|
47
|
+
# Option keys accepted by {#checkout_index}
|
|
48
|
+
#
|
|
49
|
+
CHECKOUT_INDEX_ALLOWED_OPTS = %i[prefix force all path_limiter].freeze
|
|
50
|
+
private_constant :CHECKOUT_INDEX_ALLOWED_OPTS
|
|
51
|
+
|
|
52
|
+
# Returns the name of the current branch
|
|
53
|
+
#
|
|
54
|
+
# @example Get the current branch name
|
|
55
|
+
# repo.current_branch # => "main"
|
|
56
|
+
#
|
|
57
|
+
# @example In detached HEAD state
|
|
58
|
+
# repo.current_branch # => "HEAD"
|
|
59
|
+
#
|
|
60
|
+
# @return [String] the current branch name, or `'HEAD'` when in detached
|
|
61
|
+
# HEAD state
|
|
62
|
+
#
|
|
63
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
64
|
+
#
|
|
65
|
+
def current_branch
|
|
66
|
+
result = Git::Commands::Branch::ShowCurrent.new(@execution_context).call
|
|
67
|
+
name = result.stdout.strip
|
|
68
|
+
name.empty? ? 'HEAD' : name
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Returns the current HEAD state as a structured value object
|
|
72
|
+
#
|
|
73
|
+
# HEAD can be in one of three states:
|
|
74
|
+
#
|
|
75
|
+
# - **`:active`** — HEAD points to a branch ref that has at least one commit.
|
|
76
|
+
# - **`:unborn`** — HEAD points to a branch ref that has been created but has
|
|
77
|
+
# no commits yet (e.g. immediately after `git init` before any commit).
|
|
78
|
+
# - **`:detached`** — HEAD points directly to a commit SHA rather than a branch.
|
|
79
|
+
#
|
|
80
|
+
# @example Active branch
|
|
81
|
+
# repo.current_branch_state
|
|
82
|
+
# # => #<data Git::Repository::Branching::HeadState state=:active, name="main">
|
|
83
|
+
#
|
|
84
|
+
# @example Unborn branch (no commits yet)
|
|
85
|
+
# repo.current_branch_state
|
|
86
|
+
# # => #<data Git::Repository::Branching::HeadState state=:unborn, name="main">
|
|
87
|
+
#
|
|
88
|
+
# @example Detached HEAD
|
|
89
|
+
# repo.current_branch_state
|
|
90
|
+
# # => #<data Git::Repository::Branching::HeadState state=:detached, name="HEAD">
|
|
91
|
+
#
|
|
92
|
+
# @return [Git::Repository::Branching::HeadState] the current HEAD state
|
|
93
|
+
#
|
|
94
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
95
|
+
#
|
|
96
|
+
def current_branch_state
|
|
97
|
+
branch_name = Git::Commands::Branch::ShowCurrent.new(@execution_context).call.stdout.strip
|
|
98
|
+
return HeadState.new(state: :detached, name: 'HEAD') if branch_name.empty?
|
|
99
|
+
|
|
100
|
+
state = Private.get_branch_state(@execution_context, branch_name)
|
|
101
|
+
HeadState.new(state: state, name: branch_name)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Restore working tree files from a tree-ish
|
|
105
|
+
#
|
|
106
|
+
# @example Restore README.md to its HEAD state
|
|
107
|
+
# repo.checkout_file('HEAD', 'README.md')
|
|
108
|
+
#
|
|
109
|
+
# @param version [String] the tree-ish (branch, tag, commit SHA, etc.) to
|
|
110
|
+
# restore the file from
|
|
111
|
+
#
|
|
112
|
+
# @param file [String] the path to the file to restore
|
|
113
|
+
#
|
|
114
|
+
# @return [String] git's stdout from the checkout
|
|
115
|
+
#
|
|
116
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
117
|
+
#
|
|
118
|
+
def checkout_file(version, file)
|
|
119
|
+
Git::Commands::Checkout::Files.new(@execution_context).call(version, pathspec: [file]).stdout
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Switch branches or restore working tree files
|
|
123
|
+
#
|
|
124
|
+
# @example Check out an existing branch
|
|
125
|
+
# repo.checkout('main')
|
|
126
|
+
#
|
|
127
|
+
# @example Create and check out a new branch from main
|
|
128
|
+
# repo.checkout('new-feature', new_branch: true, start_point: 'main')
|
|
129
|
+
#
|
|
130
|
+
# @example Create a new branch with a name different from the start point
|
|
131
|
+
# repo.checkout('main', new_branch: 'new-feature')
|
|
132
|
+
#
|
|
133
|
+
# @example Create and check out an unborn branch with no history
|
|
134
|
+
# repo.checkout('gh-pages', orphan: true)
|
|
135
|
+
#
|
|
136
|
+
# @example Force checkout discarding local changes
|
|
137
|
+
# repo.checkout('main', force: true)
|
|
138
|
+
#
|
|
139
|
+
# @param branch [String, nil] the branch to check out; defaults to nil
|
|
140
|
+
# (i.e. restore HEAD state)
|
|
141
|
+
#
|
|
142
|
+
# @param opts [Hash] options for the checkout command
|
|
143
|
+
#
|
|
144
|
+
# @option opts [Boolean, nil] :force (nil) discard local changes when
|
|
145
|
+
# switching branches
|
|
146
|
+
#
|
|
147
|
+
# @option opts [Boolean, String, nil] :new_branch (nil) when `true`,
|
|
148
|
+
# creates a new branch named `branch` from `:start_point`
|
|
149
|
+
#
|
|
150
|
+
# When a `String`, creates a new branch with that name, using `branch`
|
|
151
|
+
# as the start point.
|
|
152
|
+
#
|
|
153
|
+
# @option opts [Boolean, String, nil] :b (nil) alias for `:new_branch`
|
|
154
|
+
#
|
|
155
|
+
# @option opts [Boolean, nil] :f (nil) alias for `:force`
|
|
156
|
+
#
|
|
157
|
+
# @option opts [Boolean, String, nil] :orphan (nil) when `true`, creates a
|
|
158
|
+
# new unborn branch named `branch` whose first commit has no parents
|
|
159
|
+
#
|
|
160
|
+
# When a `String`, creates an unborn branch with that name, using
|
|
161
|
+
# `branch` as the start point for the working tree and index.
|
|
162
|
+
#
|
|
163
|
+
# `false` and `nil` are both treated as unset. A blank branch name is
|
|
164
|
+
# rejected rather than ignored.
|
|
165
|
+
#
|
|
166
|
+
# @option opts [String, nil] :start_point (nil) the commit or branch to
|
|
167
|
+
# start the new branch from; used together with `new_branch: true` or
|
|
168
|
+
# `orphan: true`
|
|
169
|
+
#
|
|
170
|
+
# @return [String] git's stdout from the checkout
|
|
171
|
+
#
|
|
172
|
+
# @raise [ArgumentError] if unsupported options are provided
|
|
173
|
+
#
|
|
174
|
+
# @raise [ArgumentError] if `:orphan` is given a blank or missing branch name
|
|
175
|
+
#
|
|
176
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
177
|
+
#
|
|
178
|
+
def checkout(branch = nil, opts = {})
|
|
179
|
+
if branch.is_a?(Hash) && opts.empty?
|
|
180
|
+
opts = branch
|
|
181
|
+
branch = nil
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
SharedPrivate.assert_valid_opts!(CHECKOUT_ALLOWED_OPTS, **opts)
|
|
185
|
+
|
|
186
|
+
target, translated_opts = Private.translate_checkout_opts(branch, opts)
|
|
187
|
+
Git::Commands::Checkout::Branch.new(@execution_context).call(target, **translated_opts).stdout
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Run a block with the given branch checked out, then restore the original branch
|
|
191
|
+
#
|
|
192
|
+
# Records the current branch (or the current commit when HEAD is detached),
|
|
193
|
+
# checks out `branch`, and yields to the block. If the block returns a truthy
|
|
194
|
+
# value, all pending changes are committed with `message` (see
|
|
195
|
+
# {#commit_all}); if it returns a falsy value, the index and working tree are
|
|
196
|
+
# hard-reset instead (see {#reset}). The original branch or commit is then
|
|
197
|
+
# checked out again. The hard reset discards changes to tracked files only;
|
|
198
|
+
# untracked files created by the block are left in place.
|
|
199
|
+
#
|
|
200
|
+
# Unlike `Git::Branch#in_branch`, this method does not create `branch`. The
|
|
201
|
+
# branch must be an existing local branch. Unlike {#checkout}, a commit SHA,
|
|
202
|
+
# tag, or remote-tracking branch is rejected before any checkout happens:
|
|
203
|
+
# those detach HEAD, and a commit made there would be left dangling once the
|
|
204
|
+
# original branch is restored. HEAD must
|
|
205
|
+
# be on a branch with at least one commit, or detached: an unborn branch (no
|
|
206
|
+
# commits yet) cannot be checked out again by name, so it is rejected before
|
|
207
|
+
# any checkout happens.
|
|
208
|
+
#
|
|
209
|
+
# **Note:** the restore checkout is not wrapped in `ensure`. If the block,
|
|
210
|
+
# the commit, or the reset raises an exception, the repository is left
|
|
211
|
+
# checked out on `branch` rather than restored to the original branch.
|
|
212
|
+
#
|
|
213
|
+
# @example Commit a new file on a feature branch
|
|
214
|
+
# repo.in_branch('feature', 'Add README') do
|
|
215
|
+
# File.write('README.md', '# Hello')
|
|
216
|
+
# repo.add('README.md')
|
|
217
|
+
# true # commit and return to the original branch
|
|
218
|
+
# end
|
|
219
|
+
#
|
|
220
|
+
# @example Discard experimental changes to a tracked file
|
|
221
|
+
# repo.in_branch('scratch') do
|
|
222
|
+
# File.write('README.md', '# Try something')
|
|
223
|
+
# false # hard-reset and return to the original branch
|
|
224
|
+
# end
|
|
225
|
+
#
|
|
226
|
+
# @param branch [String] the name of an existing local branch to check out
|
|
227
|
+
#
|
|
228
|
+
# @param message [String] the commit message used when the block returns a
|
|
229
|
+
# truthy value
|
|
230
|
+
#
|
|
231
|
+
# @return [String] git's stdout from the final checkout back to the original
|
|
232
|
+
# branch or commit
|
|
233
|
+
#
|
|
234
|
+
# @raise [ArgumentError] if `branch` is not an existing local branch
|
|
235
|
+
#
|
|
236
|
+
# @raise [Git::Error] if HEAD is on an unborn branch
|
|
237
|
+
#
|
|
238
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
239
|
+
#
|
|
240
|
+
# @yield executes the block with `branch` checked out
|
|
241
|
+
#
|
|
242
|
+
# @yieldreturn [Object] a truthy value to commit all changes, a falsy value to
|
|
243
|
+
# hard-reset
|
|
244
|
+
#
|
|
245
|
+
def in_branch(branch, message = 'in branch work')
|
|
246
|
+
SharedPrivate.assert_local_branch!(self, branch)
|
|
247
|
+
restore_point = SharedPrivate.head_restore_point(self)
|
|
248
|
+
checkout(branch)
|
|
249
|
+
if yield
|
|
250
|
+
commit_all(message)
|
|
251
|
+
else
|
|
252
|
+
reset(nil, hard: true)
|
|
253
|
+
end
|
|
254
|
+
checkout(restore_point)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Populate the working tree from the index
|
|
258
|
+
#
|
|
259
|
+
# @example Check out all files from the index
|
|
260
|
+
# repo.checkout_index(all: true)
|
|
261
|
+
#
|
|
262
|
+
# @example Force check out a specific file
|
|
263
|
+
# repo.checkout_index(force: true, path_limiter: 'README.md')
|
|
264
|
+
#
|
|
265
|
+
# @example Check out files to a staging prefix
|
|
266
|
+
# repo.checkout_index(prefix: 'tmp/stage/', all: true)
|
|
267
|
+
#
|
|
268
|
+
# @param options [Hash] options for the checkout-index command
|
|
269
|
+
#
|
|
270
|
+
# @option options [Boolean, nil] :all (nil) check out all files in the index
|
|
271
|
+
#
|
|
272
|
+
# @option options [Boolean, nil] :force (nil) overwrite existing files
|
|
273
|
+
#
|
|
274
|
+
# @option options [String, nil] :prefix (nil) write files under this path prefix
|
|
275
|
+
# rather than the working directory root
|
|
276
|
+
#
|
|
277
|
+
# @option options [String, Pathname, Array<String, Pathname>, nil] :path_limiter (nil)
|
|
278
|
+
# limit the check out to the given path(s)
|
|
279
|
+
#
|
|
280
|
+
# @return [String] git's stdout from the checkout-index command
|
|
281
|
+
#
|
|
282
|
+
# @raise [ArgumentError] if unsupported options are provided
|
|
283
|
+
#
|
|
284
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
285
|
+
#
|
|
286
|
+
def checkout_index(options = {})
|
|
287
|
+
SharedPrivate.assert_valid_opts!(CHECKOUT_INDEX_ALLOWED_OPTS, **options)
|
|
288
|
+
|
|
289
|
+
paths = Private.normalize_pathspecs(options[:path_limiter], 'path_limiter')
|
|
290
|
+
keyword_opts = options.except(:path_limiter)
|
|
291
|
+
Git::Commands::CheckoutIndex.new(@execution_context).call(*paths.to_a, **keyword_opts).stdout
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Returns `true` if the named branch exists as a local branch
|
|
295
|
+
#
|
|
296
|
+
# @example Check whether main exists locally
|
|
297
|
+
# repo.local_branch?('main') # => true
|
|
298
|
+
#
|
|
299
|
+
# @param branch [String] the local branch name to look up
|
|
300
|
+
#
|
|
301
|
+
# @return [Boolean] `true` if the branch exists locally, `false` otherwise
|
|
302
|
+
#
|
|
303
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
304
|
+
#
|
|
305
|
+
def local_branch?(branch)
|
|
306
|
+
result = Git::Commands::Branch::List.new(@execution_context).call(branch, format: '%(refname:short)')
|
|
307
|
+
result.stdout.chomp == branch
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Returns `true` if the named branch exists as a remote-tracking branch
|
|
311
|
+
#
|
|
312
|
+
# The `branch` argument must be the **short branch name** (e.g. `'master'`),
|
|
313
|
+
# not the combined `remote/branch` form (e.g. `'origin/master'`).
|
|
314
|
+
#
|
|
315
|
+
# @example Check whether master exists on any remote
|
|
316
|
+
# repo.remote_branch?('master') # => true
|
|
317
|
+
#
|
|
318
|
+
# @param branch [String] the short branch name to look up across all remotes
|
|
319
|
+
#
|
|
320
|
+
# @return [Boolean] `true` if a remote-tracking branch with that short name
|
|
321
|
+
# exists, `false` otherwise
|
|
322
|
+
#
|
|
323
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
324
|
+
#
|
|
325
|
+
def remote_branch?(branch)
|
|
326
|
+
result = Git::Commands::Branch::List.new(@execution_context)
|
|
327
|
+
.call("*/#{branch}", remotes: true, format: '%(refname:lstrip=3)')
|
|
328
|
+
result.stdout.each_line.any? { |line| line.chomp == branch }
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Returns `true` if the named branch exists locally or as a remote-tracking branch
|
|
332
|
+
#
|
|
333
|
+
# @example Check whether main exists anywhere
|
|
334
|
+
# repo.branch?('main') # => true
|
|
335
|
+
#
|
|
336
|
+
# @param branch [String] the branch name to look up
|
|
337
|
+
#
|
|
338
|
+
# @return [Boolean] `true` if the branch exists locally or remotely,
|
|
339
|
+
# `false` otherwise
|
|
340
|
+
#
|
|
341
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
342
|
+
#
|
|
343
|
+
def branch?(branch)
|
|
344
|
+
local_branch?(branch) || remote_branch?(branch)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Checks whether the named branch exists locally
|
|
348
|
+
#
|
|
349
|
+
# @example Check whether main exists locally
|
|
350
|
+
# repo.is_local_branch?('main') # => true
|
|
351
|
+
#
|
|
352
|
+
# @param branch [String] the local branch name to look up
|
|
353
|
+
#
|
|
354
|
+
# @return [Boolean] `true` if the branch exists locally, `false` otherwise
|
|
355
|
+
#
|
|
356
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
357
|
+
#
|
|
358
|
+
# @deprecated use {#local_branch?} instead
|
|
359
|
+
#
|
|
360
|
+
def is_local_branch?(branch) # rubocop:disable Naming/PredicatePrefix
|
|
361
|
+
Git::Deprecation.warn(
|
|
362
|
+
'Git::Repository#is_local_branch? is deprecated and will be removed in v6.0.0. ' \
|
|
363
|
+
'Use Git::Repository#local_branch? instead.'
|
|
364
|
+
)
|
|
365
|
+
local_branch?(branch)
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# Checks whether the named branch exists as a remote-tracking branch
|
|
369
|
+
#
|
|
370
|
+
# @example Check whether master exists on any remote
|
|
371
|
+
# repo.is_remote_branch?('master') # => true
|
|
372
|
+
#
|
|
373
|
+
# @param branch [String] the short branch name to look up across all remotes
|
|
374
|
+
#
|
|
375
|
+
# @return [Boolean] `true` if a remote-tracking branch with that short name
|
|
376
|
+
# exists, `false` otherwise
|
|
377
|
+
#
|
|
378
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
379
|
+
#
|
|
380
|
+
# @deprecated use {#remote_branch?} instead
|
|
381
|
+
#
|
|
382
|
+
def is_remote_branch?(branch) # rubocop:disable Naming/PredicatePrefix
|
|
383
|
+
Git::Deprecation.warn(
|
|
384
|
+
'Git::Repository#is_remote_branch? is deprecated and will be removed in v6.0.0. ' \
|
|
385
|
+
'Use Git::Repository#remote_branch? instead.'
|
|
386
|
+
)
|
|
387
|
+
remote_branch?(branch)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# Checks whether the named branch exists locally or as a remote-tracking branch
|
|
391
|
+
#
|
|
392
|
+
# @example Check whether main exists anywhere
|
|
393
|
+
# repo.is_branch?('main') # => true
|
|
394
|
+
#
|
|
395
|
+
# @param branch [String] the branch name to look up
|
|
396
|
+
#
|
|
397
|
+
# @return [Boolean] `true` if the branch exists locally or remotely,
|
|
398
|
+
# `false` otherwise
|
|
399
|
+
#
|
|
400
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
401
|
+
#
|
|
402
|
+
# @deprecated use {#branch?} instead
|
|
403
|
+
#
|
|
404
|
+
def is_branch?(branch) # rubocop:disable Naming/PredicatePrefix
|
|
405
|
+
Git::Deprecation.warn(
|
|
406
|
+
'Git::Repository#is_branch? is deprecated and will be removed in v6.0.0. ' \
|
|
407
|
+
'Use Git::Repository#branch? instead.'
|
|
408
|
+
)
|
|
409
|
+
branch?(branch)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# Option keys accepted by {#branch_new}
|
|
413
|
+
#
|
|
414
|
+
BRANCH_NEW_ALLOWED_OPTS = %i[].freeze
|
|
415
|
+
private_constant :BRANCH_NEW_ALLOWED_OPTS
|
|
416
|
+
|
|
417
|
+
# Create a new branch
|
|
418
|
+
#
|
|
419
|
+
# @example Create a new branch from the current HEAD
|
|
420
|
+
# repo.branch_new('feature')
|
|
421
|
+
#
|
|
422
|
+
# @example Create a new branch from a specific commit or branch
|
|
423
|
+
# repo.branch_new('feature', 'main')
|
|
424
|
+
#
|
|
425
|
+
# @param branch [String] the name of the branch to create
|
|
426
|
+
#
|
|
427
|
+
# @param start_point [String, nil] the commit, branch, or tag to start the
|
|
428
|
+
# new branch from; defaults to the current HEAD when `nil`
|
|
429
|
+
#
|
|
430
|
+
# @param branch_options [Hash] reserved; must be empty — no options are currently
|
|
431
|
+
# supported
|
|
432
|
+
#
|
|
433
|
+
# @return [void]
|
|
434
|
+
#
|
|
435
|
+
# @raise [ArgumentError] if unsupported options are provided
|
|
436
|
+
#
|
|
437
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
438
|
+
#
|
|
439
|
+
def branch_new(branch, start_point = nil, branch_options = {})
|
|
440
|
+
if start_point.is_a?(Hash) && branch_options.empty?
|
|
441
|
+
branch_options = start_point
|
|
442
|
+
start_point = nil
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
SharedPrivate.assert_valid_opts!(BRANCH_NEW_ALLOWED_OPTS, **branch_options)
|
|
446
|
+
Git::Commands::Branch::Create.new(@execution_context).call(branch, start_point, **branch_options)
|
|
447
|
+
|
|
448
|
+
nil
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# Option keys accepted by {#branch_delete}
|
|
452
|
+
#
|
|
453
|
+
BRANCH_DELETE_ALLOWED_OPTS = %i[force remotes].freeze
|
|
454
|
+
private_constant :BRANCH_DELETE_ALLOWED_OPTS
|
|
455
|
+
|
|
456
|
+
# Delete one or more local or remote-tracking branches
|
|
457
|
+
#
|
|
458
|
+
# @example Delete a single branch
|
|
459
|
+
# repo.branch_delete('feature') # => "Deleted branch feature (was abc1234)."
|
|
460
|
+
#
|
|
461
|
+
# @example Delete multiple branches at once
|
|
462
|
+
# repo.branch_delete('feature-1', 'feature-2')
|
|
463
|
+
#
|
|
464
|
+
# @example Force-delete an unmerged branch
|
|
465
|
+
# repo.branch_delete('unmerged-branch', force: true)
|
|
466
|
+
#
|
|
467
|
+
# @example Delete a remote-tracking branch
|
|
468
|
+
# repo.branch_delete('origin/feature', remotes: true)
|
|
469
|
+
#
|
|
470
|
+
# @param branches [Array<String>] the name(s) of the branch(es) to delete
|
|
471
|
+
#
|
|
472
|
+
# @param options [Hash] options for the delete command
|
|
473
|
+
#
|
|
474
|
+
# @option options [Boolean, nil] :force (true) allow deleting the branch
|
|
475
|
+
# irrespective of its merged status
|
|
476
|
+
#
|
|
477
|
+
# Defaults to `true` to match the 4.x behavior.
|
|
478
|
+
#
|
|
479
|
+
# @option options [Boolean, nil] :remotes (nil) delete remote-tracking
|
|
480
|
+
# branches
|
|
481
|
+
#
|
|
482
|
+
# Use together with a `remote/branch` name.
|
|
483
|
+
#
|
|
484
|
+
# @return [String] the stdout output from the delete command, e.g.
|
|
485
|
+
# `"Deleted branch feature (was abc1234)."`
|
|
486
|
+
#
|
|
487
|
+
# @raise [ArgumentError] if unsupported options are provided
|
|
488
|
+
#
|
|
489
|
+
# @raise [Git::FailedError] if git exits outside the allowed range (exit code > 1)
|
|
490
|
+
#
|
|
491
|
+
# @raise [Git::Error] if git reports a deletion failure
|
|
492
|
+
#
|
|
493
|
+
def branch_delete(*branches, **options)
|
|
494
|
+
options = { force: true }.merge(options)
|
|
495
|
+
SharedPrivate.assert_valid_opts!(BRANCH_DELETE_ALLOWED_OPTS, **options)
|
|
496
|
+
|
|
497
|
+
result = Git::Commands::Branch::Delete.new(@execution_context).call(*branches, **options)
|
|
498
|
+
|
|
499
|
+
raise Git::Error, result.stderr.strip unless result.status.success?
|
|
500
|
+
|
|
501
|
+
result.stdout.strip
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
# Writes the HEAD symbolic ref to point at the given branch
|
|
505
|
+
#
|
|
506
|
+
# Sets `HEAD` to `refs/heads/<branch_name>` via `git symbolic-ref`. This is
|
|
507
|
+
# equivalent to running `git symbolic-ref HEAD refs/heads/<branch_name>` on
|
|
508
|
+
# the command line and is the mechanism git uses internally for branch
|
|
509
|
+
# renaming and orphan-branch checkout.
|
|
510
|
+
#
|
|
511
|
+
# @example Change HEAD to point to an existing branch
|
|
512
|
+
# repo.change_head_branch('main')
|
|
513
|
+
#
|
|
514
|
+
# @example Initialize a repository with a custom default branch name (unborn-branch pattern)
|
|
515
|
+
# repo = Git.init('/path/to/repo')
|
|
516
|
+
# repo.change_head_branch('my-branch')
|
|
517
|
+
# # HEAD now points at refs/heads/my-branch before any commits exist
|
|
518
|
+
#
|
|
519
|
+
# @param branch_name [String] the branch name to point HEAD at
|
|
520
|
+
#
|
|
521
|
+
# @return [void]
|
|
522
|
+
#
|
|
523
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
524
|
+
#
|
|
525
|
+
# @note Pointing HEAD at a branch that does not yet exist places the
|
|
526
|
+
# repository in unborn-branch state. This is intentional for repository
|
|
527
|
+
# initialization workflows — for example, setting a custom default branch
|
|
528
|
+
# name before any commits land — but is unexpected if done by mistake.
|
|
529
|
+
# The repository will appear to have no commits until the first commit is
|
|
530
|
+
# made on the new branch.
|
|
531
|
+
#
|
|
532
|
+
def change_head_branch(branch_name)
|
|
533
|
+
Git::Commands::SymbolicRef::Update.new(@execution_context).call('HEAD', "refs/heads/#{branch_name}")
|
|
534
|
+
nil
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
# Returns the `git branch --list --contains` stdout for a given commit
|
|
538
|
+
#
|
|
539
|
+
# The output format is the human-readable `git branch` listing: each
|
|
540
|
+
# matching branch name appears on its own line, prefixed with two spaces,
|
|
541
|
+
# or `* ` if it is the currently checked-out branch. This is the same
|
|
542
|
+
# format returned by `Git::Lib#branch_contains` in the 4.x gem series.
|
|
543
|
+
#
|
|
544
|
+
# @example List all branches that contain a commit
|
|
545
|
+
# repo.branch_contains('abc1234')
|
|
546
|
+
# # => " main\n"
|
|
547
|
+
#
|
|
548
|
+
# @example The current branch is marked with an asterisk
|
|
549
|
+
# repo.branch_contains('abc1234')
|
|
550
|
+
# # => "* main\n feature\n"
|
|
551
|
+
#
|
|
552
|
+
# @example Limit the search to branches matching a shell wildcard pattern
|
|
553
|
+
# repo.branch_contains('abc1234', 'feature/*')
|
|
554
|
+
#
|
|
555
|
+
# @example Typical usage: check whether any branch contains the commit
|
|
556
|
+
# repo.branch_contains('abc1234').empty? # => false
|
|
557
|
+
#
|
|
558
|
+
# @param commit [String] the commit SHA or ref to look up
|
|
559
|
+
#
|
|
560
|
+
# @param branch_name [String, nil] a shell wildcard pattern to limit which
|
|
561
|
+
# branches are searched
|
|
562
|
+
#
|
|
563
|
+
# When empty or `nil`, all local branches are searched.
|
|
564
|
+
#
|
|
565
|
+
# @return [String] the `git branch --list --contains` stdout
|
|
566
|
+
#
|
|
567
|
+
# Each matching branch appears on its own line, prefixed with two
|
|
568
|
+
# spaces, or `* ` for the currently checked-out branch. Returns an
|
|
569
|
+
# empty string when no matching branch contains the commit.
|
|
570
|
+
#
|
|
571
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
572
|
+
#
|
|
573
|
+
def branch_contains(commit, branch_name = '')
|
|
574
|
+
branch_name = branch_name.to_s
|
|
575
|
+
pattern = branch_name.empty? ? nil : branch_name
|
|
576
|
+
Git::Commands::Branch::List.new(@execution_context)
|
|
577
|
+
.call(*[pattern].compact, contains: commit, no_color: true)
|
|
578
|
+
.stdout
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
# Returns all local and remote-tracking branches as structured objects
|
|
582
|
+
#
|
|
583
|
+
# @example List all branches
|
|
584
|
+
# repo.branch_list
|
|
585
|
+
# # => [#<data Git::BranchInfo refname="refs/heads/main", current=true, ...>,
|
|
586
|
+
# # #<data Git::BranchInfo refname="refs/remotes/origin/main", current=false, ...>]
|
|
587
|
+
#
|
|
588
|
+
# @example Find the currently checked-out branch
|
|
589
|
+
# repo.branch_list.find(&:current)
|
|
590
|
+
#
|
|
591
|
+
# @example List only local branches
|
|
592
|
+
# repo.branch_list.reject(&:remote?)
|
|
593
|
+
#
|
|
594
|
+
# @example Filter to an exact branch name
|
|
595
|
+
# repo.branch_list('feature/auth')
|
|
596
|
+
#
|
|
597
|
+
# @example Filter using glob patterns
|
|
598
|
+
# repo.branch_list('feature/*', 'hotfix/*')
|
|
599
|
+
#
|
|
600
|
+
# @param patterns [Array<String>] optional shell wildcard patterns passed
|
|
601
|
+
# directly to `git branch --list`; when empty (the default) all branches
|
|
602
|
+
# are returned. Pattern matching follows git's own rules; behavior may
|
|
603
|
+
# differ between local and remote-tracking branches.
|
|
604
|
+
#
|
|
605
|
+
# @param remote_names [Array<String>, nil] configured remote names used to
|
|
606
|
+
# resolve remote-tracking refs
|
|
607
|
+
#
|
|
608
|
+
# Especially useful for remotes whose remote names contain slashes. When
|
|
609
|
+
# omitted, the repository's configured remote names are fetched automatically.
|
|
610
|
+
#
|
|
611
|
+
# @return [Array<Git::BranchInfo>] parsed branch information for every
|
|
612
|
+
# local and remote-tracking branch matching the pattern
|
|
613
|
+
#
|
|
614
|
+
# Returns an empty array when the repository has no branches or no branches
|
|
615
|
+
# match the given pattern.
|
|
616
|
+
#
|
|
617
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
618
|
+
#
|
|
619
|
+
def branch_list(*patterns, remote_names: nil)
|
|
620
|
+
remote_names ||= self.remote_names
|
|
621
|
+
result = Git::Commands::Branch::List.new(@execution_context).call(
|
|
622
|
+
*patterns, all: true, format: Git::Parsers::Branch::FORMAT_STRING
|
|
623
|
+
)
|
|
624
|
+
Git::Parsers::Branch.parse_list(result.stdout, remote_names:)
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
# Returns all local and remote-tracking branches in the 4.x-compatible format
|
|
628
|
+
#
|
|
629
|
+
# Each entry is a 4-element array: `[refname, current, worktree, symref]`.
|
|
630
|
+
# The `refname` uses the short form (`main`, `remotes/origin/main`) to
|
|
631
|
+
# match the output of the legacy `Git::Lib#branches_all` method.
|
|
632
|
+
#
|
|
633
|
+
# @return [Array<Array>] array of `[refname, current, worktree, symref]` tuples
|
|
634
|
+
#
|
|
635
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
636
|
+
#
|
|
637
|
+
# @deprecated Use {#branch_list} instead, which returns richer
|
|
638
|
+
# {Git::BranchInfo} objects.
|
|
639
|
+
#
|
|
640
|
+
def branches_all
|
|
641
|
+
Git::Deprecation.warn(
|
|
642
|
+
'Git::Repository#branches_all is deprecated and will be removed in v6.0.0. ' \
|
|
643
|
+
'Use Git::Repository#branch_list instead.'
|
|
644
|
+
)
|
|
645
|
+
branch_list.map do |info|
|
|
646
|
+
refname = info.remote? ? "remotes/#{info.remote_name}/#{info.short_name}" : info.short_name
|
|
647
|
+
[refname, info.current, info.other_worktree?, info.symref]
|
|
648
|
+
end
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
# Update a branch ref to point to a new commit
|
|
652
|
+
#
|
|
653
|
+
# Derives the full ref from the `branch` argument:
|
|
654
|
+
#
|
|
655
|
+
# - `remotes/<remote>/<name>` or `refs/remotes/<remote>/<name>` →
|
|
656
|
+
# writes to `refs/remotes/<remote>/<name>` (remote-tracking branch)
|
|
657
|
+
# - Any other value → writes to `refs/heads/<branch>` (local branch)
|
|
658
|
+
#
|
|
659
|
+
# @example Advance a local branch to the current HEAD
|
|
660
|
+
# repo.update_ref('feature', repo.rev_parse('HEAD'))
|
|
661
|
+
#
|
|
662
|
+
# @example Reset a local branch to an older commit
|
|
663
|
+
# repo.update_ref('main', 'abc1234def5678')
|
|
664
|
+
#
|
|
665
|
+
# @example Update a remote-tracking branch ref
|
|
666
|
+
# repo.update_ref('remotes/origin/main', 'abc1234def5678')
|
|
667
|
+
#
|
|
668
|
+
# @param branch [String] a local or remote-tracking branch name
|
|
669
|
+
#
|
|
670
|
+
# Short local names (e.g. `'main'`) resolve to `refs/heads/<branch>`.
|
|
671
|
+
# Remote-tracking names with a `remotes/<remote>/` or
|
|
672
|
+
# `refs/remotes/<remote>/` prefix (e.g. `'remotes/origin/main'`)
|
|
673
|
+
# resolve to `refs/remotes/<remote>/<name>`.
|
|
674
|
+
#
|
|
675
|
+
# @param commit [String] the commit SHA to point the branch at
|
|
676
|
+
#
|
|
677
|
+
# @return [Git::CommandLine::Result] the result of calling `git update-ref`
|
|
678
|
+
#
|
|
679
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
680
|
+
#
|
|
681
|
+
def update_ref(branch, commit)
|
|
682
|
+
ref = Private.build_update_ref(branch)
|
|
683
|
+
Git::Commands::UpdateRef::Update.new(@execution_context).call(ref, commit)
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
# Returns a {Git::Branch} object for the given branch name
|
|
687
|
+
#
|
|
688
|
+
# @example Get a branch object for 'main'
|
|
689
|
+
# repo.branch('main') #=> #<Git::Branch 'main'>
|
|
690
|
+
#
|
|
691
|
+
# @example Get a branch object for the current branch
|
|
692
|
+
# repo.branch #=> #<Git::Branch 'main'>
|
|
693
|
+
#
|
|
694
|
+
# @param branch_name [String] the branch name (defaults to the current branch)
|
|
695
|
+
#
|
|
696
|
+
# @return [Git::Branch] the branch object
|
|
697
|
+
#
|
|
698
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
699
|
+
#
|
|
700
|
+
# @deprecated Use `branch_list(name).first` and the name-based branch
|
|
701
|
+
# operations instead
|
|
702
|
+
#
|
|
703
|
+
# {#branch_list} returns immutable {Git::BranchInfo} value objects
|
|
704
|
+
# rather than {Git::Branch}. It takes `git branch --list` patterns, so
|
|
705
|
+
# pass the short name of a local branch or `"#{remote}/#{name}"` for a
|
|
706
|
+
# remote-tracking branch; the `remotes/` and `refs/` prefixes this
|
|
707
|
+
# method accepts match nothing. A `"#{remote}/#{name}"` pattern also
|
|
708
|
+
# matches a local branch of that name, so take `find(&:remote?)` rather
|
|
709
|
+
# than `first` for a remote-tracking branch. With no argument this
|
|
710
|
+
# method wraps {#current_branch}, which is `'HEAD'` when HEAD is
|
|
711
|
+
# detached; {#branch_list} has no entry for a detached or unborn HEAD,
|
|
712
|
+
# so use {#current_branch_state} in those states. Call the
|
|
713
|
+
# corresponding {Git::Repository} method (e.g. {#checkout},
|
|
714
|
+
# {#branch_new}, {#branch_delete}) for operations on a branch.
|
|
715
|
+
#
|
|
716
|
+
# @see #branch_list
|
|
717
|
+
#
|
|
718
|
+
def branch(branch_name = current_branch)
|
|
719
|
+
Git::Deprecation.warn(
|
|
720
|
+
'Git::Repository#branch is deprecated and will be removed in v6.0.0. ' \
|
|
721
|
+
'Use Git::Repository#branch_list(name).first for a local branch, ' \
|
|
722
|
+
'Git::Repository#branch_list("remote/name").find(&:remote?) for a remote-tracking branch, ' \
|
|
723
|
+
'and the name-based branch operations instead.'
|
|
724
|
+
)
|
|
725
|
+
Git::Branch.new(self, branch_name)
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
# Returns a {Git::Branches} collection of all branches in the repository
|
|
729
|
+
#
|
|
730
|
+
# @example List all branches
|
|
731
|
+
# repo.branches
|
|
732
|
+
# # => #<Git::Branches ...>
|
|
733
|
+
#
|
|
734
|
+
# @example Iterate over all branches
|
|
735
|
+
# repo.branches.each { |b| puts b.name }
|
|
736
|
+
#
|
|
737
|
+
# @example Access local branches only
|
|
738
|
+
# repo.branches.local
|
|
739
|
+
#
|
|
740
|
+
# @example Access remote-tracking branches only
|
|
741
|
+
# repo.branches.remote
|
|
742
|
+
#
|
|
743
|
+
# @example Look up a branch by name
|
|
744
|
+
# repo.branches['main'] # => #<Git::Branch 'main'>
|
|
745
|
+
#
|
|
746
|
+
# @return [Git::Branches] a collection wrapping all local and
|
|
747
|
+
# remote-tracking branches in the repository
|
|
748
|
+
#
|
|
749
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
750
|
+
#
|
|
751
|
+
# @deprecated Use {#branch_list} instead
|
|
752
|
+
#
|
|
753
|
+
# {#branch_list} returns `Array<Git::BranchInfo>` (immutable value
|
|
754
|
+
# objects) rather than a {Git::Branches} collection. Filter it with
|
|
755
|
+
# `select(&:remote?)` or `reject(&:remote?)` in place of
|
|
756
|
+
# `branches.remote` and `branches.local`, and look a branch up by name
|
|
757
|
+
# with `branch_list(name).first` in place of `branches[name]`.
|
|
758
|
+
#
|
|
759
|
+
# @see #branch_list
|
|
760
|
+
#
|
|
761
|
+
def branches
|
|
762
|
+
Git::Deprecation.warn(
|
|
763
|
+
'Git::Repository#branches is deprecated and will be removed in v6.0.0. ' \
|
|
764
|
+
'Use Git::Repository#branch_list instead.'
|
|
765
|
+
)
|
|
766
|
+
Git::Branches.new(self)
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
# Private helpers local to {Git::Repository::Branching}
|
|
770
|
+
#
|
|
771
|
+
# @api private
|
|
772
|
+
module Private
|
|
773
|
+
module_function
|
|
774
|
+
|
|
775
|
+
# Determines whether the given branch ref points to an existing commit
|
|
776
|
+
#
|
|
777
|
+
# Returns `:active` when the branch ref resolves successfully. Returns
|
|
778
|
+
# `:unborn` when the branch ref exists but has no commits yet (exit
|
|
779
|
+
# status 1 with empty stderr from `git rev-parse --verify --quiet`).
|
|
780
|
+
# Re-raises for any other failure.
|
|
781
|
+
#
|
|
782
|
+
# @param execution_context [Git::ExecutionContext::Repository] the
|
|
783
|
+
# execution context for git commands
|
|
784
|
+
#
|
|
785
|
+
# The full `refs/heads/<name>` ref is verified rather than the bare name.
|
|
786
|
+
# A bare name follows the gitrevisions search order, in which
|
|
787
|
+
# `refs/tags/<name>` is tried before `refs/heads/<name>`, so an unborn
|
|
788
|
+
# branch that shares its name with a tag would be reported as `:active`.
|
|
789
|
+
#
|
|
790
|
+
# @param branch_name [String] the branch name to verify
|
|
791
|
+
#
|
|
792
|
+
# @return [:active, :unborn] the branch ref state
|
|
793
|
+
#
|
|
794
|
+
# @raise [Git::FailedError] if git exits with a failure unrelated to an
|
|
795
|
+
# unborn branch
|
|
796
|
+
#
|
|
797
|
+
# @api private
|
|
798
|
+
#
|
|
799
|
+
def get_branch_state(execution_context, branch_name)
|
|
800
|
+
Git::Commands::RevParse.new(execution_context).call("refs/heads/#{branch_name}", verify: true, quiet: true)
|
|
801
|
+
:active
|
|
802
|
+
rescue Git::FailedError => e
|
|
803
|
+
raise unless e.result.status.exitstatus == 1 && e.result.stderr.empty?
|
|
804
|
+
|
|
805
|
+
:unborn
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
# Translates {#checkout} options to the new command interface
|
|
809
|
+
#
|
|
810
|
+
# Legacy callers passed combinations like:
|
|
811
|
+
# checkout('branch', new_branch: true, start_point: 'main')
|
|
812
|
+
# which should map to:
|
|
813
|
+
# checkout('main', b: 'branch')
|
|
814
|
+
#
|
|
815
|
+
# `orphan: true` follows the same shape, naming the unborn branch:
|
|
816
|
+
# checkout('branch', orphan: true, start_point: 'main')
|
|
817
|
+
# maps to:
|
|
818
|
+
# checkout('main', orphan: 'branch')
|
|
819
|
+
#
|
|
820
|
+
# @param branch [String, nil] the branch argument passed to {#checkout}
|
|
821
|
+
#
|
|
822
|
+
# @param checkout_options [Hash] the raw options passed to {#checkout}
|
|
823
|
+
#
|
|
824
|
+
# @return [Array((String, nil), Hash)] a two-element tuple
|
|
825
|
+
# `[target, options]` containing the translated checkout arguments
|
|
826
|
+
#
|
|
827
|
+
# `target` (`String` or `nil`) is the branch or commit to check out.
|
|
828
|
+
# `options` is a `Hash` of keyword arguments for
|
|
829
|
+
# `Git::Commands::Checkout::Branch#call`
|
|
830
|
+
#
|
|
831
|
+
# @api private
|
|
832
|
+
#
|
|
833
|
+
def translate_checkout_opts(branch, checkout_options)
|
|
834
|
+
checkout_options = normalize_orphan_option(checkout_options)
|
|
835
|
+
|
|
836
|
+
if checkout_options[:new_branch] == true || checkout_options[:b] == true
|
|
837
|
+
[checkout_options[:start_point], checkout_options.except(:new_branch, :b, :start_point).merge(b: branch)]
|
|
838
|
+
elsif checkout_options[:new_branch].is_a?(String)
|
|
839
|
+
[branch, checkout_options.except(:new_branch).merge(b: checkout_options[:new_branch])]
|
|
840
|
+
elsif checkout_options[:orphan] == true
|
|
841
|
+
translate_orphan_opts(branch, checkout_options)
|
|
842
|
+
else
|
|
843
|
+
[branch, checkout_options]
|
|
844
|
+
end
|
|
845
|
+
end
|
|
846
|
+
|
|
847
|
+
# Normalizes the `:orphan` option, rejecting names that git would never see
|
|
848
|
+
#
|
|
849
|
+
# `:orphan` is a value option on the underlying command, so a literal
|
|
850
|
+
# `false` would be emitted as `--orphan false` and create a branch named
|
|
851
|
+
# "false". Flag options such as `:force` already ignore `false`; this
|
|
852
|
+
# gives `:orphan` the same behavior.
|
|
853
|
+
#
|
|
854
|
+
# A blank name is rejected rather than dropped: the argument DSL omits
|
|
855
|
+
# empty values, so `orphan: ''` would otherwise degrade silently into a
|
|
856
|
+
# plain checkout.
|
|
857
|
+
#
|
|
858
|
+
# @param checkout_options [Hash] the raw options passed to {#checkout}
|
|
859
|
+
#
|
|
860
|
+
# @return [Hash] the options with a `false` `:orphan` key removed
|
|
861
|
+
#
|
|
862
|
+
# @raise [ArgumentError] if `:orphan` is given a blank branch name
|
|
863
|
+
#
|
|
864
|
+
# @api private
|
|
865
|
+
#
|
|
866
|
+
def normalize_orphan_option(checkout_options)
|
|
867
|
+
orphan = checkout_options[:orphan]
|
|
868
|
+
return checkout_options.except(:orphan) if orphan == false
|
|
869
|
+
raise ArgumentError, 'orphan requires a non-empty branch name' if orphan.is_a?(String) && orphan.strip.empty?
|
|
870
|
+
|
|
871
|
+
checkout_options
|
|
872
|
+
end
|
|
873
|
+
|
|
874
|
+
# Translates `orphan: true` into the command's `:orphan` value option
|
|
875
|
+
#
|
|
876
|
+
# `orphan: true` names the unborn branch from the positional argument and
|
|
877
|
+
# takes its start point from `:start_point`, mirroring `new_branch: true`.
|
|
878
|
+
#
|
|
879
|
+
# @param branch [String, nil] the branch argument passed to {#checkout}
|
|
880
|
+
#
|
|
881
|
+
# @param checkout_options [Hash] the raw options passed to {#checkout}
|
|
882
|
+
#
|
|
883
|
+
# @return [Array((String, nil), Hash)] a two-element tuple
|
|
884
|
+
# `[target, options]` containing the translated checkout arguments
|
|
885
|
+
#
|
|
886
|
+
# @raise [ArgumentError] if `branch` is blank (`nil`, empty, or whitespace
|
|
887
|
+
# only), since the unborn branch would otherwise have no name
|
|
888
|
+
#
|
|
889
|
+
# @api private
|
|
890
|
+
#
|
|
891
|
+
def translate_orphan_opts(branch, checkout_options)
|
|
892
|
+
raise ArgumentError, 'orphan: true requires a branch name' if branch.to_s.strip.empty?
|
|
893
|
+
|
|
894
|
+
[checkout_options[:start_point], checkout_options.except(:start_point).merge(orphan: branch)]
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
# Normalizes path specifications for Git commands
|
|
898
|
+
#
|
|
899
|
+
# @param pathspecs [String, Pathname, Array<String, Pathname>, nil]
|
|
900
|
+
# the path(s) to normalize
|
|
901
|
+
#
|
|
902
|
+
# @param arg_name [String] the argument name used in error messages
|
|
903
|
+
#
|
|
904
|
+
# @return [Array<String>, nil] the normalized paths, or `nil` if none are valid
|
|
905
|
+
#
|
|
906
|
+
# @raise [ArgumentError] when any path is not a `String` or `Pathname`
|
|
907
|
+
#
|
|
908
|
+
# @api private
|
|
909
|
+
#
|
|
910
|
+
def normalize_pathspecs(pathspecs, arg_name)
|
|
911
|
+
return nil unless pathspecs
|
|
912
|
+
|
|
913
|
+
normalized = Array(pathspecs)
|
|
914
|
+
validate_pathspec_types(normalized, arg_name)
|
|
915
|
+
|
|
916
|
+
normalized = normalized.map(&:to_s).reject(&:empty?)
|
|
917
|
+
return nil if normalized.empty?
|
|
918
|
+
|
|
919
|
+
normalized
|
|
920
|
+
end
|
|
921
|
+
|
|
922
|
+
# Raises an error if any element of `pathspecs` is not a `String` or `Pathname`
|
|
923
|
+
#
|
|
924
|
+
# @param pathspecs [Array] the path elements to validate
|
|
925
|
+
#
|
|
926
|
+
# @param arg_name [String] the argument name used in error messages
|
|
927
|
+
#
|
|
928
|
+
# @return [void]
|
|
929
|
+
#
|
|
930
|
+
# @raise [ArgumentError] when any element is not a `String` or `Pathname`
|
|
931
|
+
#
|
|
932
|
+
# @api private
|
|
933
|
+
#
|
|
934
|
+
def validate_pathspec_types(pathspecs, arg_name)
|
|
935
|
+
return if pathspecs.all? { |path| path.is_a?(String) || path.is_a?(Pathname) }
|
|
936
|
+
|
|
937
|
+
raise ArgumentError, "Invalid #{arg_name}: must be a String, Pathname, or Array of Strings/Pathnames"
|
|
938
|
+
end
|
|
939
|
+
|
|
940
|
+
# Builds the full git ref string from a branch name argument
|
|
941
|
+
#
|
|
942
|
+
# Mirrors the routing logic of `Git::Branch#update_ref` for backward
|
|
943
|
+
# compatibility:
|
|
944
|
+
#
|
|
945
|
+
# - `remotes/<remote>/<name>` or `refs/remotes/<remote>/<name>` →
|
|
946
|
+
# `refs/remotes/<remote>/<name>`
|
|
947
|
+
# - Any other value → `refs/heads/<branch>`
|
|
948
|
+
#
|
|
949
|
+
# @param branch [String] a short local branch name or a remote-tracking
|
|
950
|
+
# branch name with a `remotes/` or `refs/remotes/` prefix
|
|
951
|
+
#
|
|
952
|
+
# @return [String] the full git ref string
|
|
953
|
+
#
|
|
954
|
+
# @api private
|
|
955
|
+
#
|
|
956
|
+
def build_update_ref(branch)
|
|
957
|
+
match = branch.match(%r{\A(?:refs/)?remotes/([^/]+)/(.+)\z})
|
|
958
|
+
match ? "refs/remotes/#{match[1]}/#{match[2]}" : "refs/heads/#{branch}"
|
|
959
|
+
end
|
|
960
|
+
end
|
|
961
|
+
private_constant :Private
|
|
962
|
+
end
|
|
963
|
+
end
|
|
964
|
+
end
|