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.

API:

  • private

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)

API:

  • private

"\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, parsed into a Time) %cn = committer name %ce = committer email %cI = committer date (ISO 8601 format, parsed into a Time)

API:

  • private

[
  '%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

API:

  • private

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

API:

  • private

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

Class Method Summary collapse

Class Method Details

.author_info(parts) ⇒ Git::AuthorInfo

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 author identity from the parsed fields

Parameters:

  • the parsed format fields

Returns:

  • the stash author; its date is a Time

API:

  • private



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

def author_info(parts)
  build_author_info(parts[Fields::AUTHOR_NAME], parts[Fields::AUTHOR_EMAIL], parts[Fields::AUTHOR_DATE])
end

.build_author_info(name, email, date) ⇒ Git::AuthorInfo

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 Git::AuthorInfo from identity fields

The date is parsed with Time.iso8601, so the UTC offset git emits for %aI and %cI is preserved in the resulting Time.

Parameters:

  • the %an or %cn field

  • the %ae or %ce field

  • the %aI or %cI field in ISO 8601 format

Returns:

  • the identity with date as a Time

Raises:

  • if the date is not a valid ISO 8601 date

API:

  • private



225
226
227
# File 'lib/git/parsers/stash.rb', line 225

def build_author_info(name, email, date)
  Git::AuthorInfo.new(name: name, email: email, date: parse_date(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:

  • the parsed format fields

  • fallback index if not parseable from reflog

Returns:

API:

  • private



154
155
156
157
158
# File 'lib/git/parsers/stash.rb', line 154

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_info(parts) ⇒ Git::AuthorInfo

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 committer identity from the parsed fields

Parameters:

  • the parsed format fields

Returns:

  • the stash committer; its date is a Time

API:

  • private



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

def committer_info(parts)
  build_author_info(
    parts[Fields::COMMITTER_NAME], parts[Fields::COMMITTER_EMAIL], 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:

  • the parsed format fields

  • the resolved stash index

Returns:

  • core attributes for StashInfo.new

API:

  • private



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

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:

  • the stash message

Returns:

  • the branch name or nil for custom messages

API:

  • private



261
262
263
264
# File 'lib/git/parsers/stash.rb', line 261

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:

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

Returns:

  • the index or nil if not found

API:

  • private



250
251
252
253
# File 'lib/git/parsers/stash.rb', line 250

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

.parse_date(date) ⇒ Time

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 %aI or %cI field into a Time

Parameters:

  • the date field in ISO 8601 format

Returns:

  • the parsed time, preserving the UTC offset

Raises:

  • if the field is not a valid ISO 8601 date

API:

  • private



237
238
239
240
241
242
# File 'lib/git/parsers/stash.rb', line 237

def parse_date(date)
  Time.iso8601(date)
rescue ArgumentError => e
  raise Git::UnexpectedResultError,
        "Unexpected date #{date.inspect} in output from `git stash list`: #{e.message}"
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:

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

Returns:

  • parsed stash information

Raises:

  • if stash output cannot be parsed

API:

  • private



122
123
124
125
# File 'lib/git/parsers/stash.rb', line 122

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:

  • a line from git stash list output (custom format)

  • the expected stash index for validation

  • all output lines (for error messages)

Returns:

  • parsed stash info

Raises:

  • if line format is unexpected

API:

  • private



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

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:

  • the parsed format fields

  • the resolved stash index

Returns:

  • attributes for StashInfo.new

API:

  • private



168
169
170
# File 'lib/git/parsers/stash.rb', line 168

def stash_info_attrs(parts, index)
  core_attrs(parts, index).merge(author: author_info(parts), committer: committer_info(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:

  • all output lines

  • the problematic line

  • the stash index

Returns:

  • formatted error message

API:

  • private



276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/git/parsers/stash.rb', line 276

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