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,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
# Represents a branch that failed to be deleted
|
|
5
|
+
#
|
|
6
|
+
# This is an immutable data object returned as part of {Git::BranchDeleteResult}
|
|
7
|
+
# when one or more branches could not be deleted.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# failure = Git::BranchDeleteFailure.new(
|
|
11
|
+
# name: 'nonexistent',
|
|
12
|
+
# error_message: "branch 'nonexistent' not found."
|
|
13
|
+
# )
|
|
14
|
+
# failure.name #=> 'nonexistent'
|
|
15
|
+
# failure.error_message #=> "branch 'nonexistent' not found."
|
|
16
|
+
#
|
|
17
|
+
# @see Git::BranchDeleteResult
|
|
18
|
+
#
|
|
19
|
+
# @see Git::Commands::Branch::Delete
|
|
20
|
+
#
|
|
21
|
+
# @api public
|
|
22
|
+
#
|
|
23
|
+
# @!attribute [r] name
|
|
24
|
+
# The name of the branch that failed to be deleted
|
|
25
|
+
#
|
|
26
|
+
# @return [String]
|
|
27
|
+
#
|
|
28
|
+
# @!attribute [r] error_message
|
|
29
|
+
# The error message from git explaining why the branch could not be deleted
|
|
30
|
+
#
|
|
31
|
+
# @return [String]
|
|
32
|
+
#
|
|
33
|
+
BranchDeleteFailure = Data.define(:name, :error_message)
|
|
34
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'git/branch_info'
|
|
4
|
+
require 'git/branch_delete_failure'
|
|
5
|
+
|
|
6
|
+
module Git
|
|
7
|
+
# Represents the result of a branch delete operation
|
|
8
|
+
#
|
|
9
|
+
# This is an immutable data object returned by {Git::Commands::Branch::Delete#call}.
|
|
10
|
+
# It contains information about which branches were successfully deleted and which
|
|
11
|
+
# failed to be deleted, along with the reason for each failure.
|
|
12
|
+
#
|
|
13
|
+
# Git's `git branch -d` command uses "best effort" semantics - it deletes as many
|
|
14
|
+
# branches as possible and reports errors for those that couldn't be deleted. This
|
|
15
|
+
# result object reflects that behavior, allowing callers to inspect both
|
|
16
|
+
# successes and failures.
|
|
17
|
+
#
|
|
18
|
+
# @example Successful deletion of all branches
|
|
19
|
+
# result = branch_delete.call('feature-1', 'feature-2')
|
|
20
|
+
# result.success? #=> true
|
|
21
|
+
# result.deleted.map(&:name) #=> ['feature-1', 'feature-2']
|
|
22
|
+
# result.not_deleted #=> []
|
|
23
|
+
#
|
|
24
|
+
# @example Partial failure (some branches deleted, some not found)
|
|
25
|
+
# result = branch_delete.call('feature-1', 'nonexistent', 'feature-2')
|
|
26
|
+
# result.success? #=> false
|
|
27
|
+
# result.deleted.map(&:name) #=> ['feature-1', 'feature-2']
|
|
28
|
+
# result.not_deleted.first.name #=> 'nonexistent'
|
|
29
|
+
# result.not_deleted.first.error_message #=> "branch 'nonexistent' not found."
|
|
30
|
+
#
|
|
31
|
+
# @see Git::BranchInfo
|
|
32
|
+
#
|
|
33
|
+
# @see Git::BranchDeleteFailure
|
|
34
|
+
#
|
|
35
|
+
# @see Git::Commands::Branch::Delete
|
|
36
|
+
#
|
|
37
|
+
# @api public
|
|
38
|
+
#
|
|
39
|
+
# @!attribute [r] deleted
|
|
40
|
+
# Branches that were successfully deleted
|
|
41
|
+
#
|
|
42
|
+
# @return [Array<Git::BranchInfo>]
|
|
43
|
+
#
|
|
44
|
+
# @!attribute [r] not_deleted
|
|
45
|
+
# Branches that could not be deleted, with the reason for each failure
|
|
46
|
+
#
|
|
47
|
+
# @return [Array<Git::BranchDeleteFailure>]
|
|
48
|
+
#
|
|
49
|
+
BranchDeleteResult = Data.define(:deleted, :not_deleted) do
|
|
50
|
+
# Returns true if all requested branches were successfully deleted
|
|
51
|
+
#
|
|
52
|
+
# @example Check if the delete operation succeeded
|
|
53
|
+
# result = branch_delete.call('feature-branch')
|
|
54
|
+
# if result.success?
|
|
55
|
+
# puts "All branches deleted successfully"
|
|
56
|
+
# else
|
|
57
|
+
# puts "Some branches could not be deleted:"
|
|
58
|
+
# result.not_deleted.each { |f| puts " #{f.name}: #{f.error_message}" }
|
|
59
|
+
# end
|
|
60
|
+
#
|
|
61
|
+
# @return [Boolean] true if no branches failed to delete, false otherwise
|
|
62
|
+
def success?
|
|
63
|
+
not_deleted.empty?
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
# Regular expression for parsing branch refnames
|
|
5
|
+
#
|
|
6
|
+
# Captures:
|
|
7
|
+
# - remote_name: the remote name (e.g., 'origin') for remote branches, nil for local
|
|
8
|
+
# - branch_name: the branch name without the remote prefix
|
|
9
|
+
#
|
|
10
|
+
# @example Parse branch refnames
|
|
11
|
+
# 'main' => { remote_name: nil, branch_name: 'main' }
|
|
12
|
+
# 'refs/heads/main' => { remote_name: nil, branch_name: 'main' }
|
|
13
|
+
# 'remotes/origin/main' => { remote_name: 'origin', branch_name: 'main' }
|
|
14
|
+
# 'refs/remotes/origin/main' => { remote_name: 'origin', branch_name: 'main' }
|
|
15
|
+
# 'feature/foo' => { remote_name: nil, branch_name: 'feature/foo' }
|
|
16
|
+
# 'remotes/origin/feature/bar' => { remote_name: 'origin', branch_name: 'feature/bar' }
|
|
17
|
+
#
|
|
18
|
+
# @note This regex handles both raw full refs (e.g., `refs/heads/main`) as stored in
|
|
19
|
+
# {Git::BranchInfo#refname} and normalized short-form refs (e.g., `main`,
|
|
20
|
+
# `remotes/origin/main`) used elsewhere.
|
|
21
|
+
#
|
|
22
|
+
# @note This regex is a fallback for branch refnames parsed without configured
|
|
23
|
+
# remote context. Remote names containing '/' can only be resolved reliably
|
|
24
|
+
# when the parser is given the configured remote names. See:
|
|
25
|
+
# https://github.com/ruby-git/ruby-git/issues/919
|
|
26
|
+
#
|
|
27
|
+
# @api private
|
|
28
|
+
BRANCH_REFNAME_REGEXP = %r{
|
|
29
|
+
\A # start of string
|
|
30
|
+
(?:refs/heads/)? # optional refs/heads/ prefix (stripped)
|
|
31
|
+
(?:(?:refs/)?remotes/(?<remote_name>[^/]+)/)? # optional refs?/remotes/<remote_name>/
|
|
32
|
+
(?<branch_name>.+) # branch name (everything else)
|
|
33
|
+
\z # end of string
|
|
34
|
+
}x
|
|
35
|
+
|
|
36
|
+
# Sentinel for distinguishing omitted BranchInfo remote_name from explicit nil
|
|
37
|
+
REMOTE_NAME_NOT_GIVEN = Object.new.freeze
|
|
38
|
+
private_constant :REMOTE_NAME_NOT_GIVEN
|
|
39
|
+
|
|
40
|
+
# Value object representing branch metadata from git branch output
|
|
41
|
+
#
|
|
42
|
+
# This is a lightweight, immutable data structure returned by branch listing
|
|
43
|
+
# commands. It contains only the data parsed from git output without any
|
|
44
|
+
# repository context or operations.
|
|
45
|
+
#
|
|
46
|
+
# @example Local branch with upstream tracking
|
|
47
|
+
# info = Git::BranchInfo.new(
|
|
48
|
+
# refname: 'refs/heads/main',
|
|
49
|
+
# target_oid: 'abc123def456789012345678901234567890abcd',
|
|
50
|
+
# current: true,
|
|
51
|
+
# worktree_path: nil,
|
|
52
|
+
# symref: nil,
|
|
53
|
+
# upstream: 'refs/remotes/origin/main'
|
|
54
|
+
# )
|
|
55
|
+
# info.current? #=> true
|
|
56
|
+
# info.remote? #=> false
|
|
57
|
+
# info.short_name #=> 'main'
|
|
58
|
+
# info.upstream #=> 'refs/remotes/origin/main'
|
|
59
|
+
#
|
|
60
|
+
# @example Remote-tracking branch
|
|
61
|
+
# info = Git::BranchInfo.new(
|
|
62
|
+
# refname: 'refs/remotes/origin/main',
|
|
63
|
+
# target_oid: 'abc123def456789012345678901234567890abcd',
|
|
64
|
+
# current: false,
|
|
65
|
+
# worktree_path: nil,
|
|
66
|
+
# symref: nil,
|
|
67
|
+
# upstream: nil
|
|
68
|
+
# )
|
|
69
|
+
# info.remote? #=> true
|
|
70
|
+
# info.remote_name #=> 'origin'
|
|
71
|
+
# info.short_name #=> 'main'
|
|
72
|
+
#
|
|
73
|
+
# @see Git::Repository::Branching#branch_list for the repository method that returns these
|
|
74
|
+
#
|
|
75
|
+
# @see Git::Commands::Branch::List for the command that produces these
|
|
76
|
+
#
|
|
77
|
+
# @api public
|
|
78
|
+
#
|
|
79
|
+
# @!attribute [r] refname
|
|
80
|
+
#
|
|
81
|
+
# The full reference name of the branch
|
|
82
|
+
#
|
|
83
|
+
# Must be the full refname as returned by git (e.g., 'refs/heads/main',
|
|
84
|
+
# 'refs/remotes/origin/main') because the short name alone is not guaranteed to
|
|
85
|
+
# be unique (e.g., 'main' could exist as both a local and remote branch).
|
|
86
|
+
#
|
|
87
|
+
# @return [String] the branch refname (e.g., 'refs/heads/main',
|
|
88
|
+
# 'refs/remotes/origin/main')
|
|
89
|
+
#
|
|
90
|
+
# @!attribute [r] remote_name
|
|
91
|
+
#
|
|
92
|
+
# @return [String, nil] the resolved or fallback-derived remote name, or nil
|
|
93
|
+
# for local branches
|
|
94
|
+
#
|
|
95
|
+
# @!attribute [r] target_oid
|
|
96
|
+
#
|
|
97
|
+
# The commit object ID (SHA) that this branch points to (aka HEAD)
|
|
98
|
+
#
|
|
99
|
+
# @return [String, nil] the full 40-character object ID, or nil if branch is
|
|
100
|
+
# unborn (no commits yet)
|
|
101
|
+
#
|
|
102
|
+
# @!attribute [r] current
|
|
103
|
+
#
|
|
104
|
+
# Whether this branch is currently checked out in the current worktree
|
|
105
|
+
#
|
|
106
|
+
# @return [Boolean] true if this is the current branch
|
|
107
|
+
#
|
|
108
|
+
# @note A branch can be current ({#current?} true) or in another worktree
|
|
109
|
+
# ({#other_worktree?} true), but never both. A branch not checked out
|
|
110
|
+
# anywhere has both false.
|
|
111
|
+
#
|
|
112
|
+
# @!attribute [r] worktree_path
|
|
113
|
+
#
|
|
114
|
+
# The absolute path of the *other* linked worktree this branch is checked
|
|
115
|
+
# out in, or nil.
|
|
116
|
+
#
|
|
117
|
+
# This is nil in two distinct cases:
|
|
118
|
+
# - The branch is the current branch in this worktree (use {#current?} to
|
|
119
|
+
# distinguish that case)
|
|
120
|
+
# - The branch is not checked out in any worktree
|
|
121
|
+
#
|
|
122
|
+
# This path is suppressed for the current branch even though git reports it
|
|
123
|
+
# via `%(worktreepath)`, because the current worktree's path is already
|
|
124
|
+
# known from the repository object and storing it here would make
|
|
125
|
+
# {#other_worktree?} incorrect.
|
|
126
|
+
#
|
|
127
|
+
# @return [String, nil] the absolute path of the linked worktree root
|
|
128
|
+
# directory (e.g., `'/home/user/projects/my-repo-hotfix'`), or nil if
|
|
129
|
+
# the branch is not checked out in a different linked worktree
|
|
130
|
+
#
|
|
131
|
+
# @!attribute [r] symref
|
|
132
|
+
#
|
|
133
|
+
# The target reference if this is a symbolic reference
|
|
134
|
+
#
|
|
135
|
+
# @return [String, nil] the target ref (e.g., 'refs/heads/main'), or nil if not a symref
|
|
136
|
+
#
|
|
137
|
+
# @!attribute [r] upstream
|
|
138
|
+
#
|
|
139
|
+
# The configured upstream/tracking branch refname as reported by git
|
|
140
|
+
#
|
|
141
|
+
# @return [String, nil] the raw upstream refname from `%(upstream)`
|
|
142
|
+
# (e.g., `'refs/remotes/origin/main'`), or nil if no upstream is configured
|
|
143
|
+
#
|
|
144
|
+
# @note Remote-tracking branches (e.g., `'refs/remotes/origin/main'`) have upstream: nil
|
|
145
|
+
#
|
|
146
|
+
# @note This is the raw refname snapshot from when the branch list was read.
|
|
147
|
+
# It does not reflect live git state after the snapshot was taken.
|
|
148
|
+
#
|
|
149
|
+
BranchInfo = Data.define(:refname, :remote_name, :target_oid, :current, :worktree_path, :symref, :upstream) do
|
|
150
|
+
# @param refname [String] the full branch refname
|
|
151
|
+
#
|
|
152
|
+
# @param remote_name [String, nil] resolved remote name, nil for local branches,
|
|
153
|
+
# or omitted to derive from `refname`
|
|
154
|
+
#
|
|
155
|
+
# @param target_oid [String, nil] the commit object ID, or nil for unborn branches
|
|
156
|
+
#
|
|
157
|
+
# @param current [Boolean] whether this branch is currently checked out
|
|
158
|
+
#
|
|
159
|
+
# @param worktree_path [String, nil] path to another linked worktree, or nil
|
|
160
|
+
#
|
|
161
|
+
# @param symref [String, nil] symbolic reference target, or nil
|
|
162
|
+
#
|
|
163
|
+
# @param upstream [String, nil] upstream refname, or nil
|
|
164
|
+
#
|
|
165
|
+
def initialize(refname:, target_oid:, current:, worktree_path:, symref:, upstream:, # rubocop:disable Metrics/ParameterLists
|
|
166
|
+
remote_name: REMOTE_NAME_NOT_GIVEN)
|
|
167
|
+
remote_name = self.class.fallback_remote_name(refname) if remote_name.equal?(REMOTE_NAME_NOT_GIVEN)
|
|
168
|
+
self.class.validate_remote_name!(refname, remote_name)
|
|
169
|
+
|
|
170
|
+
super
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# @param refname [String] the branch refname to validate
|
|
174
|
+
#
|
|
175
|
+
# @param remote_name [String, nil] the remote name to validate
|
|
176
|
+
#
|
|
177
|
+
# @return [void]
|
|
178
|
+
#
|
|
179
|
+
# @raise [ArgumentError] if the remote name contradicts the refname type
|
|
180
|
+
def self.validate_remote_name!(refname, remote_name)
|
|
181
|
+
if remote_tracking_refname?(refname)
|
|
182
|
+
unless remote_name.is_a?(String) && !remote_name.empty?
|
|
183
|
+
raise ArgumentError, 'remote_name must be a non-empty String for remote-tracking refname'
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
|
|
187
|
+
raise ArgumentError, 'remote_name must match remote-tracking refname' unless refname.match?(remote_ref_prefix)
|
|
188
|
+
elsif !remote_name.nil?
|
|
189
|
+
raise ArgumentError, 'remote_name must be nil for local branch refname'
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# @param refname [String] the branch refname to parse
|
|
194
|
+
#
|
|
195
|
+
# @return [String, nil] the regex-derived remote name, or nil for local branches
|
|
196
|
+
def self.fallback_remote_name(refname)
|
|
197
|
+
refname.match(Git::BRANCH_REFNAME_REGEXP)[:remote_name]
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# @param refname [String] the branch refname to inspect
|
|
201
|
+
#
|
|
202
|
+
# @return [Boolean] true if the refname is a remote-tracking refname
|
|
203
|
+
def self.remote_tracking_refname?(refname)
|
|
204
|
+
refname.match?(%r{\A(?:refs/)?remotes/[^/]+/.+})
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# @return [Boolean] always false for BranchInfo (see DetachedHeadInfo for detached state)
|
|
208
|
+
def detached? = false
|
|
209
|
+
|
|
210
|
+
# @return [Boolean] true if this is an unborn branch (no commits yet)
|
|
211
|
+
def unborn? = target_oid.nil?
|
|
212
|
+
|
|
213
|
+
# @return [String] the short branch name without any remote or heads prefix
|
|
214
|
+
# (e.g., 'main' or 'feature/foo')
|
|
215
|
+
def short_name
|
|
216
|
+
return refname.delete_prefix('refs/heads/') if remote_name.nil?
|
|
217
|
+
|
|
218
|
+
remote_ref_prefix = %r{\A(?:refs/)?remotes/#{Regexp.escape(remote_name)}/}
|
|
219
|
+
refname.sub(remote_ref_prefix, '')
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# @return [Boolean] true if this is the currently checked out branch
|
|
223
|
+
def current? = current
|
|
224
|
+
|
|
225
|
+
# @return [Boolean] true if this branch is checked out in another linked worktree
|
|
226
|
+
def other_worktree? = !worktree_path.nil?
|
|
227
|
+
|
|
228
|
+
# @return [Boolean] true if this is a symbolic reference
|
|
229
|
+
def symref? = !symref.nil?
|
|
230
|
+
|
|
231
|
+
# @return [Boolean] true if this is a remote-tracking branch
|
|
232
|
+
def remote? = !remote_name.nil?
|
|
233
|
+
|
|
234
|
+
# @return [String] string representation (the full refname)
|
|
235
|
+
def to_s = refname
|
|
236
|
+
end
|
|
237
|
+
end
|
data/lib/git/branches.rb
CHANGED
|
@@ -1,71 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Git
|
|
2
|
-
|
|
3
|
-
#
|
|
4
|
+
# Collection of all Git branches in a repository
|
|
5
|
+
#
|
|
6
|
+
# Wraps both local and remote-tracking branches and provides filtering,
|
|
7
|
+
# enumeration, and name-based lookup.
|
|
8
|
+
#
|
|
9
|
+
# @example Enumerate all branches
|
|
10
|
+
# branches = repo.branches
|
|
11
|
+
# branches.each { |b| puts b.name }
|
|
12
|
+
#
|
|
13
|
+
# @deprecated Use {Git::Repository::Branching#branch_list} instead
|
|
14
|
+
#
|
|
15
|
+
# {Git::Repository::Branching#branch_list} returns `Array<Git::BranchInfo>`
|
|
16
|
+
# (immutable value objects). Filter it with `select(&:remote?)` or
|
|
17
|
+
# `reject(&:remote?)` in place of {#remote} and {#local}, and look a
|
|
18
|
+
# branch up by name with `branch_list(name).first` in place of {#[]}.
|
|
19
|
+
# Constructing a `Git::Branches` emits a deprecation warning.
|
|
20
|
+
#
|
|
21
|
+
# @api public
|
|
22
|
+
#
|
|
4
23
|
class Branches
|
|
5
|
-
|
|
6
24
|
include Enumerable
|
|
7
|
-
|
|
25
|
+
|
|
26
|
+
# Creates a new Branches collection populated from the given repository
|
|
27
|
+
#
|
|
28
|
+
# @param base [Git::Repository] the repository to enumerate
|
|
29
|
+
# branches from
|
|
30
|
+
#
|
|
31
|
+
# @return [void]
|
|
32
|
+
#
|
|
33
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
34
|
+
#
|
|
35
|
+
# @deprecated Use {Git::Repository::Branching#branch_list} instead
|
|
36
|
+
#
|
|
37
|
+
# @see Git::Repository::Branching#branch_list
|
|
38
|
+
#
|
|
8
39
|
def initialize(base)
|
|
40
|
+
Git::Deprecation.warn(
|
|
41
|
+
'Git::Branches is deprecated and will be removed in v6.0.0. ' \
|
|
42
|
+
'Use Git::Repository#branch_list instead.'
|
|
43
|
+
)
|
|
9
44
|
@branches = {}
|
|
10
|
-
|
|
45
|
+
@lookup = {}
|
|
46
|
+
|
|
11
47
|
@base = base
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@branches[b[0]] = Git::Branch.new(@base, b[0])
|
|
15
|
-
end
|
|
48
|
+
|
|
49
|
+
load_branches
|
|
16
50
|
end
|
|
17
51
|
|
|
52
|
+
# Returns all local (non-remote-tracking) branches
|
|
53
|
+
#
|
|
54
|
+
# @example List local branch names
|
|
55
|
+
# repo.branches.local.map(&:name)
|
|
56
|
+
#
|
|
57
|
+
# @return [Array<Git::Branch>] the local branches
|
|
58
|
+
#
|
|
18
59
|
def local
|
|
19
|
-
|
|
60
|
+
reject(&:remote)
|
|
20
61
|
end
|
|
21
|
-
|
|
62
|
+
|
|
63
|
+
# Returns all remote-tracking branches
|
|
64
|
+
#
|
|
65
|
+
# @example List remote branch names
|
|
66
|
+
# repo.branches.remote.map(&:name)
|
|
67
|
+
#
|
|
68
|
+
# @return [Array<Git::Branch>] the remote-tracking branches
|
|
69
|
+
#
|
|
22
70
|
def remote
|
|
23
|
-
self.select
|
|
71
|
+
self.select(&:remote)
|
|
24
72
|
end
|
|
25
|
-
|
|
26
|
-
# array like methods
|
|
27
73
|
|
|
74
|
+
# Returns the number of branches in the collection
|
|
75
|
+
#
|
|
76
|
+
# @example Count all branches
|
|
77
|
+
# repo.branches.size # => 3
|
|
78
|
+
#
|
|
79
|
+
# @return [Integer] the total number of branches
|
|
80
|
+
#
|
|
28
81
|
def size
|
|
29
82
|
@branches.size
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def each(&block)
|
|
33
|
-
@branches.values.each(&block)
|
|
34
83
|
end
|
|
35
|
-
|
|
36
|
-
#
|
|
84
|
+
|
|
85
|
+
# Iterates over every branch in the collection
|
|
86
|
+
#
|
|
87
|
+
# @overload each
|
|
88
|
+
#
|
|
89
|
+
# @example Get an enumerator over all branches
|
|
90
|
+
# enum = repo.branches.each
|
|
91
|
+
#
|
|
92
|
+
# @return [Enumerator<Git::Branch>] an enumerator over all branches
|
|
93
|
+
#
|
|
94
|
+
# @overload each(&block)
|
|
95
|
+
#
|
|
96
|
+
# @example Print every branch name
|
|
97
|
+
# repo.branches.each { |b| puts b.name }
|
|
98
|
+
#
|
|
99
|
+
# @return [Array<Git::Branch>] the full list of branches
|
|
100
|
+
#
|
|
101
|
+
# @yield [branch] passes each branch to the block
|
|
102
|
+
#
|
|
103
|
+
# @yieldparam branch [Git::Branch] a branch in the repository
|
|
104
|
+
#
|
|
105
|
+
# @yieldreturn [void]
|
|
106
|
+
#
|
|
107
|
+
def each(&)
|
|
108
|
+
@branches.values.each(&)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Returns the branch with the given name
|
|
112
|
+
#
|
|
113
|
+
# Supports short names (`'main'`), remote-qualified names
|
|
114
|
+
# (`'working/master'`), and full refspec names
|
|
115
|
+
# (`'remotes/working/master'`).
|
|
37
116
|
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
# master
|
|
41
|
-
# remotes/working/master
|
|
117
|
+
# @example Look up a branch by short name
|
|
118
|
+
# repo.branches['main']
|
|
42
119
|
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
120
|
+
# @example Look up a remote-tracking branch
|
|
121
|
+
# repo.branches['working/master']
|
|
122
|
+
#
|
|
123
|
+
# @param branch_name [#to_s] the name of the branch to retrieve
|
|
124
|
+
#
|
|
125
|
+
# @return [Git::Branch, nil] the matching branch, or `nil` if not found
|
|
46
126
|
#
|
|
47
|
-
# @param [#to_s] branch_name the target branch name.
|
|
48
|
-
# @return [Git::Branch] the target branch.
|
|
49
127
|
def [](branch_name)
|
|
50
|
-
@
|
|
51
|
-
branches[branch.full] ||= branch
|
|
52
|
-
|
|
53
|
-
# This is how Git (version 1.7.9.5) works.
|
|
54
|
-
# Lets you ignore the 'remotes' if its at the beginning of the branch full name (even if is not a real remote branch).
|
|
55
|
-
branches[branch.full.sub('remotes/', '')] ||= branch if branch.full =~ /^remotes\/.+/
|
|
56
|
-
|
|
57
|
-
branches
|
|
58
|
-
end[branch_name.to_s]
|
|
128
|
+
@lookup[branch_name.to_s]
|
|
59
129
|
end
|
|
60
|
-
|
|
130
|
+
|
|
131
|
+
# Returns a string listing all branches, prefixed with `*` for the current branch
|
|
132
|
+
#
|
|
133
|
+
# @example Display all branches
|
|
134
|
+
# puts repo.branches.to_s
|
|
135
|
+
#
|
|
136
|
+
# @return [String] a formatted branch listing
|
|
137
|
+
#
|
|
61
138
|
def to_s
|
|
62
|
-
out = ''
|
|
63
|
-
@branches.
|
|
64
|
-
|
|
139
|
+
out = +''
|
|
140
|
+
@branches.each_value do |b|
|
|
141
|
+
# Git::Branch#current is deprecated too; silence it so one to_s call emits one warning
|
|
142
|
+
current = Git::Deprecation.silence { b.current }
|
|
143
|
+
out << (current ? '* ' : ' ') << b.to_s << "\n"
|
|
65
144
|
end
|
|
66
145
|
out
|
|
67
146
|
end
|
|
68
|
-
|
|
69
|
-
end
|
|
70
147
|
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
# Builds a Git::Branch for every branch in the repository and indexes it
|
|
151
|
+
#
|
|
152
|
+
# @return [void]
|
|
153
|
+
#
|
|
154
|
+
# @api private
|
|
155
|
+
#
|
|
156
|
+
def load_branches
|
|
157
|
+
branch_repository.branch_list.each do |branch_info|
|
|
158
|
+
branch = Git::Branch.new(@base, branch_info)
|
|
159
|
+
|
|
160
|
+
@branches[branch_info.refname] = branch
|
|
161
|
+
index_branch_lookup(branch, refname: branch_info.refname)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# @return [Git::Repository] the repository used to enumerate branches
|
|
166
|
+
#
|
|
167
|
+
# @api private
|
|
168
|
+
#
|
|
169
|
+
def branch_repository
|
|
170
|
+
@base
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Indexes all supported lookup keys for a branch without mutating
|
|
174
|
+
# the canonical `@branches` collection used by enumeration
|
|
175
|
+
#
|
|
176
|
+
# @param branch [Git::Branch] the branch to index
|
|
177
|
+
#
|
|
178
|
+
# @param refname [String] the full refname key to use for primary lookup
|
|
179
|
+
#
|
|
180
|
+
# @return [void]
|
|
181
|
+
#
|
|
182
|
+
# @api private
|
|
183
|
+
#
|
|
184
|
+
def index_branch_lookup(branch, refname:)
|
|
185
|
+
@lookup[refname] ||= branch
|
|
186
|
+
@lookup[branch.full] ||= branch
|
|
187
|
+
|
|
188
|
+
return unless branch.full.start_with?('remotes/')
|
|
189
|
+
|
|
190
|
+
# Mirror git compatibility: allow omitting a leading "remotes/".
|
|
191
|
+
@lookup[branch.full.delete_prefix('remotes/')] ||= branch
|
|
192
|
+
end
|
|
193
|
+
end
|
|
71
194
|
end
|