git 4.3.2 → 5.0.0.beta.1
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/.github/copilot-instructions.md +67 -2705
- data/.github/pull_request_template.md +3 -1
- data/.github/skills/breaking-change-analysis/SKILL.md +102 -0
- data/.github/skills/ci-cd-troubleshooting/SKILL.md +264 -0
- data/.github/skills/command-implementation/REFERENCE.md +993 -0
- data/.github/skills/command-implementation/SKILL.md +229 -0
- data/.github/skills/command-test-conventions/SKILL.md +660 -0
- data/.github/skills/command-yard-documentation/SKILL.md +426 -0
- data/.github/skills/dependency-management/SKILL.md +72 -0
- data/.github/skills/development-workflow/SKILL.md +506 -0
- data/.github/skills/extract-command-from-lib/SKILL.md +487 -0
- data/.github/skills/extract-facade-from-base-lib/SKILL.md +586 -0
- data/.github/skills/facade-implementation/REFERENCE.md +840 -0
- data/.github/skills/facade-implementation/SKILL.md +260 -0
- data/.github/skills/facade-test-conventions/SKILL.md +380 -0
- data/.github/skills/facade-yard-documentation/SKILL.md +429 -0
- data/.github/skills/make-skill-template/SKILL.md +176 -0
- data/.github/skills/pr-readiness-review/SKILL.md +185 -0
- data/.github/skills/project-context/SKILL.md +313 -0
- data/.github/skills/pull-request-review/SKILL.md +168 -0
- data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +131 -0
- data/.github/skills/release-management/SKILL.md +125 -0
- data/.github/skills/review-arguments-dsl/CHECKLIST.md +788 -0
- data/.github/skills/review-arguments-dsl/SKILL.md +214 -0
- data/.github/skills/review-backward-compatibility/SKILL.md +275 -0
- data/.github/skills/review-cross-command-consistency/SKILL.md +139 -0
- data/.github/skills/reviewing-skills/SKILL.md +189 -0
- data/.github/skills/rspec-unit-testing-standards/SKILL.md +639 -0
- data/.github/skills/tdd-refactor-step/SKILL.md +236 -0
- data/.github/skills/test-debugging/SKILL.md +160 -0
- data/.github/skills/yard-documentation/SKILL.md +793 -0
- data/.github/workflows/continuous_integration.yml +3 -2
- data/.github/workflows/enforce_conventional_commits.yml +1 -1
- data/.github/workflows/experimental_continuous_integration.yml +2 -2
- data/.github/workflows/release.yml +3 -4
- data/.gitignore +8 -0
- data/.husky/pre-commit +13 -0
- data/.release-please-manifest.json +1 -1
- data/.rspec +3 -0
- data/.rubocop.yml +7 -3
- data/.rubocop_todo.yml +23 -5
- data/.yardopts +1 -0
- data/CHANGELOG.md +0 -40
- data/CONTRIBUTING.md +694 -53
- data/README.md +17 -5
- data/Rakefile +61 -9
- data/commitlint.test +4 -0
- data/git.gemspec +14 -8
- data/lib/git/args_builder.rb +0 -8
- data/lib/git/base.rb +486 -410
- data/lib/git/branch.rb +380 -43
- data/lib/git/branch_delete_failure.rb +31 -0
- data/lib/git/branch_delete_result.rb +63 -0
- data/lib/git/branch_info.rb +178 -0
- data/lib/git/branches.rb +130 -24
- data/lib/git/command_line/base.rb +245 -0
- data/lib/git/command_line/capturing.rb +249 -0
- data/lib/git/command_line/result.rb +96 -0
- data/lib/git/command_line/streaming.rb +194 -0
- data/lib/git/command_line.rb +43 -322
- data/lib/git/command_line_result.rb +4 -88
- data/lib/git/commands/add.rb +131 -0
- data/lib/git/commands/am/abort.rb +43 -0
- data/lib/git/commands/am/apply.rb +252 -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 +47 -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 +237 -0
- data/lib/git/commands/archive/list_formats.rb +46 -0
- data/lib/git/commands/archive.rb +140 -0
- data/lib/git/commands/arguments.rb +3510 -0
- data/lib/git/commands/base.rb +403 -0
- data/lib/git/commands/branch/copy.rb +94 -0
- data/lib/git/commands/branch/create.rb +173 -0
- data/lib/git/commands/branch/delete.rb +80 -0
- data/lib/git/commands/branch/list.rb +162 -0
- data/lib/git/commands/branch/move.rb +94 -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 +57 -0
- data/lib/git/commands/branch.rb +34 -0
- data/lib/git/commands/cat_file/batch.rb +364 -0
- data/lib/git/commands/cat_file/filtered.rb +105 -0
- data/lib/git/commands/cat_file/raw.rb +210 -0
- data/lib/git/commands/cat_file.rb +49 -0
- data/lib/git/commands/checkout/branch.rb +151 -0
- data/lib/git/commands/checkout/files.rb +115 -0
- data/lib/git/commands/checkout.rb +38 -0
- data/lib/git/commands/checkout_index.rb +105 -0
- data/lib/git/commands/clean.rb +100 -0
- data/lib/git/commands/clone.rb +240 -0
- data/lib/git/commands/commit.rb +272 -0
- data/lib/git/commands/commit_tree.rb +100 -0
- data/lib/git/commands/config_option_syntax/add.rb +83 -0
- data/lib/git/commands/config_option_syntax/get.rb +117 -0
- data/lib/git/commands/config_option_syntax/get_all.rb +115 -0
- data/lib/git/commands/config_option_syntax/get_color.rb +91 -0
- data/lib/git/commands/config_option_syntax/get_color_bool.rb +93 -0
- data/lib/git/commands/config_option_syntax/get_regexp.rb +115 -0
- data/lib/git/commands/config_option_syntax/get_urlmatch.rb +102 -0
- data/lib/git/commands/config_option_syntax/list.rb +107 -0
- data/lib/git/commands/config_option_syntax/remove_section.rb +74 -0
- data/lib/git/commands/config_option_syntax/rename_section.rb +78 -0
- data/lib/git/commands/config_option_syntax/replace_all.rb +104 -0
- data/lib/git/commands/config_option_syntax/set.rb +114 -0
- data/lib/git/commands/config_option_syntax/unset.rb +89 -0
- data/lib/git/commands/config_option_syntax/unset_all.rb +89 -0
- data/lib/git/commands/config_option_syntax.rb +56 -0
- data/lib/git/commands/describe.rb +155 -0
- data/lib/git/commands/diff.rb +656 -0
- data/lib/git/commands/diff_files.rb +518 -0
- data/lib/git/commands/diff_index.rb +496 -0
- data/lib/git/commands/fetch.rb +352 -0
- data/lib/git/commands/fsck.rb +136 -0
- data/lib/git/commands/gc.rb +132 -0
- data/lib/git/commands/grep.rb +338 -0
- data/lib/git/commands/init.rb +99 -0
- data/lib/git/commands/log.rb +632 -0
- data/lib/git/commands/ls_files.rb +191 -0
- data/lib/git/commands/ls_remote.rb +155 -0
- data/lib/git/commands/ls_tree.rb +131 -0
- data/lib/git/commands/maintenance/register.rb +75 -0
- data/lib/git/commands/maintenance/run.rb +104 -0
- data/lib/git/commands/maintenance/start.rb +66 -0
- data/lib/git/commands/maintenance/stop.rb +55 -0
- data/lib/git/commands/maintenance/unregister.rb +79 -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 +245 -0
- data/lib/git/commands/merge.rb +28 -0
- data/lib/git/commands/merge_base.rb +86 -0
- data/lib/git/commands/mv.rb +77 -0
- data/lib/git/commands/name_rev.rb +114 -0
- data/lib/git/commands/pull.rb +377 -0
- data/lib/git/commands/push.rb +246 -0
- data/lib/git/commands/read_tree.rb +149 -0
- data/lib/git/commands/remote/add.rb +91 -0
- data/lib/git/commands/remote/get_url.rb +66 -0
- data/lib/git/commands/remote/list.rb +54 -0
- data/lib/git/commands/remote/prune.rb +61 -0
- data/lib/git/commands/remote/remove.rb +52 -0
- data/lib/git/commands/remote/rename.rb +69 -0
- data/lib/git/commands/remote/set_branches.rb +63 -0
- data/lib/git/commands/remote/set_head.rb +82 -0
- data/lib/git/commands/remote/set_url.rb +71 -0
- data/lib/git/commands/remote/set_url_add.rb +61 -0
- data/lib/git/commands/remote/set_url_delete.rb +64 -0
- data/lib/git/commands/remote/show.rb +71 -0
- data/lib/git/commands/remote/update.rb +72 -0
- data/lib/git/commands/remote.rb +42 -0
- data/lib/git/commands/repack.rb +277 -0
- data/lib/git/commands/reset.rb +147 -0
- data/lib/git/commands/rev_parse.rb +297 -0
- data/lib/git/commands/revert/abort.rb +45 -0
- data/lib/git/commands/revert/continue.rb +57 -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 +153 -0
- data/lib/git/commands/revert.rb +29 -0
- data/lib/git/commands/rm.rb +114 -0
- data/lib/git/commands/show.rb +632 -0
- data/lib/git/commands/show_ref/exclude_existing.rb +120 -0
- data/lib/git/commands/show_ref/exists.rb +78 -0
- data/lib/git/commands/show_ref/list.rb +145 -0
- data/lib/git/commands/show_ref/verify.rb +120 -0
- data/lib/git/commands/show_ref.rb +42 -0
- data/lib/git/commands/stash/apply.rb +75 -0
- data/lib/git/commands/stash/branch.rb +65 -0
- data/lib/git/commands/stash/clear.rb +41 -0
- data/lib/git/commands/stash/create.rb +58 -0
- data/lib/git/commands/stash/drop.rb +67 -0
- data/lib/git/commands/stash/list.rb +39 -0
- data/lib/git/commands/stash/pop.rb +78 -0
- data/lib/git/commands/stash/push.rb +103 -0
- data/lib/git/commands/stash/show.rb +149 -0
- data/lib/git/commands/stash/store.rb +63 -0
- data/lib/git/commands/stash.rb +38 -0
- data/lib/git/commands/status.rb +169 -0
- data/lib/git/commands/symbolic_ref/delete.rb +68 -0
- data/lib/git/commands/symbolic_ref/read.rb +95 -0
- data/lib/git/commands/symbolic_ref/update.rb +76 -0
- data/lib/git/commands/symbolic_ref.rb +38 -0
- data/lib/git/commands/tag/create.rb +139 -0
- data/lib/git/commands/tag/delete.rb +55 -0
- data/lib/git/commands/tag/list.rb +143 -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 +140 -0
- data/lib/git/commands/update_ref/delete.rb +92 -0
- data/lib/git/commands/update_ref/update.rb +106 -0
- data/lib/git/commands/update_ref.rb +42 -0
- data/lib/git/commands/version.rb +52 -0
- data/lib/git/commands/worktree/add.rb +140 -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 +89 -0
- data/lib/git/detached_head_info.rb +54 -0
- data/lib/git/diff.rb +297 -7
- data/lib/git/diff_file_numstat_info.rb +29 -0
- data/lib/git/diff_file_patch_info.rb +134 -0
- data/lib/git/diff_file_raw_info.rb +127 -0
- data/lib/git/diff_info.rb +169 -0
- data/lib/git/diff_path_status.rb +78 -19
- data/lib/git/diff_result.rb +32 -0
- data/lib/git/diff_stats.rb +59 -14
- data/lib/git/dirstat_info.rb +86 -0
- data/lib/git/errors.rb +65 -2
- data/lib/git/execution_context/global.rb +56 -0
- data/lib/git/execution_context/repository.rb +147 -0
- data/lib/git/execution_context.rb +482 -0
- data/lib/git/file_ref.rb +74 -0
- data/lib/git/fsck_object.rb +9 -9
- data/lib/git/fsck_result.rb +1 -1
- data/lib/git/lib.rb +1606 -1028
- data/lib/git/log.rb +15 -2
- data/lib/git/object.rb +92 -22
- data/lib/git/parsers/branch.rb +224 -0
- data/lib/git/parsers/cat_file.rb +111 -0
- data/lib/git/parsers/diff.rb +585 -0
- data/lib/git/parsers/fsck.rb +133 -0
- data/lib/git/parsers/grep.rb +42 -0
- data/lib/git/parsers/ls_tree.rb +58 -0
- data/lib/git/parsers/stash.rb +208 -0
- data/lib/git/parsers/tag.rb +257 -0
- data/lib/git/remote.rb +133 -9
- data/lib/git/repository/branching.rb +572 -0
- data/lib/git/repository/committing.rb +191 -0
- data/lib/git/repository/configuring.rb +156 -0
- data/lib/git/repository/diffing.rb +775 -0
- data/lib/git/repository/inspecting.rb +153 -0
- data/lib/git/repository/logging.rb +247 -0
- data/lib/git/repository/merging.rb +295 -0
- data/lib/git/repository/object_operations.rb +1101 -0
- data/lib/git/repository/path_resolver.rb +207 -0
- data/lib/git/repository/remote_operations.rb +753 -0
- data/lib/git/repository/shared_private.rb +51 -0
- data/lib/git/repository/staging.rb +390 -0
- data/lib/git/repository/stashing.rb +107 -0
- data/lib/git/repository/status_operations.rb +180 -0
- data/lib/git/repository/worktree_operations.rb +159 -0
- data/lib/git/repository.rb +264 -1
- data/lib/git/stash.rb +85 -4
- data/lib/git/stash_info.rb +104 -0
- data/lib/git/stashes.rb +130 -13
- data/lib/git/status.rb +224 -18
- data/lib/git/tag_delete_failure.rb +31 -0
- data/lib/git/tag_delete_result.rb +63 -0
- data/lib/git/tag_info.rb +105 -0
- data/lib/git/version.rb +109 -2
- data/lib/git/version_constraint.rb +81 -0
- data/lib/git/worktree.rb +120 -5
- data/lib/git/worktrees.rb +107 -7
- data/lib/git.rb +114 -18
- data/redesign/1_architecture_existing.md +54 -18
- data/redesign/2_architecture_redesign.md +365 -46
- data/redesign/3_architecture_implementation.md +1451 -54
- data/tasks/gem_tasks.rake +4 -0
- data/tasks/npm_tasks.rake +7 -0
- data/tasks/rspec.rake +48 -0
- data/tasks/test.rake +13 -1
- data/tasks/yard.rake +34 -7
- metadata +349 -20
- data/lib/git/index.rb +0 -6
- data/lib/git/path.rb +0 -38
- data/lib/git/working_directory.rb +0 -6
- /data/{release-please-config.json → .release-please-config.json} +0 -0
|
@@ -0,0 +1,178 @@
|
|
|
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
|
+
# @note This regex is similar to Git::Branch::BRANCH_NAME_REGEXP but uses \A/\z anchors
|
|
11
|
+
# instead of ^/$ for stricter matching. As part of the architectural redesign,
|
|
12
|
+
# Git::Branch will eventually be refactored to use BranchInfo internally, at which
|
|
13
|
+
# point this will become the single source of truth for branch name parsing.
|
|
14
|
+
#
|
|
15
|
+
# @note This regex assumes remote names do not contain '/'. If a remote name
|
|
16
|
+
# contains '/', parsing will be incorrect. For example, 'remotes/team/upstream/main'
|
|
17
|
+
# would parse as remote_name='team' instead of 'team/upstream'. This is an inherent
|
|
18
|
+
# ambiguity in git refnames that can only be resolved with knowledge of configured
|
|
19
|
+
# remotes. See: https://github.com/ruby-git/ruby-git/issues/919
|
|
20
|
+
#
|
|
21
|
+
# @example
|
|
22
|
+
# 'main' => { remote_name: nil, branch_name: 'main' }
|
|
23
|
+
# 'remotes/origin/main' => { remote_name: 'origin', branch_name: 'main' }
|
|
24
|
+
# 'feature/foo' => { remote_name: nil, branch_name: 'feature/foo' }
|
|
25
|
+
# 'remotes/origin/feature/bar' => { remote_name: 'origin', branch_name: 'feature/bar' }
|
|
26
|
+
#
|
|
27
|
+
# @api private
|
|
28
|
+
BRANCH_REFNAME_REGEXP = %r{
|
|
29
|
+
\A # start of string
|
|
30
|
+
(?:(?:refs/)?remotes/(?<remote_name>[^/]+)/)? # optional 'refs?/remotes/<remote_name>/'
|
|
31
|
+
(?<branch_name>.+) # branch name (everything else)
|
|
32
|
+
\z # end of string
|
|
33
|
+
}x
|
|
34
|
+
|
|
35
|
+
# Value object representing branch metadata from git branch output
|
|
36
|
+
#
|
|
37
|
+
# This is a lightweight, immutable data structure returned by branch listing
|
|
38
|
+
# commands. It contains only the data parsed from git output without any
|
|
39
|
+
# repository context or operations.
|
|
40
|
+
#
|
|
41
|
+
# @example Creating from git branch output
|
|
42
|
+
# info = Git::BranchInfo.new(
|
|
43
|
+
# refname: 'main',
|
|
44
|
+
# target_oid: 'abc123def456789012345678901234567890abcd',
|
|
45
|
+
# current: true,
|
|
46
|
+
# worktree: false,
|
|
47
|
+
# symref: nil,
|
|
48
|
+
# upstream: nil
|
|
49
|
+
# )
|
|
50
|
+
# info.current? #=> true
|
|
51
|
+
# info.remote? #=> false
|
|
52
|
+
# info.short_name #=> 'main'
|
|
53
|
+
#
|
|
54
|
+
# @example Remote branch
|
|
55
|
+
# info = Git::BranchInfo.new(
|
|
56
|
+
# refname: 'remotes/origin/main',
|
|
57
|
+
# target_oid: 'abc123def456789012345678901234567890abcd',
|
|
58
|
+
# current: false,
|
|
59
|
+
# worktree: false,
|
|
60
|
+
# symref: nil,
|
|
61
|
+
# upstream: nil
|
|
62
|
+
# )
|
|
63
|
+
# info.remote? #=> true
|
|
64
|
+
# info.remote_name #=> 'origin'
|
|
65
|
+
# info.short_name #=> 'main'
|
|
66
|
+
#
|
|
67
|
+
# @example Local branch with upstream tracking
|
|
68
|
+
# upstream_info = Git::BranchInfo.new(
|
|
69
|
+
# refname: 'remotes/origin/main',
|
|
70
|
+
# target_oid: 'abc123def456789012345678901234567890abcd',
|
|
71
|
+
# current: false,
|
|
72
|
+
# worktree: false,
|
|
73
|
+
# symref: nil,
|
|
74
|
+
# upstream: nil
|
|
75
|
+
# )
|
|
76
|
+
# info = Git::BranchInfo.new(
|
|
77
|
+
# refname: 'main',
|
|
78
|
+
# target_oid: 'abc123def456789012345678901234567890abcd',
|
|
79
|
+
# current: true,
|
|
80
|
+
# worktree: false,
|
|
81
|
+
# symref: nil,
|
|
82
|
+
# upstream: upstream_info
|
|
83
|
+
# )
|
|
84
|
+
# info.upstream.remote_name #=> 'origin'
|
|
85
|
+
#
|
|
86
|
+
# @see Git::Branch for the full-featured branch object with operations
|
|
87
|
+
#
|
|
88
|
+
# @see Git::Commands::Branch::List for the command that produces these
|
|
89
|
+
#
|
|
90
|
+
# @api public
|
|
91
|
+
#
|
|
92
|
+
# @!attribute [r] refname
|
|
93
|
+
#
|
|
94
|
+
# The full reference name of the branch
|
|
95
|
+
#
|
|
96
|
+
# @return [String] the branch refname (e.g., 'main', 'remotes/origin/main')
|
|
97
|
+
#
|
|
98
|
+
# @!attribute [r] target_oid
|
|
99
|
+
#
|
|
100
|
+
# The commit object ID (SHA) that this branch points to
|
|
101
|
+
#
|
|
102
|
+
# @return [String, nil] the full 40-character object ID, or nil if unavailable
|
|
103
|
+
#
|
|
104
|
+
# @!attribute [r] current
|
|
105
|
+
#
|
|
106
|
+
# Whether this branch is currently checked out in the current worktree
|
|
107
|
+
#
|
|
108
|
+
# @return [Boolean] true if this is the current branch
|
|
109
|
+
#
|
|
110
|
+
# @!attribute [r] worktree
|
|
111
|
+
#
|
|
112
|
+
# Whether this branch is checked out in another linked worktree
|
|
113
|
+
#
|
|
114
|
+
# @return [Boolean] true if checked out in a different worktree
|
|
115
|
+
#
|
|
116
|
+
# @!attribute [r] symref
|
|
117
|
+
#
|
|
118
|
+
# The target reference if this is a symbolic reference
|
|
119
|
+
#
|
|
120
|
+
# @return [String, nil] the target ref (e.g., 'refs/heads/main'), or nil if not a symref
|
|
121
|
+
#
|
|
122
|
+
# @!attribute [r] upstream
|
|
123
|
+
#
|
|
124
|
+
# The configured upstream/tracking branch
|
|
125
|
+
#
|
|
126
|
+
# @return [Git::BranchInfo, nil] the upstream branch info, or nil if no upstream is configured
|
|
127
|
+
#
|
|
128
|
+
# @note Remote-tracking branches (e.g., 'origin/main') have upstream: nil
|
|
129
|
+
#
|
|
130
|
+
# @note When upstream exists but the remote-tracking branch hasn't been fetched,
|
|
131
|
+
# the upstream's target_oid may be nil
|
|
132
|
+
#
|
|
133
|
+
BranchInfo = Data.define(:refname, :target_oid, :current, :worktree, :symref, :upstream) do
|
|
134
|
+
# @return [Boolean] always false for BranchInfo (see DetachedHeadInfo for detached state)
|
|
135
|
+
def detached? = false
|
|
136
|
+
|
|
137
|
+
# @return [Boolean] true if this is an unborn branch (no commits yet)
|
|
138
|
+
def unborn? = target_oid.nil?
|
|
139
|
+
|
|
140
|
+
# @return [Boolean] true if this is the currently checked out branch
|
|
141
|
+
def current? = current
|
|
142
|
+
|
|
143
|
+
# @return [Boolean] true if this branch is checked out in another worktree
|
|
144
|
+
def worktree? = worktree
|
|
145
|
+
|
|
146
|
+
# @return [Boolean] true if this is a symbolic reference
|
|
147
|
+
def symref? = !symref.nil?
|
|
148
|
+
|
|
149
|
+
# @return [Boolean] true if this is a remote-tracking branch
|
|
150
|
+
def remote? = !remote_name.nil?
|
|
151
|
+
|
|
152
|
+
# @return [String, nil] the name of the remote (e.g., 'origin'), or nil for local branches
|
|
153
|
+
def remote_name
|
|
154
|
+
parse_refname[:remote_name]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# @return [String] the branch name without remote prefix (e.g., 'main' or 'feature/foo')
|
|
158
|
+
def short_name
|
|
159
|
+
parse_refname[:branch_name]
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# @return [String] string representation (the full refname)
|
|
163
|
+
def to_s = refname
|
|
164
|
+
|
|
165
|
+
private
|
|
166
|
+
|
|
167
|
+
# Parse the refname and return match data
|
|
168
|
+
#
|
|
169
|
+
# The regex is guaranteed to match any non-empty string due to the `.+` pattern,
|
|
170
|
+
# so we don't need nil checking. If refname is empty/nil, this would fail at
|
|
171
|
+
# object creation time since refname is a required attribute.
|
|
172
|
+
#
|
|
173
|
+
# @return [MatchData] the match result
|
|
174
|
+
def parse_refname
|
|
175
|
+
refname.match(Git::BRANCH_REFNAME_REGEXP)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
data/lib/git/branches.rb
CHANGED
|
@@ -1,68 +1,174 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'git/base'
|
|
4
|
+
|
|
3
5
|
module Git
|
|
4
|
-
#
|
|
6
|
+
# Collection of all Git branches in a repository
|
|
7
|
+
#
|
|
8
|
+
# Wraps both local and remote-tracking branches and provides filtering,
|
|
9
|
+
# enumeration, and name-based lookup.
|
|
10
|
+
#
|
|
11
|
+
# @example Enumerate all branches
|
|
12
|
+
# branches = repo.branches
|
|
13
|
+
# branches.each { |b| puts b.name }
|
|
14
|
+
#
|
|
15
|
+
# @api public
|
|
16
|
+
#
|
|
5
17
|
class Branches
|
|
6
18
|
include Enumerable
|
|
7
19
|
|
|
20
|
+
# Creates a new Branches collection populated from the given repository
|
|
21
|
+
#
|
|
22
|
+
# @param base [Git::Base, Git::Repository] the repository to enumerate
|
|
23
|
+
# branches from
|
|
24
|
+
#
|
|
25
|
+
# @return [void]
|
|
26
|
+
#
|
|
27
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
28
|
+
#
|
|
8
29
|
def initialize(base)
|
|
9
30
|
@branches = {}
|
|
31
|
+
@lookup = {}
|
|
10
32
|
|
|
11
33
|
@base = base
|
|
12
34
|
|
|
13
|
-
|
|
14
|
-
|
|
35
|
+
branch_repository.branches_all.each do |branch_info|
|
|
36
|
+
branch = Git::Branch.new(base, branch_info)
|
|
37
|
+
|
|
38
|
+
@branches[branch_info.refname] = branch
|
|
39
|
+
index_branch_lookup(branch, refname: branch_info.refname)
|
|
15
40
|
end
|
|
16
41
|
end
|
|
17
42
|
|
|
43
|
+
# Returns all local (non-remote-tracking) branches
|
|
44
|
+
#
|
|
45
|
+
# @example List local branch names
|
|
46
|
+
# repo.branches.local.map(&:name)
|
|
47
|
+
#
|
|
48
|
+
# @return [Array<Git::Branch>] the local branches
|
|
49
|
+
#
|
|
18
50
|
def local
|
|
19
51
|
reject(&:remote)
|
|
20
52
|
end
|
|
21
53
|
|
|
54
|
+
# Returns all remote-tracking branches
|
|
55
|
+
#
|
|
56
|
+
# @example List remote branch names
|
|
57
|
+
# repo.branches.remote.map(&:name)
|
|
58
|
+
#
|
|
59
|
+
# @return [Array<Git::Branch>] the remote-tracking branches
|
|
60
|
+
#
|
|
22
61
|
def remote
|
|
23
62
|
self.select(&:remote)
|
|
24
63
|
end
|
|
25
64
|
|
|
26
|
-
#
|
|
27
|
-
|
|
65
|
+
# Returns the number of branches in the collection
|
|
66
|
+
#
|
|
67
|
+
# @example Count all branches
|
|
68
|
+
# repo.branches.size # => 3
|
|
69
|
+
#
|
|
70
|
+
# @return [Integer] the total number of branches
|
|
71
|
+
#
|
|
28
72
|
def size
|
|
29
73
|
@branches.size
|
|
30
74
|
end
|
|
31
75
|
|
|
76
|
+
# Iterates over every branch in the collection
|
|
77
|
+
#
|
|
78
|
+
# @overload each
|
|
79
|
+
#
|
|
80
|
+
# @example Get an enumerator over all branches
|
|
81
|
+
# enum = repo.branches.each
|
|
82
|
+
#
|
|
83
|
+
# @return [Enumerator<Git::Branch>] an enumerator over all branches
|
|
84
|
+
#
|
|
85
|
+
# @overload each(&block)
|
|
86
|
+
#
|
|
87
|
+
# @example Print every branch name
|
|
88
|
+
# repo.branches.each { |b| puts b.name }
|
|
89
|
+
#
|
|
90
|
+
# @return [Array<Git::Branch>] the full list of branches
|
|
91
|
+
#
|
|
92
|
+
# @yield [branch] passes each branch to the block
|
|
93
|
+
#
|
|
94
|
+
# @yieldparam branch [Git::Branch] a branch in the repository
|
|
95
|
+
#
|
|
96
|
+
# @yieldreturn [void]
|
|
97
|
+
#
|
|
32
98
|
def each(&)
|
|
33
99
|
@branches.values.each(&)
|
|
34
100
|
end
|
|
35
101
|
|
|
36
|
-
# Returns the
|
|
102
|
+
# Returns the branch with the given name
|
|
103
|
+
#
|
|
104
|
+
# Supports short names (`'main'`), remote-qualified names
|
|
105
|
+
# (`'working/master'`), and full refspec names
|
|
106
|
+
# (`'remotes/working/master'`).
|
|
37
107
|
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
# master
|
|
41
|
-
# remotes/working/master
|
|
108
|
+
# @example Look up a branch by short name
|
|
109
|
+
# repo.branches['main']
|
|
42
110
|
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
111
|
+
# @example Look up a remote-tracking branch
|
|
112
|
+
# repo.branches['working/master']
|
|
113
|
+
#
|
|
114
|
+
# @param branch_name [#to_s] the name of the branch to retrieve
|
|
115
|
+
#
|
|
116
|
+
# @return [Git::Branch, nil] the matching branch, or `nil` if not found
|
|
46
117
|
#
|
|
47
|
-
# @param [#to_s] branch_name the target branch name.
|
|
48
|
-
# @return [Git::Branch] the target branch.
|
|
49
118
|
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
|
|
55
|
-
# name (even if is not a real remote branch).
|
|
56
|
-
branches[branch.full.sub('remotes/', '')] ||= branch if branch.full =~ %r{^remotes/.+}
|
|
57
|
-
end[branch_name.to_s]
|
|
119
|
+
@lookup[branch_name.to_s]
|
|
58
120
|
end
|
|
59
121
|
|
|
122
|
+
# Returns a string listing all branches, prefixed with `*` for the current branch
|
|
123
|
+
#
|
|
124
|
+
# @example Display all branches
|
|
125
|
+
# puts repo.branches.to_s
|
|
126
|
+
#
|
|
127
|
+
# @return [String] a formatted branch listing
|
|
128
|
+
#
|
|
60
129
|
def to_s
|
|
61
|
-
out = ''
|
|
130
|
+
out = +''
|
|
62
131
|
@branches.each_value do |b|
|
|
63
132
|
out << (b.current ? '* ' : ' ') << b.to_s << "\n"
|
|
64
133
|
end
|
|
65
134
|
out
|
|
66
135
|
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
# Resolves the {Git::Repository} for this collection of branches
|
|
140
|
+
#
|
|
141
|
+
# Accepts either a {Git::Repository} (new form) or a {Git::Base} (legacy).
|
|
142
|
+
# The `is_a?(Git::Base)` guard will be removed when {Git::Base} is deleted
|
|
143
|
+
# in Phase 4.
|
|
144
|
+
#
|
|
145
|
+
# @return [Git::Repository] the repository used to enumerate branches
|
|
146
|
+
#
|
|
147
|
+
# @api private
|
|
148
|
+
#
|
|
149
|
+
def branch_repository
|
|
150
|
+
@base.is_a?(Git::Base) ? @base.facade_repository : @base
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Indexes all supported lookup keys for a branch without mutating
|
|
154
|
+
# the canonical `@branches` collection used by enumeration
|
|
155
|
+
#
|
|
156
|
+
# @param branch [Git::Branch] the branch to index
|
|
157
|
+
#
|
|
158
|
+
# @param refname [String] the full refname key to use for primary lookup
|
|
159
|
+
#
|
|
160
|
+
# @return [void]
|
|
161
|
+
#
|
|
162
|
+
# @api private
|
|
163
|
+
#
|
|
164
|
+
def index_branch_lookup(branch, refname:)
|
|
165
|
+
@lookup[refname] ||= branch
|
|
166
|
+
@lookup[branch.full] ||= branch
|
|
167
|
+
|
|
168
|
+
return unless branch.full.start_with?('remotes/')
|
|
169
|
+
|
|
170
|
+
# Mirror git compatibility: allow omitting a leading "remotes/".
|
|
171
|
+
@lookup[branch.full.delete_prefix('remotes/')] ||= branch
|
|
172
|
+
end
|
|
67
173
|
end
|
|
68
174
|
end
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'git/command_line'
|
|
4
|
+
require 'git/errors'
|
|
5
|
+
|
|
6
|
+
module Git
|
|
7
|
+
module CommandLine
|
|
8
|
+
# Abstract base class for git command-line execution strategies
|
|
9
|
+
#
|
|
10
|
+
# Concrete subclasses must implement {#run} to execute a git command and
|
|
11
|
+
# return a {Git::CommandLine::Result}. Two implementations are provided:
|
|
12
|
+
#
|
|
13
|
+
# * {Git::CommandLine::Capturing} — buffers stdout and stderr in memory
|
|
14
|
+
# * {Git::CommandLine::Streaming} — streams stdout to a caller-supplied IO
|
|
15
|
+
#
|
|
16
|
+
# @example Instantiate a concrete subclass
|
|
17
|
+
# env = { 'GIT_DIR' => '/path/to/git/dir' }
|
|
18
|
+
# binary_path = '/usr/bin/git'
|
|
19
|
+
# global_opts = %w[--git-dir /path/to/git/dir]
|
|
20
|
+
# logger = Logger.new($stdout)
|
|
21
|
+
# cli = Git::CommandLine::Capturing.new(env, binary_path, global_opts, logger)
|
|
22
|
+
# cli.run('version') #=> #<Git::CommandLine::Result ...>
|
|
23
|
+
#
|
|
24
|
+
# @abstract Subclass and implement {#run}
|
|
25
|
+
#
|
|
26
|
+
# @api public
|
|
27
|
+
#
|
|
28
|
+
class Base
|
|
29
|
+
# Create a Base (or subclass) object
|
|
30
|
+
#
|
|
31
|
+
# @param env [Hash{String => String, nil}] environment variables to set or
|
|
32
|
+
# unset. String values set the variable; `nil` values unset it.
|
|
33
|
+
#
|
|
34
|
+
# @param binary_path [String] the path to the git binary
|
|
35
|
+
#
|
|
36
|
+
# @param global_opts [Array<String>] global options to pass to git
|
|
37
|
+
#
|
|
38
|
+
# @param logger [Logger] the logger to use
|
|
39
|
+
#
|
|
40
|
+
def initialize(env, binary_path, global_opts, logger)
|
|
41
|
+
@env = env
|
|
42
|
+
@binary_path = binary_path
|
|
43
|
+
@global_opts = global_opts
|
|
44
|
+
@logger = logger
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Execute a git command and return the result
|
|
48
|
+
#
|
|
49
|
+
# Concrete subclasses must override this method.
|
|
50
|
+
#
|
|
51
|
+
# @raise [NotImplementedError] always — must be implemented by subclasses
|
|
52
|
+
#
|
|
53
|
+
def run(*)
|
|
54
|
+
raise NotImplementedError, "#{self.class}#run is not implemented"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @attribute [r] env
|
|
58
|
+
#
|
|
59
|
+
# Variables to set (or unset) in the git command's environment
|
|
60
|
+
#
|
|
61
|
+
# @example
|
|
62
|
+
# env = { 'GIT_DIR' => '/path/to/git/dir' }
|
|
63
|
+
# cli = Git::CommandLine::Capturing.new(env, '/usr/bin/git', [], Logger.new(nil))
|
|
64
|
+
# cli.env #=> { 'GIT_DIR' => '/path/to/git/dir' }
|
|
65
|
+
#
|
|
66
|
+
# @return [Hash{String => String, nil}]
|
|
67
|
+
#
|
|
68
|
+
# @see https://ruby-doc.org/3.2.1/Process.html#method-c-spawn Process.spawn
|
|
69
|
+
# for details on how to set environment variables using the `env` parameter
|
|
70
|
+
#
|
|
71
|
+
attr_reader :env
|
|
72
|
+
|
|
73
|
+
# @attribute [r] binary_path
|
|
74
|
+
#
|
|
75
|
+
# The path to the command line binary to run
|
|
76
|
+
#
|
|
77
|
+
# @example
|
|
78
|
+
# cli = Git::CommandLine::Capturing.new({}, '/usr/bin/git', [], Logger.new(nil))
|
|
79
|
+
# cli.binary_path #=> '/usr/bin/git'
|
|
80
|
+
#
|
|
81
|
+
# @return [String]
|
|
82
|
+
#
|
|
83
|
+
attr_reader :binary_path
|
|
84
|
+
|
|
85
|
+
# @attribute [r] global_opts
|
|
86
|
+
#
|
|
87
|
+
# The global options to pass to git
|
|
88
|
+
#
|
|
89
|
+
# These are options that are passed to git before the command name and
|
|
90
|
+
# arguments. For example, in `git --git-dir /path/to/git/dir version`, the
|
|
91
|
+
# global options are %w[--git-dir /path/to/git/dir].
|
|
92
|
+
#
|
|
93
|
+
# @example
|
|
94
|
+
# global_opts = %w[--git-dir /path/to/git/dir]
|
|
95
|
+
# cli = Git::CommandLine::Capturing.new({}, '/usr/bin/git', global_opts, Logger.new(nil))
|
|
96
|
+
# cli.global_opts #=> %w[--git-dir /path/to/git/dir]
|
|
97
|
+
#
|
|
98
|
+
# @return [Array<String>]
|
|
99
|
+
#
|
|
100
|
+
attr_reader :global_opts
|
|
101
|
+
|
|
102
|
+
# @attribute [r] logger
|
|
103
|
+
#
|
|
104
|
+
# The logger to use for logging git commands and results
|
|
105
|
+
#
|
|
106
|
+
# @example
|
|
107
|
+
# logger = Logger.new(nil)
|
|
108
|
+
# cli = Git::CommandLine::Capturing.new({}, '/usr/bin/git', [], logger)
|
|
109
|
+
# cli.logger == logger #=> true
|
|
110
|
+
#
|
|
111
|
+
# @return [Logger]
|
|
112
|
+
#
|
|
113
|
+
attr_reader :logger
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
# Merge caller-supplied options into `defaults` and raise if any unknown keys are present
|
|
118
|
+
#
|
|
119
|
+
# @param defaults [Hash] the allowed keys and their default values (e.g. RUN_OPTION_DEFAULTS)
|
|
120
|
+
#
|
|
121
|
+
# @param options_hash [Hash] caller-supplied options
|
|
122
|
+
#
|
|
123
|
+
# @return [Hash] defaults with any supplied values overridden
|
|
124
|
+
#
|
|
125
|
+
# @raise [ArgumentError] if options_hash contains keys not present in defaults
|
|
126
|
+
#
|
|
127
|
+
# @api private
|
|
128
|
+
#
|
|
129
|
+
def merge_and_validate_options(defaults, options_hash)
|
|
130
|
+
merged = defaults.merge(options_hash)
|
|
131
|
+
extra = merged.keys - defaults.keys
|
|
132
|
+
raise ArgumentError, "Unknown options: #{extra.join(', ')}" if extra.any?
|
|
133
|
+
|
|
134
|
+
merged
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Merge the instance-level env with any per-call overrides in options_hash[:env]
|
|
138
|
+
#
|
|
139
|
+
# @param options_hash [Hash] options that may include an :env override
|
|
140
|
+
#
|
|
141
|
+
# @return [Hash{String => String, nil}]
|
|
142
|
+
#
|
|
143
|
+
# @api private
|
|
144
|
+
#
|
|
145
|
+
def merged_env(options_hash)
|
|
146
|
+
env.merge(options_hash[:env] || {})
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Yield to a block that calls ProcessExecuter and translate any ProcessExecuter
|
|
150
|
+
# errors to their ruby-git equivalents
|
|
151
|
+
#
|
|
152
|
+
# @raise [ArgumentError] in place of ProcessExecuter::ArgumentError
|
|
153
|
+
#
|
|
154
|
+
# @raise [Git::Error] in place of ProcessExecuter::SpawnError (binary not found or
|
|
155
|
+
# failed to launch)
|
|
156
|
+
#
|
|
157
|
+
# @raise [Git::ProcessIOError] in place of ProcessExecuter::ProcessIOError
|
|
158
|
+
#
|
|
159
|
+
# @return [Object] the return value of the block
|
|
160
|
+
#
|
|
161
|
+
# @api private
|
|
162
|
+
#
|
|
163
|
+
def run_process_executer
|
|
164
|
+
yield
|
|
165
|
+
rescue ProcessExecuter::ArgumentError => e
|
|
166
|
+
raise ::ArgumentError, e.message
|
|
167
|
+
rescue ProcessExecuter::SpawnError => e
|
|
168
|
+
raise Git::Error, e.message, cause: e.cause
|
|
169
|
+
rescue ProcessExecuter::ProcessIOError => e
|
|
170
|
+
raise Git::ProcessIOError, e.message, cause: e.cause
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Build the git command line from the available sources to send to `Process.spawn`
|
|
174
|
+
#
|
|
175
|
+
# @param args [Array<String>] command-line arguments to append after global options
|
|
176
|
+
#
|
|
177
|
+
# @return [Array<String>]
|
|
178
|
+
#
|
|
179
|
+
# @raise [ArgumentError] if any element of args is itself an Array
|
|
180
|
+
#
|
|
181
|
+
# @api private
|
|
182
|
+
#
|
|
183
|
+
def build_git_cmd(args)
|
|
184
|
+
raise ArgumentError, 'The args array can not contain an array' if args.any?(Array)
|
|
185
|
+
|
|
186
|
+
[binary_path, *global_opts, *args].map(&:to_s)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Log the result of a git command at info/debug level
|
|
190
|
+
#
|
|
191
|
+
# @param result [ProcessExecuter::Result] the raw process result
|
|
192
|
+
#
|
|
193
|
+
# @param command [Array<String>] the full command that was run
|
|
194
|
+
#
|
|
195
|
+
# @param processed_out [String] the post-processed stdout string
|
|
196
|
+
#
|
|
197
|
+
# @param processed_err [String] the post-processed stderr string
|
|
198
|
+
#
|
|
199
|
+
# @return [void]
|
|
200
|
+
#
|
|
201
|
+
# @api private
|
|
202
|
+
#
|
|
203
|
+
def log_result(result, command, processed_out, processed_err)
|
|
204
|
+
logger.info { "#{command} exited with status #{result}" }
|
|
205
|
+
logger.debug { "stdout:\n#{processed_out.inspect}\nstderr:\n#{processed_err.inspect}" }
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Build a {Git::CommandLine::Result} and raise on timeout, signal, or failure
|
|
209
|
+
#
|
|
210
|
+
# @param command [Array<String>] the full command that was run
|
|
211
|
+
#
|
|
212
|
+
# @param result [ProcessExecuter::Result] the raw process result
|
|
213
|
+
#
|
|
214
|
+
# @param processed_out [String] processed stdout string
|
|
215
|
+
#
|
|
216
|
+
# @param processed_err [String] processed stderr string
|
|
217
|
+
#
|
|
218
|
+
# @param timeout [Numeric, nil] the timeout value (for error context)
|
|
219
|
+
#
|
|
220
|
+
# @param raise_on_failure [Boolean] whether to raise on non-zero exit status
|
|
221
|
+
#
|
|
222
|
+
# @return [Git::CommandLine::Result]
|
|
223
|
+
#
|
|
224
|
+
# @raise [Git::TimeoutError] if the command timed out
|
|
225
|
+
#
|
|
226
|
+
# @raise [Git::SignaledError] if the command was terminated by a signal
|
|
227
|
+
#
|
|
228
|
+
# @raise [Git::FailedError] if the command failed and raise_on_failure is true
|
|
229
|
+
#
|
|
230
|
+
# @api private
|
|
231
|
+
#
|
|
232
|
+
# rubocop:disable Metrics/ParameterLists
|
|
233
|
+
def command_line_result(command, result, processed_out, processed_err, timeout, raise_on_failure)
|
|
234
|
+
Git::CommandLine::Result.new(command, result, processed_out, processed_err).tap do |processed_result|
|
|
235
|
+
raise Git::TimeoutError.new(processed_result, timeout) if result.timed_out?
|
|
236
|
+
|
|
237
|
+
raise Git::SignaledError, processed_result if result.signaled?
|
|
238
|
+
|
|
239
|
+
raise Git::FailedError, processed_result if raise_on_failure && !result.success?
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
# rubocop:enable Metrics/ParameterLists
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|