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
data/lib/git/branch.rb
CHANGED
|
@@ -1,102 +1,381 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'git/
|
|
3
|
+
require 'git/base'
|
|
4
|
+
require_relative 'branch_info'
|
|
4
5
|
|
|
5
6
|
module Git
|
|
6
7
|
# Represents a Git branch
|
|
8
|
+
#
|
|
9
|
+
# Branch objects provide access to branch metadata and operations like checkout,
|
|
10
|
+
# delete, and merge. They should be obtained via {Git::Base#branch} or
|
|
11
|
+
# {Git::Base#branches}, not constructed directly.
|
|
12
|
+
#
|
|
13
|
+
# @example Getting a branch
|
|
14
|
+
# git = Git.open('.')
|
|
15
|
+
# branch = git.branch('main')
|
|
16
|
+
# branch.checkout
|
|
17
|
+
#
|
|
18
|
+
# @example Listing branches
|
|
19
|
+
# git.branches.each { |b| puts b.name }
|
|
20
|
+
#
|
|
21
|
+
# @api public
|
|
22
|
+
#
|
|
7
23
|
class Branch
|
|
8
|
-
|
|
24
|
+
# The full refname of this branch
|
|
25
|
+
#
|
|
26
|
+
# For local branches this is the short name (e.g. `'main'`). For
|
|
27
|
+
# remote-tracking branches obtained via {Git::Base#branches} this includes
|
|
28
|
+
# the `remotes/` prefix (e.g. `'remotes/origin/main'`). Branches constructed
|
|
29
|
+
# by {Git::Remote#branch} use the `<remote>/<branch>` form (e.g.
|
|
30
|
+
# `'origin/main'`) which does **not** populate {#remote}.
|
|
31
|
+
#
|
|
32
|
+
# @example Local and remote-tracking branch full refnames
|
|
33
|
+
# git.branch('main').full #=> 'main'
|
|
34
|
+
# git.branch('remotes/origin/main').full #=> 'remotes/origin/main'
|
|
35
|
+
#
|
|
36
|
+
# @return [String] the full refname
|
|
37
|
+
#
|
|
38
|
+
attr_accessor :full
|
|
9
39
|
|
|
10
|
-
|
|
11
|
-
|
|
40
|
+
# The remote for this branch, or `nil` for local or bare-name remote-tracking branches
|
|
41
|
+
#
|
|
42
|
+
# Set to a {Git::Remote} object only when this branch was initialized with a
|
|
43
|
+
# `remotes/<remote>/` or `refs/remotes/<remote>/` prefix. `nil` for local
|
|
44
|
+
# branches and for remote-tracking branches in `<remote>/<branch>` form
|
|
45
|
+
# (such as those returned by {Git::Remote#branch}).
|
|
46
|
+
#
|
|
47
|
+
# @example Local and remote-tracking branches
|
|
48
|
+
# git.branch('main').remote #=> nil
|
|
49
|
+
# git.branch('remotes/origin/main').remote #=> #<Git::Remote 'origin'>
|
|
50
|
+
# git.remote('origin').branch('main').remote #=> nil # uses 'origin/main' form
|
|
51
|
+
#
|
|
52
|
+
# @return [Git::Remote, nil] the remote object, or `nil`
|
|
53
|
+
#
|
|
54
|
+
attr_accessor :remote
|
|
55
|
+
|
|
56
|
+
# The short branch name without the remote prefix
|
|
57
|
+
#
|
|
58
|
+
# For both local and remote-tracking branches this is the bare branch
|
|
59
|
+
# name (e.g. `'main'` rather than `'remotes/origin/main'`).
|
|
60
|
+
#
|
|
61
|
+
# @example Local and remote-tracking branch short names
|
|
62
|
+
# git.branch('main').name #=> 'main'
|
|
63
|
+
# git.branch('remotes/origin/main').name #=> 'main'
|
|
64
|
+
#
|
|
65
|
+
# @return [String] the short branch name
|
|
66
|
+
#
|
|
67
|
+
attr_accessor :name
|
|
68
|
+
|
|
69
|
+
# Initialize a new Branch object
|
|
70
|
+
#
|
|
71
|
+
# @param base [Git::Base, Git::Repository] the git repository
|
|
72
|
+
#
|
|
73
|
+
# Accepts either a {Git::Base} (legacy) or a {Git::Repository} (new form).
|
|
74
|
+
# The `is_a?(Git::Base)` guard will be removed when {Git::Base} is deleted
|
|
75
|
+
# in Phase 4.
|
|
76
|
+
#
|
|
77
|
+
# @param branch_info_or_name [Git::BranchInfo, String] branch info object or name string
|
|
78
|
+
#
|
|
79
|
+
# Passing a BranchInfo is preferred; String support is for backward compatibility.
|
|
80
|
+
#
|
|
81
|
+
# @note Use {Git::Base#branch} or {Git::Base#branches} instead of constructing directly
|
|
82
|
+
#
|
|
83
|
+
# @api private
|
|
84
|
+
#
|
|
85
|
+
def initialize(base, branch_info_or_name)
|
|
12
86
|
@base = base
|
|
13
87
|
@gcommit = nil
|
|
14
88
|
@stashes = nil
|
|
15
|
-
|
|
89
|
+
|
|
90
|
+
initialize_from_argument(branch_info_or_name)
|
|
16
91
|
end
|
|
17
92
|
|
|
93
|
+
# Returns the commit at the tip of this branch
|
|
94
|
+
#
|
|
95
|
+
# The result is memoized after the first call.
|
|
96
|
+
#
|
|
97
|
+
# @example Get the tip commit
|
|
98
|
+
# git.branch('main').gcommit #=> #<Git::Object ...>
|
|
99
|
+
#
|
|
100
|
+
# @return [Git::Object] the commit at the tip of this branch
|
|
101
|
+
#
|
|
18
102
|
def gcommit
|
|
19
|
-
@gcommit ||=
|
|
103
|
+
@gcommit ||= branch_repository.gcommit(@full)
|
|
20
104
|
@gcommit
|
|
21
105
|
end
|
|
22
106
|
|
|
107
|
+
# Returns the stash list for this repository
|
|
108
|
+
#
|
|
109
|
+
# The result is memoized after the first call.
|
|
110
|
+
#
|
|
111
|
+
# @example Iterate over stash entries
|
|
112
|
+
# git.branch('main').stashes.each { |s| puts s }
|
|
113
|
+
#
|
|
114
|
+
# @return [Git::Stashes] the stash list
|
|
115
|
+
#
|
|
23
116
|
def stashes
|
|
24
|
-
@stashes ||= Git::Stashes.new(
|
|
117
|
+
@stashes ||= Git::Stashes.new(branch_repository)
|
|
25
118
|
end
|
|
26
119
|
|
|
120
|
+
# Checks out this branch, attempting to create it first if it does not already exist
|
|
121
|
+
#
|
|
122
|
+
# Branch creation is attempted via {#check_if_create}; any error from that
|
|
123
|
+
# step is silently ignored and the checkout proceeds regardless.
|
|
124
|
+
#
|
|
125
|
+
# **Note:** for remote-tracking branches (where {#remote} is not `nil`),
|
|
126
|
+
# `check_if_create` will attempt to create a *local* branch named {#name}
|
|
127
|
+
# as a side-effect before checking out {#full} (which typically results in
|
|
128
|
+
# a detached HEAD). This is a known limitation; see
|
|
129
|
+
# [ruby-git#1280](https://github.com/ruby-git/ruby-git/issues/1280).
|
|
130
|
+
#
|
|
131
|
+
# @example Check out a branch
|
|
132
|
+
# git = Git.open('.')
|
|
133
|
+
# git.branch('main').checkout
|
|
134
|
+
#
|
|
135
|
+
# @return [String] git's stdout from the checkout
|
|
136
|
+
#
|
|
137
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
138
|
+
#
|
|
27
139
|
def checkout
|
|
28
140
|
check_if_create
|
|
29
|
-
|
|
141
|
+
branch_repository.checkout(@full)
|
|
30
142
|
end
|
|
31
143
|
|
|
144
|
+
# Archives this branch and writes the result to a file
|
|
145
|
+
#
|
|
146
|
+
# @example Archive to a tar file
|
|
147
|
+
# git.branch('main').archive('/tmp/main.tar')
|
|
148
|
+
#
|
|
149
|
+
# @example Archive to a zip file
|
|
150
|
+
# git.branch('main').archive('/tmp/main.zip', format: 'zip')
|
|
151
|
+
#
|
|
152
|
+
# @param file [String] path to the destination archive file
|
|
153
|
+
#
|
|
154
|
+
# @param opts [Hash] archive options (see {Git::Base#archive})
|
|
155
|
+
#
|
|
156
|
+
# @return [String] the path to the written archive file
|
|
157
|
+
#
|
|
158
|
+
# @raise [Git::FailedError] if `git archive` fails
|
|
159
|
+
#
|
|
32
160
|
def archive(file, opts = {})
|
|
33
|
-
|
|
161
|
+
branch_repository.archive(@full, file, opts)
|
|
34
162
|
end
|
|
35
163
|
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
164
|
+
# Checks out this branch for the duration of a block, then restores the original branch
|
|
165
|
+
#
|
|
166
|
+
# If the block returns a truthy value, all pending changes are committed with the
|
|
167
|
+
# given message before switching back to the original branch. If the block returns
|
|
168
|
+
# a falsy value, a hard reset is performed before switching back.
|
|
169
|
+
#
|
|
170
|
+
# **Note:** the restore checkout is not wrapped in `ensure`. If the block,
|
|
171
|
+
# the commit, or the reset raises an exception, the repository will be left
|
|
172
|
+
# checked out on this branch rather than restored to the original.
|
|
173
|
+
#
|
|
174
|
+
# @example Commit a new file on a feature branch
|
|
175
|
+
# git.branch('feature').in_branch('Add README') do
|
|
176
|
+
# File.write('README.md', '# Hello')
|
|
177
|
+
# git.add('README.md')
|
|
178
|
+
# true # commit and return to original branch
|
|
179
|
+
# end
|
|
180
|
+
#
|
|
181
|
+
# @param message [String] commit message used when the block returns truthy
|
|
182
|
+
#
|
|
183
|
+
# @yield Executes the block with this branch checked out
|
|
184
|
+
#
|
|
185
|
+
# @yieldreturn [Object] return a truthy value to commit all changes, a falsy value to hard-reset
|
|
186
|
+
#
|
|
187
|
+
# @return [String] git's stdout from the final checkout back to the original branch
|
|
188
|
+
#
|
|
189
|
+
# @raise [Git::FailedError] if any of the underlying git operations (checkout, commit, reset) fail
|
|
190
|
+
#
|
|
41
191
|
def in_branch(message = 'in branch work')
|
|
42
|
-
old_current =
|
|
192
|
+
old_current = branch_repository.current_branch
|
|
43
193
|
checkout
|
|
44
194
|
if yield
|
|
45
|
-
|
|
195
|
+
branch_repository.commit_all(message)
|
|
46
196
|
else
|
|
47
|
-
|
|
197
|
+
branch_repository.reset(nil, hard: true)
|
|
48
198
|
end
|
|
49
|
-
|
|
199
|
+
branch_repository.checkout(old_current)
|
|
50
200
|
end
|
|
51
201
|
|
|
202
|
+
# Creates this branch if it does not already exist
|
|
203
|
+
#
|
|
204
|
+
# Silently ignores any error raised during branch creation (including the case
|
|
205
|
+
# where the branch already exists).
|
|
206
|
+
#
|
|
207
|
+
# @example Create a new branch
|
|
208
|
+
# git.branch('feature').create
|
|
209
|
+
#
|
|
210
|
+
# @return [nil]
|
|
211
|
+
#
|
|
52
212
|
def create
|
|
53
213
|
check_if_create
|
|
54
214
|
end
|
|
55
215
|
|
|
216
|
+
# Deletes this branch
|
|
217
|
+
#
|
|
218
|
+
# Remote-tracking branches (one where {#remote} is not `nil`) delete the
|
|
219
|
+
# local remote-tracking ref; they do not push a deletion to the remote.
|
|
220
|
+
#
|
|
221
|
+
# @example Delete a local branch
|
|
222
|
+
# git.branch('old-feature').delete
|
|
223
|
+
#
|
|
224
|
+
# @return [String] git's deletion output
|
|
225
|
+
#
|
|
226
|
+
# @raise [Git::Error] if the branch cannot be deleted
|
|
227
|
+
#
|
|
56
228
|
def delete
|
|
57
|
-
@
|
|
229
|
+
if @remote
|
|
230
|
+
branch_repository.branch_delete("#{@remote.name}/#{@name}", remotes: true)
|
|
231
|
+
else
|
|
232
|
+
branch_repository.branch_delete(@name)
|
|
233
|
+
end
|
|
58
234
|
end
|
|
59
235
|
|
|
236
|
+
# Returns true if this is the currently checked-out branch
|
|
237
|
+
#
|
|
238
|
+
# **Note:** this compares the current branch's short name against {#name}.
|
|
239
|
+
# For a remote-tracking branch (where {#remote} is not `nil`), {#name} is
|
|
240
|
+
# still the bare short name (e.g. `'main'`), so this will return `true`
|
|
241
|
+
# whenever the *local* branch with that name is checked out — not the
|
|
242
|
+
# remote-tracking ref itself.
|
|
243
|
+
#
|
|
244
|
+
# @example Check whether currently on main
|
|
245
|
+
# git.branch('main').current #=> true
|
|
246
|
+
#
|
|
247
|
+
# @return [Boolean] whether this branch is currently checked out
|
|
248
|
+
#
|
|
249
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
250
|
+
#
|
|
60
251
|
def current # rubocop:disable Naming/PredicateMethod
|
|
61
|
-
|
|
252
|
+
branch_repository.current_branch == @name
|
|
62
253
|
end
|
|
63
254
|
|
|
255
|
+
# Returns true if this branch contains the given commit
|
|
256
|
+
#
|
|
257
|
+
# **Note:** this queries local branches by short name. For a remote-tracking
|
|
258
|
+
# branch (where {#remote} is not `nil`), it checks the *local* branch with
|
|
259
|
+
# the same {#name} rather than the remote-tracking ref, which may give an
|
|
260
|
+
# inaccurate result.
|
|
261
|
+
#
|
|
262
|
+
# @example Check if a commit is reachable from this branch
|
|
263
|
+
# git.branch('main').contains?('abc1234') #=> true
|
|
264
|
+
#
|
|
265
|
+
# @param commit [String] the commit SHA or ref to check
|
|
266
|
+
#
|
|
267
|
+
# @return [Boolean] whether this branch contains the given commit
|
|
268
|
+
#
|
|
269
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
270
|
+
#
|
|
64
271
|
def contains?(commit)
|
|
65
|
-
|
|
272
|
+
!branch_repository.branch_contains(commit, name).empty?
|
|
66
273
|
end
|
|
67
274
|
|
|
275
|
+
# Merges a branch into this branch, or merges this branch into the current branch
|
|
276
|
+
#
|
|
277
|
+
# @overload merge(branch, message = nil)
|
|
278
|
+
#
|
|
279
|
+
# Temporarily checks out this branch, merges the given branch into it,
|
|
280
|
+
# then restores the original branch.
|
|
281
|
+
#
|
|
282
|
+
# **Note:** if `self` is a remote-tracking branch (where {#remote} is not
|
|
283
|
+
# `nil`), this delegates to {#checkout} which has the detached-HEAD
|
|
284
|
+
# side-effect described there. The remote-tracking ref will not be updated.
|
|
285
|
+
#
|
|
286
|
+
# @example Merge a feature branch into main
|
|
287
|
+
# git.branch('main').merge('feature')
|
|
288
|
+
#
|
|
289
|
+
# @param branch [String] the name of the branch to merge into this one
|
|
290
|
+
#
|
|
291
|
+
# @param message [String, nil] commit message for the merge commit
|
|
292
|
+
#
|
|
293
|
+
# @return [String] git's stdout from the final checkout back to the original branch
|
|
294
|
+
#
|
|
295
|
+
# @overload merge()
|
|
296
|
+
#
|
|
297
|
+
# Merges this branch into the currently checked-out branch.
|
|
298
|
+
#
|
|
299
|
+
# @example Merge main into the current branch
|
|
300
|
+
# git.branch('main').merge
|
|
301
|
+
#
|
|
302
|
+
# @return [String] git's stdout from the merge command
|
|
303
|
+
#
|
|
304
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
305
|
+
#
|
|
68
306
|
def merge(branch = nil, message = nil)
|
|
69
307
|
if branch
|
|
70
308
|
in_branch do
|
|
71
|
-
|
|
309
|
+
branch_repository.merge(branch, message)
|
|
72
310
|
false
|
|
73
311
|
end
|
|
74
312
|
# merge a branch into this one
|
|
75
313
|
else
|
|
76
314
|
# merge this branch into the current one
|
|
77
|
-
|
|
315
|
+
branch_repository.merge(@name)
|
|
78
316
|
end
|
|
79
317
|
end
|
|
80
318
|
|
|
319
|
+
# Updates the git ref for this branch to point to the given commit
|
|
320
|
+
#
|
|
321
|
+
# The target ref depends on whether {#remote} is set:
|
|
322
|
+
# - When {#remote} is not `nil` (i.e. the branch was initialized with a
|
|
323
|
+
# `remotes/<remote>/` or `refs/remotes/<remote>/` prefix), updates
|
|
324
|
+
# `refs/remotes/<remote>/<name>`.
|
|
325
|
+
# - Otherwise updates `refs/heads/<name>`. Note that branches in the
|
|
326
|
+
# `<remote>/<branch>` form (e.g. those returned by {Git::Remote#branch})
|
|
327
|
+
# have `remote == nil` and therefore update `refs/heads/<remote>/<name>`,
|
|
328
|
+
# **not** `refs/remotes/...`.
|
|
329
|
+
#
|
|
330
|
+
# @example Advance a local branch to a new commit
|
|
331
|
+
# git.branch('feature').update_ref('abc1234def5678')
|
|
332
|
+
#
|
|
333
|
+
# @param commit [String] the commit SHA to point this branch at
|
|
334
|
+
#
|
|
335
|
+
# @return [Git::CommandLineResult] the result of calling `git update-ref`
|
|
336
|
+
#
|
|
337
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
338
|
+
#
|
|
81
339
|
def update_ref(commit)
|
|
82
340
|
if @remote
|
|
83
|
-
|
|
341
|
+
branch_repository.update_ref("remotes/#{@remote.name}/#{@name}", commit)
|
|
84
342
|
else
|
|
85
|
-
|
|
343
|
+
branch_repository.update_ref(@name, commit)
|
|
86
344
|
end
|
|
87
345
|
end
|
|
88
346
|
|
|
347
|
+
# Returns this branch as a single-element array containing its full refname
|
|
348
|
+
#
|
|
349
|
+
# @example Get branch as array
|
|
350
|
+
# git.branch('main').to_a #=> ['main']
|
|
351
|
+
#
|
|
352
|
+
# @return [Array<String>] a single-element array containing the full refname
|
|
353
|
+
#
|
|
89
354
|
def to_a
|
|
90
355
|
[@full]
|
|
91
356
|
end
|
|
92
357
|
|
|
358
|
+
# Returns the full refname of this branch as a string
|
|
359
|
+
#
|
|
360
|
+
# @example Get branch as string
|
|
361
|
+
# git.branch('main').to_s #=> 'main'
|
|
362
|
+
#
|
|
363
|
+
# @return [String] the full refname
|
|
364
|
+
#
|
|
93
365
|
def to_s
|
|
94
366
|
@full
|
|
95
367
|
end
|
|
96
368
|
|
|
369
|
+
# Regular expression for parsing branch refnames
|
|
370
|
+
#
|
|
371
|
+
# Matches full and short refnames, capturing an optional remote name and the
|
|
372
|
+
# branch name. Used internally to identify remote-tracking branches.
|
|
373
|
+
#
|
|
374
|
+
# @api private
|
|
375
|
+
#
|
|
97
376
|
BRANCH_NAME_REGEXP = %r{
|
|
98
377
|
^
|
|
99
|
-
# Optional 'refs/remotes/' at the
|
|
378
|
+
# Optional 'remotes/' or 'refs/remotes/' at the beginning to specify a remote tracking branch
|
|
100
379
|
# with a <remote_name>. <remote_name> is nil if not present.
|
|
101
380
|
(?:
|
|
102
381
|
(?:(?:refs/)?remotes/)(?<remote_name>[^/]+)/
|
|
@@ -107,26 +386,66 @@ module Git
|
|
|
107
386
|
|
|
108
387
|
private
|
|
109
388
|
|
|
110
|
-
#
|
|
389
|
+
# Dispatches initialization to the appropriate strategy
|
|
390
|
+
#
|
|
391
|
+
# @param branch_info_or_name [Git::BranchInfo, String] branch info or name string
|
|
392
|
+
#
|
|
393
|
+
# @return [nil]
|
|
111
394
|
#
|
|
112
|
-
#
|
|
113
|
-
# Takes the second part (splittign by '/') as the remote name.
|
|
114
|
-
# Takes the rest as the repo name (can also hold one or more '/').
|
|
395
|
+
# @api private
|
|
115
396
|
#
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
397
|
+
def initialize_from_argument(branch_info_or_name)
|
|
398
|
+
if branch_info_or_name.is_a?(Git::BranchInfo)
|
|
399
|
+
initialize_from_branch_info(branch_info_or_name)
|
|
400
|
+
else
|
|
401
|
+
initialize_from_name(branch_info_or_name)
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# Initialize from a BranchInfo object (preferred path)
|
|
121
406
|
#
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
|
|
407
|
+
# @param branch_info [Git::BranchInfo] the branch info
|
|
408
|
+
#
|
|
409
|
+
# @return [nil]
|
|
410
|
+
#
|
|
411
|
+
def initialize_from_branch_info(branch_info)
|
|
412
|
+
@full = branch_info.refname
|
|
413
|
+
@name = branch_info.short_name
|
|
414
|
+
@remote = branch_info.remote_name ? Git::Remote.new(@base, branch_info.remote_name) : nil
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Initialize from a string name (legacy path for backward compatibility)
|
|
418
|
+
#
|
|
419
|
+
# @param name [String] the branch name
|
|
420
|
+
#
|
|
421
|
+
# @return [nil]
|
|
422
|
+
#
|
|
423
|
+
def initialize_from_name(name)
|
|
424
|
+
@full = name
|
|
425
|
+
@remote, @name = parse_name(name)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# Parses a full branch name into remote and short branch name components
|
|
429
|
+
#
|
|
430
|
+
# Strips an optional `remotes/` or `refs/remotes/` prefix. Only inputs that
|
|
431
|
+
# begin with one of those prefixes yield a remote object; all other inputs
|
|
432
|
+
# (including `'origin/master'`) are treated as local branch names with a
|
|
433
|
+
# `nil` remote.
|
|
434
|
+
#
|
|
435
|
+
# @example Local branches
|
|
436
|
+
# parse_name('master') #=> [nil, 'master']
|
|
437
|
+
# parse_name('origin/master') #=> [nil, 'origin/master']
|
|
438
|
+
#
|
|
439
|
+
# @example Remote-tracking branches
|
|
440
|
+
# parse_name('remotes/origin/master') #=> [#<Git::Remote 'origin'>, 'master']
|
|
441
|
+
# parse_name('refs/remotes/origin/master') #=> [#<Git::Remote 'origin'>, 'master']
|
|
442
|
+
#
|
|
443
|
+
# @param name [String] the full branch name to parse
|
|
444
|
+
#
|
|
445
|
+
# @return [Array(Git::Remote, String)] a two-element array; the first element is
|
|
446
|
+
# a {Git::Remote} for remote-tracking branches or `nil` for local branches,
|
|
447
|
+
# and the second element is the short branch name
|
|
127
448
|
#
|
|
128
|
-
# param [String] name branch full name.
|
|
129
|
-
# return [<Git::Remote,NilClass,String>] an Array containing the remote and branch names.
|
|
130
449
|
def parse_name(name)
|
|
131
450
|
# Expect this will always match
|
|
132
451
|
match = name.match(BRANCH_NAME_REGEXP)
|
|
@@ -135,10 +454,28 @@ module Git
|
|
|
135
454
|
[remote, branch_name]
|
|
136
455
|
end
|
|
137
456
|
|
|
457
|
+
# Creates the branch if it does not already exist, ignoring errors
|
|
458
|
+
#
|
|
459
|
+
# @return [nil]
|
|
460
|
+
#
|
|
138
461
|
def check_if_create
|
|
139
|
-
|
|
462
|
+
branch_repository.branch_new(@name)
|
|
140
463
|
rescue StandardError
|
|
141
464
|
nil
|
|
142
465
|
end
|
|
466
|
+
|
|
467
|
+
# Resolves the {Git::Repository} for this branch
|
|
468
|
+
#
|
|
469
|
+
# Accepts either a {Git::Repository} (new form) or a {Git::Base} (legacy).
|
|
470
|
+
# The `is_a?(Git::Base)` guard will be removed when {Git::Base} is deleted
|
|
471
|
+
# in Phase 4.
|
|
472
|
+
#
|
|
473
|
+
# @return [Git::Repository]
|
|
474
|
+
#
|
|
475
|
+
# @api private
|
|
476
|
+
#
|
|
477
|
+
def branch_repository
|
|
478
|
+
@base.is_a?(Git::Base) ? @base.facade_repository : @base
|
|
479
|
+
end
|
|
143
480
|
end
|
|
144
481
|
end
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
# @see Git::Commands::Branch::Delete
|
|
19
|
+
#
|
|
20
|
+
# @api public
|
|
21
|
+
#
|
|
22
|
+
# @!attribute [r] name
|
|
23
|
+
# The name of the branch that failed to be deleted
|
|
24
|
+
# @return [String]
|
|
25
|
+
#
|
|
26
|
+
# @!attribute [r] error_message
|
|
27
|
+
# The error message from git explaining why the branch could not be deleted
|
|
28
|
+
# @return [String]
|
|
29
|
+
#
|
|
30
|
+
BranchDeleteFailure = Data.define(:name, :error_message)
|
|
31
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
+
# @see Git::BranchDeleteFailure
|
|
33
|
+
# @see Git::Commands::Branch::Delete
|
|
34
|
+
#
|
|
35
|
+
# @api public
|
|
36
|
+
#
|
|
37
|
+
# @!attribute [r] deleted
|
|
38
|
+
# Branches that were successfully deleted
|
|
39
|
+
# @return [Array<Git::BranchInfo>]
|
|
40
|
+
#
|
|
41
|
+
# @!attribute [r] not_deleted
|
|
42
|
+
# Branches that could not be deleted, with the reason for each failure
|
|
43
|
+
# @return [Array<Git::BranchDeleteFailure>]
|
|
44
|
+
#
|
|
45
|
+
BranchDeleteResult = Data.define(:deleted, :not_deleted) do
|
|
46
|
+
# Returns true if all requested branches were successfully deleted
|
|
47
|
+
#
|
|
48
|
+
# @return [Boolean] true if no branches failed to delete, false otherwise
|
|
49
|
+
#
|
|
50
|
+
# @example
|
|
51
|
+
# result = branch_delete.call('feature-branch')
|
|
52
|
+
# if result.success?
|
|
53
|
+
# puts "All branches deleted successfully"
|
|
54
|
+
# else
|
|
55
|
+
# puts "Some branches could not be deleted:"
|
|
56
|
+
# result.not_deleted.each { |f| puts " #{f.name}: #{f.error_message}" }
|
|
57
|
+
# end
|
|
58
|
+
#
|
|
59
|
+
def success?
|
|
60
|
+
not_deleted.empty?
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|