Something went wrong. Try again.
A Zig library that reads git repositories without running git, with log, show and diff output byte-identical to git's.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! The fsmonitor cache: the `FSMN` extension of the index, read from its//! bytes as gitformat-index(5) lays it out: a version, then for version 1//! a time in nanoseconds and for version 2 the monitor's token and a NUL,//! then the size of an EWAH bitmap and the bitmap, whose set bits are the//! index entries the monitor has not vouched for.//!//! What git says of one it cannot read, seen by writing malformed ones,//! is a warning, after which it reads the index without it://!//! - fewer than nine bytes, or fewer than the version's fields: "corrupt//! fsmonitor extension (too short)" (where git reads past the end of a//! short version 1 or an unterminated token, this says so too);//! - any version but 1 and 2: "bad fsmonitor version N";//! - a bitmap it cannot read, or one that does not take exactly the size//! given for it: "failed to parse ewah bitmap reading fsmonitor index//! extension", after the bitmap's own complaint if it has one. Bytes//! after the bitmap are passed over.//!//! Which entries the monitor vouches for is not what the extension says//! alone: when `core.fsmonitor` is set, git asks the monitor before//! answering, which this library cannot do.
const std = @import("std");
const Diagnostics = @import("../Diagnostics.zig");const ewah = @import("ewah.zig");
/// What the monitor's answers were last relative to.pub const Since = union(enum) { /// Version 1: nanoseconds since the epoch. time: u64, /// Version 2: the monitor's opaque token. token: []const u8,};
/// The fsmonitor cache.pub const Fsmonitor = struct { since: Since, /// Set for each index entry the monitor has not vouched for. dirty: ewah.Bitmap,
/// The extension's version. pub fn version(f: *const Fsmonitor) u32 { return switch (f.since) { .time => 1, .token => 2, }; }};
/// Why an `FSMN` extension could not be read, each a warning in git's/// words: `FsmonitorTooShort`, `BadFsmonitorVersion` (the detail is the/// version) and `BadFsmonitorBitmap` (the detail is the bitmap's own/// complaint, or empty when it has none).pub const Error = error{ FsmonitorTooShort, BadFsmonitorVersion, BadFsmonitorBitmap };
/// Reads the `FSMN` extension's contents.pub fn parse(data: []const u8, diag: ?*Diagnostics) Error!Fsmonitor { // git wants nine bytes before it looks at the version: four of it, // and at least a NUL and a size after. if (data.len < 9) return error.FsmonitorTooShort; const v = std.mem.readInt(u32, data[0..4], .big); var pos: usize = 4; var since: Since = undefined; switch (v) { 1 => { if (data.len - pos < 8) return error.FsmonitorTooShort; since = .{ .time = std.mem.readInt(u64, data[pos..][0..8], .big) }; pos += 8; }, 2 => { const nul = std.mem.findScalar(u8, data[pos..], 0) orelse return error.FsmonitorTooShort; since = .{ .token = data[pos..][0..nul] }; pos += nul + 1; }, else => { if (diag) |d| d.setDetail("{d}", .{v}); return error.BadFsmonitorVersion; }, } if (data.len - pos < 4) return error.FsmonitorTooShort; const size = std.mem.readInt(u32, data[pos..][0..4], .big); pos += 4; const region = data[pos..][0..@min(size, data.len - pos)]; if (diag) |d| d.setDetail("", .{}); const p = ewah.parse(region, diag) catch return error.BadFsmonitorBitmap; if (p.len != size) { if (diag) |d| d.setDetail("", .{}); return error.BadFsmonitorBitmap; } return .{ .since = since, .dirty = p.bitmap };}
// -- tests -----------------------------------------------------------------------
const testing = std.testing;
const fuzz_input = @import("../testing/fuzz.zig");
test "fsmonitor extensions are read, and refused in git's words" { const bitmap = "\x00\x00\x00\x00\x00\x00\x00\x01" ++ "\x00\x00\x00\x00\x00\x00\x00\x00" ++ "\x00\x00\x00\x00"; const f = try parse("\x00\x00\x00\x02tok\x00\x00\x00\x00\x14" ++ bitmap, null); try testing.expectEqualStrings("tok", f.since.token); try testing.expectEqual(2, f.version()); const g = try parse("\x00\x00\x00\x01" ++ "\x00\x00\x00\x00\x00\x00\x00" ++ "\x05\x00\x00\x00\x14" ++ bitmap, null); try testing.expectEqual(5, g.since.time); var d: Diagnostics = .{}; try testing.expectError(error.BadFsmonitorVersion, parse("\x00\x00\x00\x03tok\x00\x00", &d)); try testing.expectEqualStrings("3", d.detail()); try testing.expectError(error.FsmonitorTooShort, parse("\x00\x00\x00\x03tok\x00", &d)); try testing.expectError(error.FsmonitorTooShort, parse("\x00\x00\x00\x02tok", &d)); try testing.expectError(error.FsmonitorTooShort, parse("\x00\x00\x00\x01\x00\x00", &d)); try testing.expectError(error.BadFsmonitorBitmap, parse("\x00\x00\x00\x02tok\x00\x00\x00\x00\x15" ++ bitmap, &d)); try testing.expectEqualStrings("", d.detail()); try testing.expectError(error.BadFsmonitorBitmap, parse("\x00\x00\x00\x02tok\x00\x00\x00\x00\x13" ++ bitmap, &d)); try testing.expectEqualStrings("eof before rlw", d.detail());}
test "fuzz: fsmonitor extensions" { try fuzz_input.fuzz({}, struct { fn one(_: void, smith: *testing.Smith) anyerror!void { var buffer: [1024]u8 = undefined; const input = @import("../testing/fuzz.zig").bytes(smith, &buffer); const f = parse(input, null) catch return; _ = f.dirty.anySetFrom(64); } }.one, .{ .corpus = &.{ @embedFile("../fuzz/index-fsmonitor"), } });}