Module: Git::Parsers::Branch Private

Defined in:
lib/git/parsers/branch.rb

Overview

This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.

Parser for git branch command output

Handles parsing of git branch --list and git branch --delete output into structured data objects.

Design Note: Namespace Organization

This parser creates and returns BranchInfo and BranchDeleteResult objects, which live at the top-level Git:: namespace rather than within Git::Parsers::. This is intentional:

  • Parsers are infrastructure - marked @api private, users shouldn't interact with them directly
  • Info/Result classes are public API - returned by commands and used throughout the codebase
  • Info classes are domain entities - represent core git concepts (branches as data)
  • Result classes are operation outcomes - represent command results, not parsing details

Keeping Info/Result classes at Git:: improves discoverability and correctly reflects their role as public types rather than parser internals.

Constant Summary collapse

FORMAT_STRING =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Format string for git branch --format

Fields (null-delimited):

  1. refname - full ref name (e.g., refs/heads/main, refs/remotes/origin/main)
  2. objectname - full SHA of the commit the branch points to
  3. HEAD - '*' if current branch, empty otherwise
  4. worktreepath - path if checked out in another worktree, empty otherwise
  5. symref - target ref if symbolic reference, empty otherwise
  6. upstream - full upstream ref (e.g., refs/remotes/origin/main), empty if none

Null bytes (%00) are used as field delimiters so that worktree paths containing special characters (including '|') parse correctly.

'%(refname)%00%(objectname)%00%(HEAD)%00%(worktreepath)%00%(symref)%00%(upstream)'
FIELD_DELIMITER =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Delimiter used in format output

"\0"
DELETED_BRANCH_REGEX =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Regex to parse successful deletion lines from stdout Matches: Deleted branch branchname (was abc123). Matches: Deleted remote-tracking branch origin/branchname (was abc123). Uses non-greedy match to capture branch names containing spaces

/^Deleted (?:remote-tracking )?branch (.+?) \(was/
ERROR_BRANCH_REGEX =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Regex to parse error messages from stderr Matches: error: branch 'branchname' not found.

/^error: branch '([^']+)'(.*)$/

Class Method Summary collapse

Class Method Details

.build_branch_info(fields, remote_names: []) ⇒ Git::BranchInfo

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Build a BranchInfo from parsed fields

Parameters:

  • fields (Array<String>)

    the parsed fields: [refname, objectname, head, worktreepath, symref, upstream]

  • remote_names (Array<String>) (defaults to: [])

    configured remote names used to resolve remote-tracking refs with slash-containing remote names

Returns:



117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/git/parsers/branch.rb', line 117

def build_branch_info(fields, remote_names: [])
  raw_refname, objectname, head, worktreepath, symref, upstream = fields
  Git::BranchInfo.new(
    refname: raw_refname,
    target_oid: presence(objectname),
    current: head == '*',
    worktree_path: head == '*' ? nil : presence(worktreepath),
    symref: presence(symref),
    upstream: build_upstream_info(upstream),
    remote_name: resolve_remote_name(raw_refname, remote_names)
  )
end

.build_delete_result(requested_names, existing_branches, deleted_names, error_map) ⇒ Git::BranchDeleteResult

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Build the BranchDeleteResult from parsed data

Parameters:

  • requested_names (Array<String>)

    originally requested branch names

  • existing_branches (Hash<String, Git::BranchInfo>)

    branches that existed before delete

  • deleted_names (Array<String>)

    names confirmed deleted in stdout

  • error_map (Hash<String, String>)

    map of branch name to error message

Returns:



228
229
230
231
232
233
234
235
236
237
# File 'lib/git/parsers/branch.rb', line 228

def build_delete_result(requested_names, existing_branches, deleted_names, error_map)
  deleted = deleted_names.filter_map { |name| existing_branches[name] }

  not_deleted = (requested_names - deleted_names).map do |name|
    error_message = error_map[name] || "branch '#{name}' could not be deleted"
    Git::BranchDeleteFailure.new(name: name, error_message: error_message)
  end

  Git::BranchDeleteResult.new(deleted: deleted, not_deleted: not_deleted)
end

.build_upstream_info(upstream_ref) ⇒ String?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return the raw upstream refname string, or nil if empty

Parameters:

  • upstream_ref (String, nil)

    the upstream ref (e.g., 'refs/remotes/origin/main')

Returns:

  • (String, nil)

    the raw upstream refname, or nil



169
170
171
172
173
# File 'lib/git/parsers/branch.rb', line 169

def build_upstream_info(upstream_ref)
  return nil if upstream_ref.nil? || upstream_ref.empty?

  upstream_ref
end

.non_branch_entry?(refname) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Check if the refname represents a detached HEAD state or non-branch entry

Git outputs special entries for detached HEAD and non-branch states:

  • "(HEAD detached at )" when in detached HEAD state
  • "(not a branch)" for non-branch entries

Parameters:

  • refname (String)

    the refname to check

Returns:

  • (Boolean)

    true if this is a non-branch entry



159
160
161
# File 'lib/git/parsers/branch.rb', line 159

def non_branch_entry?(refname)
  refname.match?(/^\(HEAD detached/) || refname.match?(/^\(not a branch\)/)
end

.parse_branch_line(line, remote_names: []) ⇒ Git::BranchInfo?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parse a single formatted branch line

Parameters:

  • line (String)

    the line to parse (NUL-delimited fields)

  • remote_names (Array<String>) (defaults to: [])

    configured remote names used to resolve remote-tracking refs with slash-containing remote names

Returns:

  • (Git::BranchInfo, nil)

    branch info object, or nil if line should be skipped



99
100
101
102
103
104
105
# File 'lib/git/parsers/branch.rb', line 99

def parse_branch_line(line, remote_names: [])
  fields = line.split(FIELD_DELIMITER, 6)

  return nil if non_branch_entry?(fields[0])

  build_branch_info(fields, remote_names: remote_names)
end

.parse_deleted_branches(stdout) ⇒ Array<String>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parse deleted branch names from stdout

Examples:

BranchParser.parse_deleted_branches("Deleted branch feature (was abc123).\n")
# => ["feature"]

Parameters:

  • stdout (String)

    command stdout

Returns:

  • (Array<String>)

    names of successfully deleted branches



195
196
197
# File 'lib/git/parsers/branch.rb', line 195

def parse_deleted_branches(stdout)
  stdout.scan(DELETED_BRANCH_REGEX).flatten
end

.parse_error_messages(stderr) ⇒ Hash<String, String>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parse error messages from stderr into a map

Examples:

BranchParser.parse_error_messages("error: branch 'missing' not found.\n")
# => {"missing" => "error: branch 'missing' not found."}

Parameters:

  • stderr (String)

    command stderr

Returns:

  • (Hash<String, String>)

    map of branch name to error message



209
210
211
212
213
214
# File 'lib/git/parsers/branch.rb', line 209

def parse_error_messages(stderr)
  stderr.each_line.with_object({}) do |line, hash|
    match = line.match(ERROR_BRANCH_REGEX)
    hash[match[1]] = line.strip if match
  end
end

.parse_list(stdout, remote_names: []) ⇒ Array<Git::BranchInfo>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parse git branch --list output into BranchInfo objects

Examples:

Parse NUL-delimited branch list output

Git::Parsers::Branch.parse_list(
  "refs/heads/main\0abc1234\0*\0\0\0\n" \
  "refs/heads/feature\0def5678\0\0\0\0\n"
)
# => [#<data Git::BranchInfo refname="refs/heads/main", ...>,
#     #<data Git::BranchInfo refname="refs/heads/feature", ...>]

Parameters:

  • stdout (String)

    output from git branch --list --format=...

  • remote_names (Array<String>) (defaults to: [])

    configured remote names used to resolve remote-tracking refs with slash-containing remote names

Returns:



86
87
88
# File 'lib/git/parsers/branch.rb', line 86

def parse_list(stdout, remote_names: [])
  stdout.split("\n").filter_map { |line| parse_branch_line(line, remote_names: remote_names) }
end

.presence(value) ⇒ String?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return value if non-empty, nil otherwise

Parameters:

  • value (String, nil)

    the value to check

Returns:

  • (String, nil)

    the value or nil



181
182
183
# File 'lib/git/parsers/branch.rb', line 181

def presence(value)
  value.nil? || value.empty? ? nil : value
end

.resolve_remote_name(refname, remote_names) ⇒ String?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Resolve a remote-tracking refname to a configured remote name

Parameters:

  • refname (String)

    the branch refname to inspect

  • remote_names (Array<String>)

    configured remote names

Returns:

  • (String, nil)

    the resolved remote name, or nil for local branches



138
139
140
141
142
143
144
145
146
147
# File 'lib/git/parsers/branch.rb', line 138

def resolve_remote_name(refname, remote_names)
  remote_path = refname[%r{\A(?:refs/)?remotes/(.+)\z}, 1]
  return nil if remote_path.nil?

  configured_remote_name = remote_names
                           .select { |remote_name| remote_path.start_with?("#{remote_name}/") }
                           .max_by(&:length)

  configured_remote_name || Git::BranchInfo.fallback_remote_name(refname)
end