Module: Git::Parsers::Tag Private
- Defined in:
- lib/git/parsers/tag.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.
Known limitation: If a tag message contains the field delimiter character (\x1f, ASCII unit separator), it will be preserved correctly since the message is the last field. However, messages are rarely crafted with non-printable control characters.
Parser for git tag command output
Handles parsing of git tag --list and git tag --delete output
into structured data objects.
Design Note: Namespace Organization
This parser creates and returns TagInfo and TagDeleteResult
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 (tags 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
- 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 for separating fields in git tag --format output Field separator used in custom format output Using the ASCII unit separator (US, 0x1F / "\x1f"), a non-printable character, minimizes the chance of collisions with tag names or messages and remains safe to pass through Process.spawn and shell argument boundaries.
"\x1f"- RECORD_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 for separating records (tags) in output Using the ASCII record separator (RS, 0x1E / "\x1e") to delimit complete tag records. This allows multi-line messages (which contain newlines) to be parsed correctly since we split by record separator first, then by field delimiter.
"\x1e"- 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
8- 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 tag --format
Fields:
- %(refname:short) - tag name
- %(objectname) - SHA of the tag object (for annotated) or commit (for lightweight)
- %(*objectname) - Dereferenced SHA (commit ID for annotated tags, empty for lightweight)
- %(objecttype) - 'tag' for annotated tags, target object type (commit/tree/blob/etc.) for lightweight tags
- %(taggername) - tagger name (empty for lightweight tags)
- %(taggeremail) - tagger email (empty for lightweight tags)
- %(taggerdate:iso8601-strict) - tagger date in strict ISO 8601 format
- %(contents) - full tag message (can be multi-line)
Each tag record is terminated by the RECORD_DELIMITER to allow multi-line messages.
[ '%(refname:short)', '%(objectname)', '%(*objectname)', '%(objecttype)', '%(taggername)', '%(taggeremail)', '%(taggerdate:iso8601-strict)', '%(contents)' ].join(FIELD_DELIMITER) + RECORD_DELIMITER
- DELETED_TAG_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 tag 'tagname' (was abc123)
/^Deleted tag '([^']+)'/- ERROR_TAG_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: tag 'tagname' not found.
/^error: tag '([^']+)'(.*)$/
Class Method Summary collapse
-
.build_delete_result(requested_names, existing_tags, deleted_names, error_map) ⇒ Git::TagDeleteResult
private
Build the TagDeleteResult from parsed data.
-
.build_tag_info(parts) ⇒ Git::TagInfo
private
Build a TagInfo object from parsed parts.
-
.build_tag_info_object(parts, oid, target_oid) ⇒ Git::TagInfo
private
Builds a TagInfo object from normalized parser values.
-
.parse_date(date) ⇒ Time
private
Parse a
%(taggerdate:iso8601-strict)field into a Time. -
.parse_deleted_tags(stdout) ⇒ Array<String>
private
Parse deleted tag names from stdout.
-
.parse_error_messages(stderr) ⇒ Hash<String, String>
private
Parse error messages from stderr into a map.
-
.parse_list(stdout) ⇒ Array<Git::TagInfo>
private
Parse git tag --list output into TagInfo objects.
-
.parse_message(objecttype, message) ⇒ String?
private
Parse message field, returning nil for lightweight tags or empty messages Strips trailing newlines that git adds to %(contents) output.
-
.parse_tag_record(record, index, all_records) ⇒ Git::TagInfo
private
Parse a single formatted tag record.
-
.parse_tagger(name, email, date) ⇒ Git::AuthorInfo?
private
Build the tagger identity from the tagger name, email, and date fields.
-
.resolve_oids(objecttype, objectname, dereferenced) ⇒ Array((String, nil), String)
private
Resolves canonical and target object OIDs from git tag format fields.
-
.unexpected_tag_record_error(records, record, index) ⇒ String
private
Generate error message for unexpected tag record format.
Class Method Details
.build_delete_result(requested_names, existing_tags, deleted_names, error_map) ⇒ Git::TagDeleteResult
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 TagDeleteResult from parsed data
304 305 306 307 308 309 310 311 312 313 |
# File 'lib/git/parsers/tag.rb', line 304 def build_delete_result(requested_names, , deleted_names, error_map) deleted = deleted_names.filter_map { |name| [name] } not_deleted = (requested_names - deleted_names).map do |name| = error_map[name] || "tag '#{name}' could not be deleted" Git::TagDeleteFailure.new(name: name, error_message: ) end Git::TagDeleteResult.new(deleted: deleted, not_deleted: not_deleted) end |
.build_tag_info(parts) ⇒ Git::TagInfo
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.
For annotated tags:
- oid = %(objectname) (the tag object's ID)
- target_oid = %(*objectname) (the dereferenced commit ID)
For lightweight tags:
- oid = nil (lightweight tags are not objects)
- target_oid = %(objectname) (the commit ID)
Build a TagInfo object from parsed parts
156 157 158 159 |
# File 'lib/git/parsers/tag.rb', line 156 def build_tag_info(parts) oid, target_oid = resolve_oids(parts[3], parts[1], parts[2]) build_tag_info_object(parts, oid, target_oid) end |
.build_tag_info_object(parts, oid, target_oid) ⇒ Git::TagInfo
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.
Builds a TagInfo object from normalized parser values
186 187 188 189 190 191 |
# File 'lib/git/parsers/tag.rb', line 186 def build_tag_info_object(parts, oid, target_oid) Git::TagInfo.new( name: parts[0], oid: oid, target_oid: target_oid, objecttype: parts[3], tagger: parse_tagger(parts[4], parts[5], parts[6]), message: (parts[3], parts[7]) ) 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 %(taggerdate:iso8601-strict) field into a Time
240 241 242 243 244 245 |
# File 'lib/git/parsers/tag.rb', line 240 def parse_date(date) Time.iso8601(date) rescue ArgumentError => e raise Git::UnexpectedResultError, "Unexpected tagger date #{date.inspect} in output from `git tag --list`: #{e.}" end |
.parse_deleted_tags(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 tag names from stdout
271 272 273 |
# File 'lib/git/parsers/tag.rb', line 271 def (stdout) stdout.scan(DELETED_TAG_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
285 286 287 288 289 290 |
# File 'lib/git/parsers/tag.rb', line 285 def (stderr) stderr.each_line.with_object({}) do |line, hash| match = line.match(ERROR_TAG_REGEX) hash[match[1]] = line.strip if match end end |
.parse_list(stdout) ⇒ Array<Git::TagInfo>
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 tag --list output into TagInfo objects
105 106 107 108 109 110 111 |
# File 'lib/git/parsers/tag.rb', line 105 def parse_list(stdout) # Split by record separator # Each record may have a leading newline from the previous record's %(contents) output # Use lstrip to remove leading whitespace (which includes the newline) from each record records = stdout.split(RECORD_DELIMITER).map(&:lstrip).reject(&:empty?) records.map.with_index { |record, index| parse_tag_record(record, index, records) } end |
.parse_message(objecttype, 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.
Parse message field, returning nil for lightweight tags or empty messages Strips trailing newlines that git adds to %(contents) output
256 257 258 259 |
# File 'lib/git/parsers/tag.rb', line 256 def (objecttype, ) stripped = .chomp objecttype == 'tag' && !stripped.empty? ? stripped : nil end |
.parse_tag_record(record, index, all_records) ⇒ Git::TagInfo
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 tag record
The record format is:
name
For lightweight tags, Git emits empty strings for the tagger fields and message; these are converted to nil by #parse_tagger and #parse_message.
132 133 134 135 136 137 138 139 140 |
# File 'lib/git/parsers/tag.rb', line 132 def parse_tag_record(record, index, all_records) parts = record.split(FIELD_DELIMITER, FIELD_COUNT) unless parts.length == FIELD_COUNT raise Git::UnexpectedResultError, unexpected_tag_record_error(all_records, record, index) end build_tag_info(parts) end |
.parse_tagger(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 the tagger identity from the tagger name, email, and date fields
Git emits empty strings for all three fields when there is no tag object
(lightweight tags) or the tag object has no tagger header, in which case
the tagger is nil. Otherwise the angle brackets git wraps around
%(taggeremail) are stripped and the strict ISO 8601
%(taggerdate:iso8601-strict) value is parsed into a Time that
preserves the UTC offset. A partially populated identity (for example an
empty name with an email and date) is kept as emitted rather than dropped,
and an empty date becomes nil.
222 223 224 225 226 227 228 229 230 |
# File 'lib/git/parsers/tag.rb', line 222 def parse_tagger(name, email, date) return nil if [name, email, date].all?(&:empty?) Git::AuthorInfo.new( name: name, email: email.delete_prefix('<').delete_suffix('>'), date: date.empty? ? nil : parse_date(date) ) end |
.resolve_oids(objecttype, objectname, dereferenced) ⇒ Array((String, nil), 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.
Resolves canonical and target object OIDs from git tag format fields
172 173 174 |
# File 'lib/git/parsers/tag.rb', line 172 def resolve_oids(objecttype, objectname, dereferenced) objecttype == 'tag' ? [objectname, dereferenced] : [nil, objectname] end |
.unexpected_tag_record_error(records, record, 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 tag record format
325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/git/parsers/tag.rb', line 325 def unexpected_tag_record_error(records, record, index) format_str = FORMAT_STRING.gsub(FIELD_DELIMITER, '<FS>').gsub(RECORD_DELIMITER, '<RS>') <<~ERROR Unexpected record in output from `git tag --list --format=#{format_str}`, at index #{index} Expected #{FIELD_COUNT} fields separated by '\\x1f' (unit separator), got #{record.split(FIELD_DELIMITER, -1).length} Full output: #{records.join("\n ")} Record at index #{index}: "#{record}" ERROR end |