Module: Git::Parsers::Stash Private

Defined in:
lib/git/parsers/stash.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.

Note:

Known limitation: If a stash message contains the field separator character (\x1f, ASCII unit separator), parsing will fail or produce incorrect results. This is extremely rare in practice since \x1f is a non-printable control character.

Parser for git stash command output

Handles parsing of git stash list output into structured data objects.

Design Note: Namespace Organization

This parser creates and returns StashInfo 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 classes are public API - returned by commands and used throughout the codebase
  • Info classes are domain entities - represent core git concepts (stashes as data)

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

Defined Under Namespace

Modules: Fields

Constant Summary collapse

FIELD_SEPARATOR =

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.

Field separator used in custom format output Using a non-printable unit separator (US, 0x1F) to avoid collisions with stash messages and author/committer fields, while still working with Process.spawn (which doesn't allow NUL bytes in arguments)

"\x1f"
STASH_FORMAT =

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.

Custom format for git stash list that extracts all available metadata %H = full commit SHA %h = abbreviated commit SHA %gd = reflog selector (stash@{n}) %gs = reflog subject (the stash message) %an = author name %ae = author email %aI = author date (ISO 8601 format) %cn = committer name %ce = committer email %cI = committer date (ISO 8601 format)

[
  '%H',  # 0: full SHA
  '%h',  # 1: short SHA
  '%gd', # 2: reflog selector
  '%gs', # 3: reflog subject (message)
  '%an', # 4: author name
  '%ae', # 5: author email
  '%aI', # 6: author date
  '%cn', # 7: committer name
  '%ce', # 8: committer email
  '%cI'  # 9: committer date
].join(FIELD_SEPARATOR)
FIELD_COUNT =

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.

Number of fields expected in the parsed output

10
BRANCH_PATTERN =

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.

Pattern to extract branch from standard stash messages Matches "WIP on :" or "On :" at the start

/^(?:WIP on|On)\s+([^:]+):/

Class Method Summary collapse

Class Method Details

.author_attrs(parts)

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.



151
152
153
154
155
156
# File 'lib/git/parsers/stash.rb', line 151

def author_attrs(parts)
  {
    author_name: parts[Fields::AUTHOR_NAME], author_email: parts[Fields::AUTHOR_EMAIL],
    author_date: parts[Fields::AUTHOR_DATE]
  }
end

.build_stash_info(parts, expected_index) ⇒ Git::StashInfo

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 StashInfo from parsed format parts

Parameters:

  • parts (Array<String>)

    the parsed format fields

  • expected_index (Integer)

    fallback index if not parseable from reflog

Returns:



127
128
129
130
131
# File 'lib/git/parsers/stash.rb', line 127

def build_stash_info(parts, expected_index)
  index = extract_index(parts[Fields::REFLOG]) || expected_index

  Git::StashInfo.new(**stash_info_attrs(parts, index))
end

.committer_attrs(parts)

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.



158
159
160
161
162
163
# File 'lib/git/parsers/stash.rb', line 158

def committer_attrs(parts)
  {
    committer_name: parts[Fields::COMMITTER_NAME], committer_email: parts[Fields::COMMITTER_EMAIL],
    committer_date: parts[Fields::COMMITTER_DATE]
  }
end

.core_attrs(parts, index)

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.



143
144
145
146
147
148
149
# File 'lib/git/parsers/stash.rb', line 143

def core_attrs(parts, index)
  {
    index: index, name: parts[Fields::REFLOG], oid: parts[Fields::OID],
    short_oid: parts[Fields::SHORT_OID], branch: extract_branch(parts[Fields::MESSAGE]),
    message: parts[Fields::MESSAGE]
  }
end

.extract_branch(message) ⇒ 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.

Extract the branch name from a stash message

Parameters:

  • message (String)

    the stash message

Returns:

  • (String, nil)

    the branch name or nil for custom messages



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

def extract_branch(message)
  match = BRANCH_PATTERN.match(message)
  match ? match[1] : nil
end

.extract_index(reflog_selector) ⇒ Integer?

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.

Extract the stash index from a reflog selector

Parameters:

  • reflog_selector (String)

    e.g., "stash@{0}"

Returns:

  • (Integer, nil)

    the index or nil if not found



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

def extract_index(reflog_selector)
  match = reflog_selector&.match(/stash@\{(\d+)\}/)
  match ? match[1].to_i : nil
end

.parse_list(stdout) ⇒ Array<Git::StashInfo>

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 stash list output into StashInfo objects

Examples:

StashParser.parse_list("abc123\x1fabc\x1fstash@\\{0}\x1fWIP on main: msg\x1f...\n")
# => [#<Git::StashInfo index: 0, ...>]

Parameters:

  • stdout (String)

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

Returns:

Raises:



99
100
101
102
# File 'lib/git/parsers/stash.rb', line 99

def parse_list(stdout)
  lines = stdout.split("\n")
  lines.each_with_index.map { |line, idx| parse_stash_line(line, idx, lines) }
end

.parse_stash_line(line, expected_index, all_lines) ⇒ Git::StashInfo

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 stash list line into a StashInfo object

Parameters:

  • line (String)

    a line from git stash list output (custom format)

  • expected_index (Integer)

    the expected stash index for validation

  • all_lines (Array<String>)

    all output lines (for error messages)

Returns:

Raises:



114
115
116
117
118
119
# File 'lib/git/parsers/stash.rb', line 114

def parse_stash_line(line, expected_index, all_lines)
  parts = line.split(FIELD_SEPARATOR, FIELD_COUNT)
  return build_stash_info(parts, expected_index) if parts.length == FIELD_COUNT

  raise Git::UnexpectedResultError, unexpected_stash_line_error(all_lines, line, expected_index)
end

.stash_info_attrs(parts, index) ⇒ Hash

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 StashInfo attributes hash from parsed parts

Parameters:

  • parts (Array<String>)

    the parsed format fields

  • index (Integer)

    the resolved stash index

Returns:

  • (Hash)

    attributes for StashInfo.new



139
140
141
# File 'lib/git/parsers/stash.rb', line 139

def stash_info_attrs(parts, index)
  core_attrs(parts, index).merge(author_attrs(parts)).merge(committer_attrs(parts))
end

.unexpected_stash_line_error(lines, line, index) ⇒ 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.

Generate error message for unexpected stash line format

Parameters:

  • lines (Array<String>)

    all output lines

  • line (String)

    the problematic line

  • index (Integer)

    the stash index

Returns:

  • (String)

    formatted error message



192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/git/parsers/stash.rb', line 192

def unexpected_stash_line_error(lines, line, index)
  format_str = STASH_FORMAT.gsub(FIELD_SEPARATOR, '<FS>')
  <<~ERROR
    Unexpected line in output from `git stash list --format=#{format_str}`, at index #{index}

    Expected #{FIELD_COUNT} fields separated by '\\x1f' (unit separator), got #{line.split(FIELD_SEPARATOR, -1).length}

    Full output:
      #{lines.join("\n  ")}

    Line at index #{index}:
      "#{line}"
  ERROR
end