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,240 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'git/branch_info'
|
|
4
|
+
require 'git/branch_delete_result'
|
|
5
|
+
require 'git/branch_delete_failure'
|
|
6
|
+
|
|
7
|
+
module Git
|
|
8
|
+
# Internal parsers that translate raw git command output into structured Ruby objects.
|
|
9
|
+
#
|
|
10
|
+
# @api private
|
|
11
|
+
#
|
|
12
|
+
module Parsers
|
|
13
|
+
# Parser for git branch command output
|
|
14
|
+
#
|
|
15
|
+
# Handles parsing of `git branch --list` and `git branch --delete` output
|
|
16
|
+
# into structured data objects.
|
|
17
|
+
#
|
|
18
|
+
# ## Design Note: Namespace Organization
|
|
19
|
+
#
|
|
20
|
+
# This parser creates and returns {Git::BranchInfo} and {Git::BranchDeleteResult}
|
|
21
|
+
# objects, which live at the top-level `Git::` namespace rather than within
|
|
22
|
+
# `Git::Parsers::`. This is intentional:
|
|
23
|
+
#
|
|
24
|
+
# - **Parsers are infrastructure** - marked `@api private`, users shouldn't
|
|
25
|
+
# interact with them directly
|
|
26
|
+
# - **Info/Result classes are public API** - returned by commands and used
|
|
27
|
+
# throughout the codebase
|
|
28
|
+
# - **Info classes are domain entities** - represent core git concepts
|
|
29
|
+
# (branches as data)
|
|
30
|
+
# - **Result classes are operation outcomes** - represent command results,
|
|
31
|
+
# not parsing details
|
|
32
|
+
#
|
|
33
|
+
# Keeping Info/Result classes at `Git::` improves discoverability and correctly
|
|
34
|
+
# reflects their role as public types rather than parser internals.
|
|
35
|
+
#
|
|
36
|
+
# @api private
|
|
37
|
+
#
|
|
38
|
+
module Branch
|
|
39
|
+
# Format string for git branch --format
|
|
40
|
+
#
|
|
41
|
+
# Fields (null-delimited):
|
|
42
|
+
# 1. refname - full ref name (e.g., refs/heads/main, refs/remotes/origin/main)
|
|
43
|
+
# 2. objectname - full SHA of the commit the branch points to
|
|
44
|
+
# 3. HEAD - '*' if current branch, empty otherwise
|
|
45
|
+
# 4. worktreepath - path if checked out in another worktree, empty otherwise
|
|
46
|
+
# 5. symref - target ref if symbolic reference, empty otherwise
|
|
47
|
+
# 6. upstream - full upstream ref (e.g., refs/remotes/origin/main), empty if none
|
|
48
|
+
#
|
|
49
|
+
# Null bytes (%00) are used as field delimiters so that worktree paths
|
|
50
|
+
# containing special characters (including '|') parse correctly.
|
|
51
|
+
#
|
|
52
|
+
FORMAT_STRING = '%(refname)%00%(objectname)%00%(HEAD)%00%(worktreepath)%00%(symref)%00%(upstream)'
|
|
53
|
+
|
|
54
|
+
# Delimiter used in format output
|
|
55
|
+
FIELD_DELIMITER = "\0"
|
|
56
|
+
|
|
57
|
+
# Regex to parse successful deletion lines from stdout
|
|
58
|
+
# Matches: Deleted branch branchname (was abc123).
|
|
59
|
+
# Matches: Deleted remote-tracking branch origin/branchname (was abc123).
|
|
60
|
+
# Uses non-greedy match to capture branch names containing spaces
|
|
61
|
+
DELETED_BRANCH_REGEX = /^Deleted (?:remote-tracking )?branch (.+?) \(was/
|
|
62
|
+
|
|
63
|
+
# Regex to parse error messages from stderr
|
|
64
|
+
# Matches: error: branch 'branchname' not found.
|
|
65
|
+
ERROR_BRANCH_REGEX = /^error: branch '([^']+)'(.*)$/
|
|
66
|
+
|
|
67
|
+
module_function
|
|
68
|
+
|
|
69
|
+
# Parse git branch --list output into BranchInfo objects
|
|
70
|
+
#
|
|
71
|
+
# @example Parse NUL-delimited branch list output
|
|
72
|
+
# Git::Parsers::Branch.parse_list(
|
|
73
|
+
# "refs/heads/main\0abc1234\0*\0\0\0\n" \
|
|
74
|
+
# "refs/heads/feature\0def5678\0\0\0\0\n"
|
|
75
|
+
# )
|
|
76
|
+
# # => [#<data Git::BranchInfo refname="refs/heads/main", ...>,
|
|
77
|
+
# # #<data Git::BranchInfo refname="refs/heads/feature", ...>]
|
|
78
|
+
#
|
|
79
|
+
# @param stdout [String] output from git branch --list --format=...
|
|
80
|
+
#
|
|
81
|
+
# @param remote_names [Array<String>] configured remote names used to resolve
|
|
82
|
+
# remote-tracking refs with slash-containing remote names
|
|
83
|
+
#
|
|
84
|
+
# @return [Array<Git::BranchInfo>] parsed branch information
|
|
85
|
+
#
|
|
86
|
+
def parse_list(stdout, remote_names: [])
|
|
87
|
+
stdout.split("\n").filter_map { |line| parse_branch_line(line, remote_names: remote_names) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Parse a single formatted branch line
|
|
91
|
+
#
|
|
92
|
+
# @param line [String] the line to parse (NUL-delimited fields)
|
|
93
|
+
#
|
|
94
|
+
# @param remote_names [Array<String>] configured remote names used to resolve
|
|
95
|
+
# remote-tracking refs with slash-containing remote names
|
|
96
|
+
#
|
|
97
|
+
# @return [Git::BranchInfo, nil] branch info object, or nil if line should be skipped
|
|
98
|
+
#
|
|
99
|
+
def parse_branch_line(line, remote_names: [])
|
|
100
|
+
fields = line.split(FIELD_DELIMITER, 6)
|
|
101
|
+
|
|
102
|
+
return nil if non_branch_entry?(fields[0])
|
|
103
|
+
|
|
104
|
+
build_branch_info(fields, remote_names: remote_names)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Build a BranchInfo from parsed fields
|
|
108
|
+
#
|
|
109
|
+
# @param fields [Array<String>] the parsed fields:
|
|
110
|
+
# [refname, objectname, head, worktreepath, symref, upstream]
|
|
111
|
+
#
|
|
112
|
+
# @param remote_names [Array<String>] configured remote names used to resolve
|
|
113
|
+
# remote-tracking refs with slash-containing remote names
|
|
114
|
+
#
|
|
115
|
+
# @return [Git::BranchInfo] the branch info object
|
|
116
|
+
#
|
|
117
|
+
def build_branch_info(fields, remote_names: [])
|
|
118
|
+
raw_refname, objectname, head, worktreepath, symref, upstream = fields
|
|
119
|
+
Git::BranchInfo.new(
|
|
120
|
+
refname: raw_refname,
|
|
121
|
+
target_oid: presence(objectname),
|
|
122
|
+
current: head == '*',
|
|
123
|
+
worktree_path: head == '*' ? nil : presence(worktreepath),
|
|
124
|
+
symref: presence(symref),
|
|
125
|
+
upstream: build_upstream_info(upstream),
|
|
126
|
+
remote_name: resolve_remote_name(raw_refname, remote_names)
|
|
127
|
+
)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Resolve a remote-tracking refname to a configured remote name
|
|
131
|
+
#
|
|
132
|
+
# @param refname [String] the branch refname to inspect
|
|
133
|
+
#
|
|
134
|
+
# @param remote_names [Array<String>] configured remote names
|
|
135
|
+
#
|
|
136
|
+
# @return [String, nil] the resolved remote name, or nil for local branches
|
|
137
|
+
#
|
|
138
|
+
def resolve_remote_name(refname, remote_names)
|
|
139
|
+
remote_path = refname[%r{\A(?:refs/)?remotes/(.+)\z}, 1]
|
|
140
|
+
return nil if remote_path.nil?
|
|
141
|
+
|
|
142
|
+
configured_remote_name = remote_names
|
|
143
|
+
.select { |remote_name| remote_path.start_with?("#{remote_name}/") }
|
|
144
|
+
.max_by(&:length)
|
|
145
|
+
|
|
146
|
+
configured_remote_name || Git::BranchInfo.fallback_remote_name(refname)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Check if the refname represents a detached HEAD state or non-branch entry
|
|
150
|
+
#
|
|
151
|
+
# Git outputs special entries for detached HEAD and non-branch states:
|
|
152
|
+
# - "(HEAD detached at <ref>)" when in detached HEAD state
|
|
153
|
+
# - "(not a branch)" for non-branch entries
|
|
154
|
+
#
|
|
155
|
+
# @param refname [String] the refname to check
|
|
156
|
+
#
|
|
157
|
+
# @return [Boolean] true if this is a non-branch entry
|
|
158
|
+
#
|
|
159
|
+
def non_branch_entry?(refname)
|
|
160
|
+
refname.match?(/^\(HEAD detached/) || refname.match?(/^\(not a branch\)/)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Return the raw upstream refname string, or nil if empty
|
|
164
|
+
#
|
|
165
|
+
# @param upstream_ref [String, nil] the upstream ref (e.g., 'refs/remotes/origin/main')
|
|
166
|
+
#
|
|
167
|
+
# @return [String, nil] the raw upstream refname, or nil
|
|
168
|
+
#
|
|
169
|
+
def build_upstream_info(upstream_ref)
|
|
170
|
+
return nil if upstream_ref.nil? || upstream_ref.empty?
|
|
171
|
+
|
|
172
|
+
upstream_ref
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Return value if non-empty, nil otherwise
|
|
176
|
+
#
|
|
177
|
+
# @param value [String, nil] the value to check
|
|
178
|
+
#
|
|
179
|
+
# @return [String, nil] the value or nil
|
|
180
|
+
#
|
|
181
|
+
def presence(value)
|
|
182
|
+
value.nil? || value.empty? ? nil : value
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Parse deleted branch names from stdout
|
|
186
|
+
#
|
|
187
|
+
# @example
|
|
188
|
+
# BranchParser.parse_deleted_branches("Deleted branch feature (was abc123).\n")
|
|
189
|
+
# # => ["feature"]
|
|
190
|
+
#
|
|
191
|
+
# @param stdout [String] command stdout
|
|
192
|
+
#
|
|
193
|
+
# @return [Array<String>] names of successfully deleted branches
|
|
194
|
+
#
|
|
195
|
+
def parse_deleted_branches(stdout)
|
|
196
|
+
stdout.scan(DELETED_BRANCH_REGEX).flatten
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Parse error messages from stderr into a map
|
|
200
|
+
#
|
|
201
|
+
# @example
|
|
202
|
+
# BranchParser.parse_error_messages("error: branch 'missing' not found.\n")
|
|
203
|
+
# # => {"missing" => "error: branch 'missing' not found."}
|
|
204
|
+
#
|
|
205
|
+
# @param stderr [String] command stderr
|
|
206
|
+
#
|
|
207
|
+
# @return [Hash<String, String>] map of branch name to error message
|
|
208
|
+
#
|
|
209
|
+
def parse_error_messages(stderr)
|
|
210
|
+
stderr.each_line.with_object({}) do |line, hash|
|
|
211
|
+
match = line.match(ERROR_BRANCH_REGEX)
|
|
212
|
+
hash[match[1]] = line.strip if match
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Build the BranchDeleteResult from parsed data
|
|
217
|
+
#
|
|
218
|
+
# @param requested_names [Array<String>] originally requested branch names
|
|
219
|
+
#
|
|
220
|
+
# @param existing_branches [Hash<String, Git::BranchInfo>] branches that existed before delete
|
|
221
|
+
#
|
|
222
|
+
# @param deleted_names [Array<String>] names confirmed deleted in stdout
|
|
223
|
+
#
|
|
224
|
+
# @param error_map [Hash<String, String>] map of branch name to error message
|
|
225
|
+
#
|
|
226
|
+
# @return [Git::BranchDeleteResult] the result object
|
|
227
|
+
#
|
|
228
|
+
def build_delete_result(requested_names, existing_branches, deleted_names, error_map)
|
|
229
|
+
deleted = deleted_names.filter_map { |name| existing_branches[name] }
|
|
230
|
+
|
|
231
|
+
not_deleted = (requested_names - deleted_names).map do |name|
|
|
232
|
+
error_message = error_map[name] || "branch '#{name}' could not be deleted"
|
|
233
|
+
Git::BranchDeleteFailure.new(name: name, error_message: error_message)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
Git::BranchDeleteResult.new(deleted: deleted, not_deleted: not_deleted)
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
module Parsers
|
|
5
|
+
# Parser for `git cat-file` commit and tag output
|
|
6
|
+
#
|
|
7
|
+
# Provides class methods that transform raw `git cat-file` output lines into
|
|
8
|
+
# structured Hash objects consumed by the `Git::Repository::ObjectOperations`
|
|
9
|
+
# facade.
|
|
10
|
+
#
|
|
11
|
+
# @api private
|
|
12
|
+
#
|
|
13
|
+
module CatFile
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# Matches a single `git cat-file` header line
|
|
17
|
+
#
|
|
18
|
+
# @api private
|
|
19
|
+
#
|
|
20
|
+
CAT_FILE_HEADER_LINE = /\A(?<key>\w+) (?<value>.*)\z/
|
|
21
|
+
|
|
22
|
+
# Parse `git cat-file commit` output into a structured Hash
|
|
23
|
+
#
|
|
24
|
+
# @param lines [Array<String>] mutable cat-file output lines, consumed
|
|
25
|
+
# in place during header parsing
|
|
26
|
+
#
|
|
27
|
+
# @param sha [String] the object name passed by the caller
|
|
28
|
+
#
|
|
29
|
+
# @return [Hash] commit data hash with string keys
|
|
30
|
+
#
|
|
31
|
+
# @api private
|
|
32
|
+
#
|
|
33
|
+
def parse_commit(lines, sha)
|
|
34
|
+
headers = parse_commit_headers(lines)
|
|
35
|
+
message = "#{lines.join("\n")}\n"
|
|
36
|
+
{ 'sha' => sha, 'message' => message }.merge(headers)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Parse `git cat-file tag` output into a structured Hash
|
|
40
|
+
#
|
|
41
|
+
# @param lines [Array<String>] mutable cat-file output lines, consumed
|
|
42
|
+
# in place during header parsing; remaining lines become the message
|
|
43
|
+
#
|
|
44
|
+
# @param name [String] the tag name passed by the caller
|
|
45
|
+
#
|
|
46
|
+
# @return [Hash] tag data hash with string keys
|
|
47
|
+
#
|
|
48
|
+
# @api private
|
|
49
|
+
#
|
|
50
|
+
def parse_tag(lines, name)
|
|
51
|
+
hsh = { 'name' => name }
|
|
52
|
+
each_header(lines) { |key, value| hsh[key] = value }
|
|
53
|
+
hsh['message'] = "#{lines.join("\n")}\n"
|
|
54
|
+
hsh
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Extracts and returns commit headers from the front of `lines`
|
|
58
|
+
#
|
|
59
|
+
# Mutates `lines` in place, consuming header lines and the blank
|
|
60
|
+
# separator line. After the call `lines` contains only message lines.
|
|
61
|
+
#
|
|
62
|
+
# @param lines [Array<String>] mutable cat-file output lines
|
|
63
|
+
#
|
|
64
|
+
# @return [Hash] parsed header key/value pairs; `parent` is always
|
|
65
|
+
# an Array
|
|
66
|
+
#
|
|
67
|
+
# @api private
|
|
68
|
+
#
|
|
69
|
+
def parse_commit_headers(lines)
|
|
70
|
+
headers = { 'parent' => [] }
|
|
71
|
+
each_header(lines) do |key, value|
|
|
72
|
+
if key == 'parent'
|
|
73
|
+
headers['parent'] << value
|
|
74
|
+
else
|
|
75
|
+
headers[key] = value
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
headers
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Yields parsed header key/value pairs from `git cat-file` output lines
|
|
82
|
+
#
|
|
83
|
+
# Consumes header lines from the front of `lines` until a blank line is
|
|
84
|
+
# encountered. Continuation lines that begin with a space are folded
|
|
85
|
+
# into the previous header value using newline separators.
|
|
86
|
+
#
|
|
87
|
+
# @param lines [Array<String>] mutable output lines from a cat-file response
|
|
88
|
+
#
|
|
89
|
+
# @return [void]
|
|
90
|
+
#
|
|
91
|
+
# @yield [key, value] each parsed header pair
|
|
92
|
+
#
|
|
93
|
+
# @yieldparam key [String] header field name
|
|
94
|
+
#
|
|
95
|
+
# @yieldparam value [String] unfolded header value text
|
|
96
|
+
#
|
|
97
|
+
# @yieldreturn [void]
|
|
98
|
+
#
|
|
99
|
+
# @api private
|
|
100
|
+
#
|
|
101
|
+
def each_header(lines)
|
|
102
|
+
while (line = lines.shift) && (match = CAT_FILE_HEADER_LINE.match(line))
|
|
103
|
+
key = match[:key]
|
|
104
|
+
value_lines = [match[:value]]
|
|
105
|
+
value_lines << lines.shift.lstrip while lines.first&.start_with?(' ')
|
|
106
|
+
yield key, value_lines.join("\n")
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'git/config_entry_info'
|
|
4
|
+
|
|
5
|
+
module Git
|
|
6
|
+
module Parsers
|
|
7
|
+
# Parser for `git config` output
|
|
8
|
+
#
|
|
9
|
+
# Each public method corresponds to a different `git config` sub-command
|
|
10
|
+
# variant and handles the specific output format that variant produces.
|
|
11
|
+
# Most variants are called with `--show-scope --show-origin --null`;
|
|
12
|
+
# `parse_urlmatch` handles the `--get-urlmatch` format where `--show-origin`
|
|
13
|
+
# is unsupported and therefore absent.
|
|
14
|
+
#
|
|
15
|
+
# @api private
|
|
16
|
+
#
|
|
17
|
+
module ConfigEntry
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# Parse `git config --get --show-scope --show-origin --null` output
|
|
21
|
+
#
|
|
22
|
+
# Output format (per entry): `scope\0origin\0value\0`
|
|
23
|
+
#
|
|
24
|
+
# The key name is absent from `--get` output and must be supplied by the
|
|
25
|
+
# caller.
|
|
26
|
+
#
|
|
27
|
+
# @param key [String] the config key name that was queried
|
|
28
|
+
#
|
|
29
|
+
# @param output [String] raw stdout from the command
|
|
30
|
+
#
|
|
31
|
+
# @return [Git::ConfigEntryInfo, nil] the parsed entry, or `nil` when not found
|
|
32
|
+
#
|
|
33
|
+
def parse_get(key, output)
|
|
34
|
+
return nil if output.empty?
|
|
35
|
+
|
|
36
|
+
scope, origin, value = output.split("\0", -1)
|
|
37
|
+
Git::ConfigEntryInfo.new(scope: scope, origin: origin, key: key, value: value)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Parse `git config --get-all --show-scope --show-origin --null` output
|
|
41
|
+
#
|
|
42
|
+
# Output format (per entry): `scope\0origin\0value\0`
|
|
43
|
+
#
|
|
44
|
+
# The key name is absent from `--get-all` output and must be supplied by
|
|
45
|
+
# the caller. Multiple entries appear back-to-back in the same string.
|
|
46
|
+
#
|
|
47
|
+
# @param key [String] the config key name that was queried
|
|
48
|
+
#
|
|
49
|
+
# @param output [String] raw stdout from the command
|
|
50
|
+
#
|
|
51
|
+
# @return [Array<Git::ConfigEntryInfo>] the parsed entries
|
|
52
|
+
#
|
|
53
|
+
def parse_get_all(key, output)
|
|
54
|
+
return [] if output.empty?
|
|
55
|
+
|
|
56
|
+
tokens = output.split("\0", -1)
|
|
57
|
+
tokens.pop if tokens.last == ''
|
|
58
|
+
tokens.each_slice(3).map do |scope, origin, value|
|
|
59
|
+
Git::ConfigEntryInfo.new(scope: scope, origin: origin, key: key, value: value)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Parse `git config --list --show-scope --show-origin --null` output
|
|
64
|
+
#
|
|
65
|
+
# Also handles `--get-regexp` output, which shares the same format.
|
|
66
|
+
#
|
|
67
|
+
# Output format (per entry): `scope\0origin\0key\nvalue\0`
|
|
68
|
+
#
|
|
69
|
+
# Multiple entries appear back-to-back in the same string.
|
|
70
|
+
#
|
|
71
|
+
# @param output [String] raw stdout from the command
|
|
72
|
+
#
|
|
73
|
+
# @return [Array<Git::ConfigEntryInfo>] the parsed entries
|
|
74
|
+
#
|
|
75
|
+
def parse_list(output)
|
|
76
|
+
return [] if output.empty?
|
|
77
|
+
|
|
78
|
+
tokens = output.split("\0", -1)
|
|
79
|
+
tokens.pop if tokens.last == ''
|
|
80
|
+
tokens.each_slice(3).map do |scope, origin, key_value|
|
|
81
|
+
key, value = key_value.split("\n", 2)
|
|
82
|
+
Git::ConfigEntryInfo.new(scope: scope, origin: origin, key: key, value: value || '')
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Parse `git config --get-urlmatch --show-scope --null` output
|
|
87
|
+
#
|
|
88
|
+
# Handles the two-field-per-entry format produced by `--get-urlmatch` when
|
|
89
|
+
# `--show-scope` is used but `--show-origin` is not (git does not support
|
|
90
|
+
# `--show-origin` with `--get-urlmatch`).
|
|
91
|
+
#
|
|
92
|
+
# Output format (per entry): `scope\0key\nvalue\0`
|
|
93
|
+
#
|
|
94
|
+
# @param output [String] raw stdout from the command
|
|
95
|
+
#
|
|
96
|
+
# @return [Array<Git::ConfigEntryInfo>] the parsed entries; `origin` is `nil` for each
|
|
97
|
+
#
|
|
98
|
+
def parse_urlmatch(output)
|
|
99
|
+
return [] if output.empty?
|
|
100
|
+
|
|
101
|
+
tokens = output.split("\0", -1)
|
|
102
|
+
tokens.pop if tokens.last == ''
|
|
103
|
+
tokens.each_slice(2).map do |scope, key_value|
|
|
104
|
+
key, value = key_value.split("\n", 2)
|
|
105
|
+
Git::ConfigEntryInfo.new(scope: scope, origin: nil, key: key, value: value || '')
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|