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) ⇒ Hash<Symbol, 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.

Build author-related StashInfo attributes from parsed fields

Parameters:

  • parts (Array<String>)

    the parsed format fields

Returns:

  • (Hash<Symbol, String>)

    author attributes for StashInfo.new



191
192
193
194
195
196
# File 'lib/git/parsers/stash.rb', line 191

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:



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

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) ⇒ Hash<Symbol, 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.

Build committer-related StashInfo attributes from parsed fields

Parameters:

  • parts (Array<String>)

    the parsed format fields

Returns:

  • (Hash<Symbol, String>)

    committer attributes for StashInfo.new



204
205
206
207
208
209
# File 'lib/git/parsers/stash.rb', line 204

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) ⇒ Hash<Symbol, Object>

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 core StashInfo attributes from parsed fields

Parameters:

  • parts (Array<String>)

    the parsed format fields

  • index (Integer)

    the resolved stash index

Returns:

  • (Hash<Symbol, Object>)

    core attributes for StashInfo.new



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

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



228
229
230
231
# File 'lib/git/parsers/stash.rb', line 228

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



217
218
219
220
# File 'lib/git/parsers/stash.rb', line 217

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:



119
120
121
122
# File 'lib/git/parsers/stash.rb', line 119

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:



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

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



165
166
167
# File 'lib/git/parsers/stash.rb', line 165

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



243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/git/parsers/stash.rb', line 243

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