Artifact 83dd64c11cb80892269737f478b24cba04246f33c95b1af8d4078647809346ca:
- File
src/git_fast_export.zig
— part of check-in
[85d510d361]
at
2026-07-31 02:28:47
on branch sub-manifest
— fast-export: derive the per-commit file-command ceiling
Limits.max_file_commands_per_commit was a standalone number, and the caller pinned it to the width of our own benchmark corpus. That is a fixture size promoted to policy: no headroom, no way to raise it without a rebuild, and it refused legitimate histories. `git fast-export` lists an entire tree for a root commit and for any commit whose first parent was not exported, so the ceiling is reached by ordinary repositories whose initial commit lands a whole codebase at once.
The bound itself is necessary. admitData is the parser's only byte admission and it sees the byte count in a `data` header alone; every file command additionally allocates a list slot and a duplicated path into the per- commit arena with nothing charging for them, so the command loop is otherwise unbounded against a subprocess pipe.
It is now computed: fileCommandCeiling(limits) = max_total_data divided by min_retained_bytes_per_file_command, where that constant is @sizeOf(FileCommand) plus one -- a command's list slot plus the shortest path it can carry, the empty path being rejected. Nothing smaller is representable, which is what makes it safe to divide a byte budget by. The ceiling restates the arena authorization the caller already granted, in commands rather than bytes, and moves when that authorization moves.
Refusal retains the observed count, the ceiling, and the budget that produced it, so the diagnostic names all three rather than only its own error. The remaining limits gain distinct names -- Data, Event, FileCommand, Line, Merge and Path -- in place of one LimitExceeded that could not say which bound was hit. Exact-count data may carry an optional trailing LF, so only the first following empty line is ambiguous with the commit terminator; readDataExact and readCommitData split that decision instead of guessing. (user: dmestas, size: 29827) [annotate] [blame] [check-ins using]
const std = @import("std"); pub const Limits = struct { max_line: usize, max_data: u64, max_path: usize, max_merges_per_commit: usize = 1_024, max_events: u64 = 1_000_000, max_total_data: u64 = 1 << 40, }; /// Floor on the bytes one retained file command occupies: its slot in the /// per-commit command list plus the shortest path it can carry. `parsePathToken` /// rejects the empty path, so one byte is that floor. Nothing smaller is /// representable, which is what makes it safe to divide a byte budget by. pub const min_retained_bytes_per_file_command: u64 = @sizeOf(FileCommand) + 1; /// The per-commit file-command ceiling, derived rather than declared. /// /// `max_total_data` is the parser's authorization for bytes it may hold in an /// event arena, but `admitData` charges only `data <n>` payloads against it. /// A file command's list slot and duplicated path are arena bytes no budget /// sees, so an unbounded command loop grows the arena without ever touching /// `max_total_data`. The ceiling closes that gap by restating the same /// authorization in commands: the most whose minimum retention still fits /// inside the budget. It moves when the byte budget moves and at no other time. pub fn fileCommandCeiling(limits: Limits) usize { const ceiling = limits.max_total_data / min_retained_bytes_per_file_command; return std.math.cast(usize, ceiling) orelse std.math.maxInt(usize); } /// The numbers a `FileCommandLimitExceeded` diagnostic must name. The parser /// has no output channel, so it retains them and the caller — which knows the /// flag that raises the budget — does the formatting. pub const FileCommandOverrun = struct { /// The refused commit carried at least this many commands: the ceiling /// plus the one that did not fit. Parsing stops there, so the true count /// is unknown and this is a lower bound, never an estimate. observed_at_least: usize, ceiling: usize, payload_budget_bytes: u64, min_bytes_per_command: u64 = min_retained_bytes_per_file_command, }; pub const Error = error{ ActiveLease, Malformed, DataLimitExceeded, EventLimitExceeded, FileCommandLimitExceeded, LineLimitExceeded, MergeLimitExceeded, PathLimitExceeded, MissingDone, UnexpectedEof, TrailingData, UnsupportedFeature, UnsupportedMode, UnknownMode, OutOfMemory, Io, }; pub const ObjectRef = union(enum) { mark: u64, oid: []const u8, }; pub const FileMode = enum { regular, executable, symlink, }; pub const FileCommand = union(enum) { modify: struct { mode: FileMode, data: ObjectRef, path: []const u8, }, delete: []const u8, rename: struct { old: []const u8, new: []const u8, }, copy: struct { old: []const u8, new: []const u8, }, delete_all, }; pub const Identity = struct { name: []const u8, email: []const u8, timestamp: i64, timezone_offset_minutes: i16, }; pub const Blob = struct { mark: u64, original_oid: ?[]const u8, data: []const u8, }; pub const Commit = struct { ref: []const u8, mark: u64, original_oid: ?[]const u8, author: Identity, committer: Identity, message: []const u8, from: ?ObjectRef, merges: []const ObjectRef, commands: []const FileCommand, }; pub const Reset = struct { ref: []const u8, target: ?ObjectRef, }; pub const Tag = struct { name: []const u8, mark: ?u64, target: ObjectRef, original_oid: ?[]const u8, tagger: Identity, message: []const u8, }; pub const Event = union(enum) { blob: Blob, commit: Commit, reset: Reset, tag: Tag, done, }; pub const EventLease = struct { value: Event, reader: *Reader, released: bool = false, pub fn deinit(self: *EventLease) void { if (self.released) return; self.released = true; self.reader.releaseLease(); } }; pub const Reader = struct { gpa: std.mem.Allocator, input: std.Io.Reader, limits: Limits, line: std.ArrayList(u8), event_arena: ?std.heap.ArenaAllocator = null, active_lease: bool = false, feature_done_seen: bool = false, records_started: bool = false, done_seen: bool = false, eof_seen: bool = false, pending_top_level: ?[]u8 = null, events_seen: u64 = 0, data_bytes_seen: u64 = 0, /// Retained by the one bound whose error name cannot carry its own /// numbers. Null until `FileCommandLimitExceeded` is returned. file_command_overrun: ?FileCommandOverrun = null, pub fn init(gpa: std.mem.Allocator, input: std.Io.Reader, limits: Limits) Reader { return .{ .gpa = gpa, .input = input, .limits = limits, .line = .empty, }; } pub fn deinit(self: *Reader) void { if (self.event_arena) |arena| arena.deinit(); self.event_arena = null; self.active_lease = false; self.line.deinit(self.gpa); if (self.pending_top_level) |line| self.gpa.free(line); self.pending_top_level = null; } pub fn next(self: *Reader) Error!?EventLease { if (self.active_lease) return error.ActiveLease; if (self.eof_seen) return null; if (self.done_seen) { try self.requireEof(); self.eof_seen = true; return null; } while (true) { const line = try self.readTopLevelLine(); if (std.mem.startsWith(u8, line, "feature ")) { if (self.records_started or self.feature_done_seen) return error.Malformed; if (!std.mem.eql(u8, line, "feature done")) return error.UnsupportedFeature; self.feature_done_seen = true; continue; } if (std.mem.startsWith(u8, line, "progress ")) { if (!self.feature_done_seen) return error.Malformed; try self.admitEvent(); continue; } if (!self.feature_done_seen) return error.Malformed; const is_event = std.mem.eql(u8, line, "blob") or std.mem.startsWith(u8, line, "commit ") or std.mem.startsWith(u8, line, "reset ") or std.mem.startsWith(u8, line, "tag ") or std.mem.eql(u8, line, "done"); if (!is_event) return error.Malformed; self.records_started = true; try self.admitEvent(); self.event_arena = std.heap.ArenaAllocator.init(self.gpa); errdefer { self.event_arena.?.deinit(); self.event_arena = null; } const allocator = self.eventAllocator(); const event: Event = if (std.mem.eql(u8, line, "blob")) .{ .blob = try self.parseBlob(allocator) } else if (std.mem.startsWith(u8, line, "commit ")) .{ .commit = try self.parseCommit(allocator, line["commit ".len..]) } else if (std.mem.startsWith(u8, line, "reset ")) .{ .reset = try self.parseReset(allocator, line["reset ".len..]) } else if (std.mem.startsWith(u8, line, "tag ")) .{ .tag = try self.parseTag(allocator, line["tag ".len..]) } else if (std.mem.eql(u8, line, "done")) blk: { self.done_seen = true; break :blk .done; } else unreachable; return self.lease(event); } } fn lease(self: *Reader, value: Event) EventLease { self.active_lease = true; return .{ .value = value, .reader = self }; } fn releaseLease(self: *Reader) void { std.debug.assert(self.active_lease); self.event_arena.?.deinit(); self.event_arena = null; self.active_lease = false; } fn eventAllocator(self: *Reader) std.mem.Allocator { return self.event_arena.?.allocator(); } fn admitEvent(self: *Reader) Error!void { const next_events_seen = std.math.add(u64, self.events_seen, 1) catch return error.EventLimitExceeded; if (next_events_seen > self.limits.max_events) return error.EventLimitExceeded; self.events_seen = next_events_seen; } fn admitData(self: *Reader, size: u64) Error!void { const next_data_bytes_seen = std.math.add(u64, self.data_bytes_seen, size) catch return error.DataLimitExceeded; if (next_data_bytes_seen > self.limits.max_total_data) return error.DataLimitExceeded; self.data_bytes_seen = next_data_bytes_seen; } fn readTopLevelLine(self: *Reader) Error![]const u8 { if (self.pending_top_level) |owned| { defer self.gpa.free(owned); self.pending_top_level = null; self.line.clearRetainingCapacity(); self.line.appendSlice(self.gpa, owned) catch return error.OutOfMemory; return self.line.items; } return self.readLine() catch |err| switch (err) { error.EndOfStream => return error.MissingDone, else => |e| return e, }; } fn requiredLine(self: *Reader) Error![]const u8 { return self.readLine() catch |err| switch (err) { error.EndOfStream => return error.UnexpectedEof, else => |e| return e, }; } fn readLine(self: *Reader) (Error || error{EndOfStream})![]const u8 { self.line.clearRetainingCapacity(); while (true) { const byte = self.input.takeByte() catch |err| switch (err) { error.EndOfStream => return error.EndOfStream, error.ReadFailed => return error.Io, }; if (byte == '\n') return self.line.items; if (self.line.items.len == self.limits.max_line) return error.LineLimitExceeded; self.line.append(self.gpa, byte) catch return error.OutOfMemory; } } fn requireEof(self: *Reader) Error!void { _ = self.input.takeByte() catch |err| switch (err) { error.EndOfStream => return, error.ReadFailed => return error.Io, }; return error.TrailingData; } fn duplicate(_: *Reader, allocator: std.mem.Allocator, bytes: []const u8) Error![]const u8 { return allocator.dupe(u8, bytes) catch return error.OutOfMemory; } fn parseBlob(self: *Reader, allocator: std.mem.Allocator) Error!Blob { const mark = try parseMarkLine(try self.requiredLine()); var original_oid: ?[]const u8 = null; while (true) { const line = try self.requiredLine(); if (std.mem.startsWith(u8, line, "original-oid ")) { if (original_oid != null) return error.Malformed; original_oid = try parseOid(allocator, line["original-oid ".len..]); continue; } if (std.mem.startsWith(u8, line, "data ")) { return .{ .mark = mark, .original_oid = original_oid, .data = try self.readData(allocator, line), }; } return error.Malformed; } } fn parseCommit(self: *Reader, allocator: std.mem.Allocator, ref_text: []const u8) Error!Commit { const ref = try self.parseRef(allocator, ref_text); const mark = try parseMarkLine(try self.requiredLine()); var original_oid: ?[]const u8 = null; var author: ?Identity = null; var committer: ?Identity = null; var message: ?[]const u8 = null; while (true) { const line = try self.requiredLine(); if (std.mem.startsWith(u8, line, "original-oid ")) { if (original_oid != null) return error.Malformed; original_oid = try parseOid(allocator, line["original-oid ".len..]); } else if (std.mem.startsWith(u8, line, "author ")) { if (author != null) return error.Malformed; author = try parseIdentity(self, allocator, line["author ".len..]); } else if (std.mem.startsWith(u8, line, "committer ")) { if (committer != null) return error.Malformed; committer = try parseIdentity(self, allocator, line["committer ".len..]); } else if (std.mem.startsWith(u8, line, "data ")) { message = try self.readCommitData(allocator, line); break; } else return error.Malformed; } const parsed_author = author orelse return error.Malformed; const parsed_committer = committer orelse return error.Malformed; const parsed_message = message orelse return error.Malformed; var from: ?ObjectRef = null; var merges: std.ArrayList(ObjectRef) = .empty; var commands: std.ArrayList(FileCommand) = .empty; const file_command_ceiling = fileCommandCeiling(self.limits); // Exact-count data may have an optional trailing LF. Only the first // following empty line is ambiguous with the commit terminator. var first_command_line = true; command_loop: while (true) { var line = try self.requiredLine(); if (line.len == 0) { if (!first_command_line) break; line = self.readLine() catch |err| switch (err) { error.EndOfStream => break :command_loop, else => |e| return e, }; if (line.len == 0) break; if (isTopLevelRecord(line)) { self.pending_top_level = self.gpa.dupe(u8, line) catch return error.OutOfMemory; break; } } first_command_line = false; if (std.mem.startsWith(u8, line, "from ")) { if (from != null) return error.Malformed; from = try parseObjectRef(allocator, line["from ".len..]); } else if (std.mem.startsWith(u8, line, "merge ")) { if (merges.items.len >= self.limits.max_merges_per_commit) return error.MergeLimitExceeded; const merge = try parseObjectRef(allocator, line["merge ".len..]); merges.append(allocator, merge) catch return error.OutOfMemory; } else { if (commands.items.len >= file_command_ceiling) { self.file_command_overrun = .{ .observed_at_least = file_command_ceiling +| 1, .ceiling = file_command_ceiling, .payload_budget_bytes = self.limits.max_total_data, }; return error.FileCommandLimitExceeded; } const command = try self.parseFileCommand(allocator, line); commands.append(allocator, command) catch return error.OutOfMemory; } } return .{ .ref = ref, .mark = mark, .original_oid = original_oid, .author = parsed_author, .committer = parsed_committer, .message = parsed_message, .from = from, .merges = merges.toOwnedSlice(allocator) catch return error.OutOfMemory, .commands = commands.toOwnedSlice(allocator) catch return error.OutOfMemory, }; } fn isTopLevelRecord(line: []const u8) bool { return std.mem.eql(u8, line, "blob") or std.mem.startsWith(u8, line, "commit ") or std.mem.startsWith(u8, line, "reset ") or std.mem.startsWith(u8, line, "tag ") or std.mem.startsWith(u8, line, "progress ") or std.mem.eql(u8, line, "done"); } fn parseReset(self: *Reader, allocator: std.mem.Allocator, ref_text: []const u8) Error!Reset { const ref = try self.parseRef(allocator, ref_text); const line = try self.requiredLine(); if (line.len == 0) return .{ .ref = ref, .target = null }; if (isTopLevelRecord(line)) { self.pending_top_level = self.gpa.dupe(u8, line) catch return error.OutOfMemory; return .{ .ref = ref, .target = null }; } if (!std.mem.startsWith(u8, line, "from ")) return error.Malformed; const target = try parseObjectRef(allocator, line["from ".len..]); const separator = try self.requiredLine(); if (separator.len == 0) return .{ .ref = ref, .target = target }; if (!isTopLevelRecord(separator)) return error.Malformed; self.pending_top_level = self.gpa.dupe(u8, separator) catch return error.OutOfMemory; return .{ .ref = ref, .target = target }; } fn parseTag(self: *Reader, allocator: std.mem.Allocator, name_text: []const u8) Error!Tag { const name = try self.parseRef(allocator, name_text); var mark: ?u64 = null; var target: ?ObjectRef = null; var original_oid: ?[]const u8 = null; var tagger: ?Identity = null; while (true) { const line = try self.requiredLine(); if (std.mem.startsWith(u8, line, "mark ")) { if (mark != null) return error.Malformed; mark = try parseMarkLine(line); } else if (std.mem.startsWith(u8, line, "from ")) { if (target != null) return error.Malformed; target = try parseObjectRef(allocator, line["from ".len..]); } else if (std.mem.startsWith(u8, line, "original-oid ")) { if (original_oid != null) return error.Malformed; original_oid = try parseOid(allocator, line["original-oid ".len..]); } else if (std.mem.startsWith(u8, line, "tagger ")) { if (tagger != null) return error.Malformed; tagger = try parseIdentity(self, allocator, line["tagger ".len..]); } else if (std.mem.startsWith(u8, line, "data ")) { return .{ .name = name, .mark = mark, .target = target orelse return error.Malformed, .original_oid = original_oid, .tagger = tagger orelse return error.Malformed, .message = try self.readData(allocator, line), }; } else return error.Malformed; } } fn parseFileCommand(self: *Reader, allocator: std.mem.Allocator, line: []const u8) Error!FileCommand { if (std.mem.eql(u8, line, "deleteall")) return .delete_all; if (std.mem.startsWith(u8, line, "M ")) return self.parseModify(allocator, line[2..]); if (std.mem.startsWith(u8, line, "D ")) return .{ .delete = try self.parseSinglePath(allocator, line[2..]) }; if (std.mem.startsWith(u8, line, "R ")) return self.parsePair(allocator, line[2..], .rename); if (std.mem.startsWith(u8, line, "C ")) return self.parsePair(allocator, line[2..], .copy); return error.Malformed; } fn parseModify(self: *Reader, allocator: std.mem.Allocator, body: []const u8) Error!FileCommand { const mode_end = std.mem.indexOfScalar(u8, body, ' ') orelse return error.Malformed; const mode = try parseMode(body[0..mode_end]); var index = mode_end + 1; const data_text = takeBareToken(body, &index) orelse return error.Malformed; const data = try parseObjectRef(allocator, data_text); if (index >= body.len or body[index] != ' ') return error.Malformed; index += 1; const path = try self.parsePathToken(allocator, body, &index); if (index != body.len) return error.Malformed; return .{ .modify = .{ .mode = mode, .data = data, .path = path } }; } fn parseSinglePath(self: *Reader, allocator: std.mem.Allocator, body: []const u8) Error![]const u8 { var index: usize = 0; const path = try self.parsePathToken(allocator, body, &index); if (index != body.len) return error.Malformed; return path; } fn parsePair(self: *Reader, allocator: std.mem.Allocator, body: []const u8, comptime kind: enum { rename, copy }) Error!FileCommand { var index: usize = 0; const old = try self.parsePathToken(allocator, body, &index); if (index >= body.len or body[index] != ' ') return error.Malformed; index += 1; const new = try self.parsePathToken(allocator, body, &index); if (index != body.len) return error.Malformed; return switch (kind) { .rename => .{ .rename = .{ .old = old, .new = new } }, .copy => .{ .copy = .{ .old = old, .new = new } }, }; } fn parsePathToken(self: *Reader, allocator: std.mem.Allocator, input: []const u8, index: *usize) Error![]const u8 { if (index.* >= input.len) return error.Malformed; const start = index.*; if (input[start] == '"') { index.* += 1; var escaped = false; while (index.* < input.len) : (index.* += 1) { const byte = input[index.*]; if (escaped) { escaped = false; continue; } if (byte == '\\') { escaped = true; continue; } if (byte == '"') { index.* += 1; return self.decodeQuotedPath(allocator, input[start + 1 .. index.* - 1]); } } return error.Malformed; } while (index.* < input.len and input[index.*] != ' ') : (index.* += 1) {} return self.copyPath(allocator, input[start..index.*]); } fn copyPath(self: *Reader, allocator: std.mem.Allocator, path: []const u8) Error![]const u8 { if (path.len == 0 or std.mem.indexOfScalar(u8, path, 0) != null) return error.Malformed; if (path.len > self.limits.max_path) return error.PathLimitExceeded; return self.duplicate(allocator, path); } fn decodeQuotedPath(self: *Reader, allocator: std.mem.Allocator, encoded: []const u8) Error![]const u8 { var decoded: std.ArrayList(u8) = .empty; var index: usize = 0; while (index < encoded.len) { const byte = encoded[index]; index += 1; if (byte != '\\') { try appendPathByte(self, &decoded, byte); continue; } if (index == encoded.len) return error.Malformed; const escape = encoded[index]; index += 1; const value: u8 = switch (escape) { '"', '\\' => escape, 'a' => '\x07', 'b' => '\x08', 'f' => '\x0c', 'n' => '\n', 'r' => '\r', 't' => '\t', 'v' => '\x0b', '0'...'7' => blk: { if (index + 2 > encoded.len) return error.Malformed; const second = encoded[index]; const third = encoded[index + 1]; if (second < '0' or second > '7' or third < '0' or third > '7') return error.Malformed; index += 2; const octal: u16 = @as(u16, escape - '0') * 64 + @as(u16, second - '0') * 8 + @as(u16, third - '0'); if (octal > @as(u16, std.math.maxInt(u8))) return error.Malformed; break :blk @intCast(octal); }, else => return error.Malformed, }; try appendPathByte(self, &decoded, value); } if (decoded.items.len == 0) return error.Malformed; return decoded.toOwnedSlice(allocator) catch return error.OutOfMemory; } fn parseRef(self: *Reader, allocator: std.mem.Allocator, text: []const u8) Error![]const u8 { if (text.len == 0 or std.mem.indexOfScalar(u8, text, ' ') != null or std.mem.indexOfScalar(u8, text, 0) != null) return error.Malformed; return self.duplicate(allocator, text); } fn readData(self: *Reader, allocator: std.mem.Allocator, line: []const u8) Error![]const u8 { const data = try self.readDataExact(allocator, line); const terminator = self.input.takeByte() catch |err| switch (err) { error.EndOfStream => return error.UnexpectedEof, error.ReadFailed => return error.Io, }; if (terminator != '\n') return error.Malformed; return data; } fn readCommitData(self: *Reader, allocator: std.mem.Allocator, line: []const u8) Error![]const u8 { return self.readDataExact(allocator, line); } fn readDataExact(self: *Reader, allocator: std.mem.Allocator, line: []const u8) Error![]const u8 { if (!std.mem.startsWith(u8, line, "data ")) return error.Malformed; const size = try parseUnsigned(line["data ".len..]); if (size > self.limits.max_data or size > @as(u64, std.math.maxInt(usize))) return error.DataLimitExceeded; try self.admitData(size); const count: usize = @intCast(size); const data = allocator.alloc(u8, count) catch return error.OutOfMemory; self.input.readSliceAll(data) catch |err| switch (err) { error.EndOfStream => return error.UnexpectedEof, error.ReadFailed => return error.Io, }; return data; } }; fn appendPathByte(reader: *Reader, list: *std.ArrayList(u8), byte: u8) Error!void { if (byte == 0) return error.Malformed; if (list.items.len == reader.limits.max_path) return error.PathLimitExceeded; list.append(reader.eventAllocator(), byte) catch return error.OutOfMemory; } fn parseMarkLine(line: []const u8) Error!u64 { if (!std.mem.startsWith(u8, line, "mark :")) return error.Malformed; const mark = try parseUnsigned(line["mark :".len..]); if (mark == 0) return error.Malformed; return mark; } fn parseObjectRef(allocator: std.mem.Allocator, text: []const u8) Error!ObjectRef { if (text.len == 0) return error.Malformed; if (text[0] == ':') return .{ .mark = try parseMark(text) }; return .{ .oid = try parseOid(allocator, text) }; } fn parseMark(text: []const u8) Error!u64 { if (text.len < 2 or text[0] != ':') return error.Malformed; const mark = try parseUnsigned(text[1..]); if (mark == 0) return error.Malformed; return mark; } fn parseOid(allocator: std.mem.Allocator, text: []const u8) Error![]const u8 { if (text.len != 40 and text.len != 64) return error.Malformed; for (text) |byte| if (!std.ascii.isHex(byte)) return error.Malformed; return allocator.dupe(u8, text) catch return error.OutOfMemory; } fn parseUnsigned(text: []const u8) Error!u64 { if (text.len == 0) return error.Malformed; var value: u64 = 0; for (text) |byte| { if (byte < '0' or byte > '9') return error.Malformed; const digit: u64 = byte - '0'; if (value > (std.math.maxInt(u64) - digit) / 10) return error.Malformed; value = value * 10 + digit; } return value; } fn parseSigned(text: []const u8) Error!i64 { if (text.len == 0) return error.Malformed; const negative = text[0] == '-'; const digits = if (negative or text[0] == '+') text[1..] else text; const unsigned = try parseUnsigned(digits); if (negative) { const minimum_magnitude = @as(u64, std.math.maxInt(i64)) + 1; if (unsigned > minimum_magnitude) return error.Malformed; if (unsigned == minimum_magnitude) return std.math.minInt(i64); return -@as(i64, @intCast(unsigned)); } if (unsigned > std.math.maxInt(i64)) return error.Malformed; return @intCast(unsigned); } fn parseIdentity(reader: *Reader, allocator: std.mem.Allocator, text: []const u8) Error!Identity { const close = std.mem.lastIndexOfScalar(u8, text, '>') orelse return error.Malformed; const open = std.mem.lastIndexOfScalar(u8, text[0..close], '<') orelse return error.Malformed; if (open == 0 or text[open - 1] != ' ' or open + 1 == close or close + 1 >= text.len or text[close + 1] != ' ') return error.Malformed; const name = text[0 .. open - 1]; const email = text[open + 1 .. close]; if (name.len == 0 or email.len == 0) return error.Malformed; const stamp_and_zone = text[close + 2 ..]; const separator = std.mem.indexOfScalar(u8, stamp_and_zone, ' ') orelse return error.Malformed; const timestamp = try parseSigned(stamp_and_zone[0..separator]); const zone = stamp_and_zone[separator + 1 ..]; if (zone.len != 5 or (zone[0] != '+' and zone[0] != '-')) return error.Malformed; const hours = try parseUnsigned(zone[1..3]); const minutes = try parseUnsigned(zone[3..5]); if (hours > 23 or minutes > 59) return error.Malformed; const raw_offset: i16 = @intCast(hours * 60 + minutes); return .{ .name = try reader.duplicate(allocator, name), .email = try reader.duplicate(allocator, email), .timestamp = timestamp, .timezone_offset_minutes = if (zone[0] == '-') -raw_offset else raw_offset, }; } fn parseMode(text: []const u8) Error!FileMode { if (std.mem.eql(u8, text, "100644")) return .regular; if (std.mem.eql(u8, text, "100755")) return .executable; if (std.mem.eql(u8, text, "120000")) return .symlink; if (std.mem.eql(u8, text, "160000")) return error.UnsupportedMode; return error.UnknownMode; } fn takeBareToken(input: []const u8, index: *usize) ?[]const u8 { if (index.* >= input.len) return null; const start = index.*; while (index.* < input.len and input[index.*] != ' ') : (index.* += 1) {} if (start == index.*) return null; return input[start..index.*]; }