tiny.smg.scan.fragment
Defined in scan.
API (18)
Actions
Public operations.
Manifest.changedPathsManifest.deinitManifest.emptyManifest.getManifest.matchesProjectManifest.tombstonesloadIntoloadManifestmergeDeferredEdgemergeEvidencemergeNodewritewriteManifest
Types and contracts
Public types and contracts.
Source
Source: tools/smg/src/scan/fragment.zig
zig
const std = @import("std");const sys = @import("sys");const smg = @import("../root.zig");const core = @import("core/root.zig");const graph_mod = smg.graph;const model = smg.model;const scan_files = @import("files.zig");const text = smg.text;const fs_io = std.Options.debug_io;pub const Digest = scan_files.SourceDigest;const fragment_magic = "tiny.smg.fragment/v1\n";const manifest_magic = "tiny.smg.fragment-manifest/v2\n";const cache_directory_name = "fragments";const manifest_file_name = "fragment-state.bin";const manifest_temp_file_name = "fragment-state.bin.tmp";const checksum_bytes = std.crypto.hash.sha2.Sha256.digest_length;pub const Evidence = struct { files: usize = 0, nodes_added: usize = 0, lang_counts: []const core.Count = &.{}, type_counts: []const core.Count = &.{},};pub const LoadTiming = struct { read_ns: u64 = 0, validation_ns: u64 = 0, fold_ns: u64 = 0,};pub const ManifestEntry = struct { key: Digest, live: bool, seen: bool = false,};pub const Manifest = struct { project: ?scan_files.ProjectIdentity, entries: std.StringHashMap(ManifestEntry), pub fn empty(allocator: std.mem.Allocator) Manifest { return .{ .project = null, .entries = std.StringHashMap(ManifestEntry).init(allocator), }; } pub fn get(self: *const Manifest, path: []const u8, key: Digest) bool { const entry = self.entries.get(path) orelse return false; return entry.live and std.mem.eql(u8, &entry.key, &key); } pub fn tombstones(self: *const Manifest) usize { var count: usize = 0; var iterator = self.entries.valueIterator(); while (iterator.next()) |entry| if (!entry.live) { count += 1; }; return count; } pub fn matchesProject(self: *const Manifest, digest: Digest) bool { const project = self.project orelse return false; return std.mem.eql(u8, &project.digest, &digest); } pub fn changedPaths( self: *Manifest, allocator: std.mem.Allocator, root: []const u8, current: []const scan_files.SourceIdentity, ) ![]const []const u8 { var prior = self.entries.valueIterator(); while (prior.next()) |entry| entry.seen = false; var changed_count: usize = 0; for (current) |source| { if (self.entries.getPtr(source.path)) |entry| { entry.seen = true; if (entry.live and std.mem.eql(u8, &entry.key, &source.digest)) continue; } changed_count = std.math.add(usize, changed_count, 1) catch return error.FragmentManifestTooLarge; } var entries = self.entries.iterator(); while (entries.next()) |entry| { if (!entry.value_ptr.live or entry.value_ptr.seen) continue; changed_count = std.math.add(usize, changed_count, 1) catch return error.FragmentManifestTooLarge; } const changed = try allocator.alloc([]const u8, changed_count); var index: usize = 0; for (current) |source| { const entry = self.entries.get(source.path); if (entry != null and entry.?.live and std.mem.eql(u8, &entry.?.key, &source.digest)) continue; changed[index] = try std.fs.path.join(allocator, &.{ root, source.path }); index += 1; } entries = self.entries.iterator(); while (entries.next()) |entry| { if (!entry.value_ptr.live or entry.value_ptr.seen) continue; changed[index] = try std.fs.path.join(allocator, &.{ root, entry.key_ptr.* }); index += 1; } std.debug.assert(index == changed.len); std.mem.sort([]const u8, changed, {}, scan_files.lessThan); return changed; } pub fn deinit(self: *Manifest) void { self.entries.deinit(); self.* = undefined; }};pub fn loadInto( allocator: std.mem.Allocator, scratch: std.mem.Allocator, root: []const u8, executable: Digest, key: Digest, graph: *graph_mod.Graph, stats: *core.Stats, timing: *LoadTiming, limits: smg.ScanLimits,) !bool { var phase_started = sys.time.nanoTimestamp(); const path = try fragmentPath(scratch, root, executable, key); const encoded = text.readFile( scratch, path, limits.max_fragment_bytes, ) catch |err| { timing.read_ns += sinceNanoseconds(phase_started); return switch (err) { error.StreamTooLong => error.MaxFragmentBytesExceeded, else => false, }; }; timing.read_ns += elapsedNanoseconds(&phase_started); if (!validFragment(encoded, executable, key)) { timing.validation_ns += sinceNanoseconds(phase_started); return false; } timing.validation_ns += elapsedNanoseconds(&phase_started); var reader = payloadReader(encoded, executable, key) orelse unreachable; const evidence = (try readEvidence(allocator, &reader)) orelse unreachable; try mergeEvidence(allocator, stats, evidence); const node_count = reader.count() orelse unreachable; for (0..node_count) |_| { const node = try readNode(allocator, &reader) orelse unreachable; try mergeNode(graph, node); } const edge_count = reader.count() orelse unreachable; for (0..edge_count) |_| { const edge = try readEdge(allocator, &reader) orelse unreachable; if (try graph_mod.addEdgeTracked(graph, edge)) stats.edges_added += 1; } const deferred_count = reader.count() orelse unreachable; stats.activateDeferred(allocator); for (0..deferred_count) |_| { const edge = try readEdge(allocator, &reader) orelse unreachable; try mergeDeferredEdge(allocator, graph, stats, edge); } std.debug.assert(reader.remaining() == 0); timing.fold_ns += sinceNanoseconds(phase_started); return true;}pub fn write( scratch: std.mem.Allocator, root: []const u8, executable: Digest, key: Digest, graph: graph_mod.Graph, deferred: []const model.Edge, evidence: Evidence, limits: smg.ScanLimits,) !void { var encoded: std.Io.Writer.Allocating = .init(scratch); defer encoded.deinit(); const writer = &encoded.writer; try writer.writeAll(fragment_magic); try writer.writeAll(&executable); try writer.writeAll(&key); try writeEvidence(writer, evidence); try writeCount(writer, graph.nodes.items.len); for (graph.nodes.items) |node| try writeNode(writer, node); try writeCount(writer, graph.edges.items.len); for (graph.edges.items) |edge| try writeEdge(writer, edge); try writeCount(writer, deferred.len); for (deferred) |edge| try writeEdge(writer, edge); if (encoded.written().len > limits.max_fragment_bytes - checksum_bytes) { return error.MaxFragmentBytesExceeded; } var checksum: [checksum_bytes]u8 = undefined; std.crypto.hash.sha2.Sha256.hash(encoded.written(), &checksum, .{}); try writer.writeAll(&checksum); const directory = try executableDirectory(scratch, root, executable); try sys.fs.createDirPath(directory); const destination = try fragmentPath(scratch, root, executable, key); const pid = try sys.process.currentProcessId(); const temporary = try std.fmt.allocPrint(scratch, "{s}.tmp.{d}", .{ destination, pid }); errdefer std.Io.Dir.deleteFileAbsolute(fs_io, temporary) catch {}; { var file = try std.Io.Dir.createFileAbsolute(fs_io, temporary, .{ .truncate = true }); defer file.close(fs_io); try file.writeStreamingAll(fs_io, encoded.written()); } try sys.fs.rename(temporary, destination);}pub fn loadManifest( allocator: std.mem.Allocator, scratch: std.mem.Allocator, root: []const u8, executable: Digest, limits: smg.ScanLimits,) !Manifest { var manifest = Manifest.empty(allocator); errdefer manifest.deinit(); const path = try cachePath(scratch, root, manifest_file_name); const encoded = text.readFile( scratch, path, limits.max_manifest_bytes, ) catch return manifest; const header_bytes = manifest_magic.len + 2 * @sizeOf(Digest) + 2 * @sizeOf(u64) + checksum_bytes; if (!validChecksum(encoded) or encoded.len < header_bytes) return manifest; var reader = Reader.init(encoded[0 .. encoded.len - checksum_bytes]); if (!reader.expect(manifest_magic)) return manifest; const stored_executable = reader.digest() orelse return manifest; if (!std.mem.eql(u8, &stored_executable, &executable)) return manifest; const project_digest = reader.digest() orelse return manifest; const project_files = reader.count() orelse return manifest; const count = reader.count() orelse return manifest; if (count > reader.remaining() / (1 + @sizeOf(u32) + @sizeOf(Digest))) return manifest; try manifest.entries.ensureTotalCapacity(@intCast(count)); for (0..count) |_| { const live = switch (reader.byte() orelse return invalidManifest(&manifest)) { 0 => false, 1 => true, else => return invalidManifest(&manifest), }; const path_value = try reader.owned(allocator) orelse return invalidManifest(&manifest); const key = reader.digest() orelse return invalidManifest(&manifest); manifest.entries.putAssumeCapacity(path_value, .{ .key = key, .live = live }); } if (reader.remaining() != 0) return invalidManifest(&manifest); manifest.project = .{ .digest = project_digest, .files = project_files }; return manifest;}fn invalidManifest(manifest: *Manifest) Manifest { manifest.project = null; manifest.entries.clearRetainingCapacity(); return manifest.*;}pub fn writeManifest( allocator: std.mem.Allocator, scratch: std.mem.Allocator, root: []const u8, executable: Digest, project: scan_files.ProjectIdentity, previous: *const Manifest, current: []const scan_files.SourceIdentity, limits: smg.ScanLimits,) !usize { var live = std.StringHashMap(void).init(scratch); defer live.deinit(); try live.ensureTotalCapacity(@intCast(current.len)); for (current) |entry| live.putAssumeCapacity(entry.path, {}); var tombstones: std.ArrayList([]const u8) = .empty; var prior = previous.entries.iterator(); while (prior.next()) |entry| { if (live.contains(entry.key_ptr.*)) continue; try tombstones.append(scratch, entry.key_ptr.*); } std.mem.sort([]const u8, tombstones.items, {}, scan_files.lessThan); var encoded: std.Io.Writer.Allocating = .init(scratch); defer encoded.deinit(); const writer = &encoded.writer; try writer.writeAll(manifest_magic); try writer.writeAll(&executable); try writer.writeAll(&project.digest); try writeCount(writer, project.files); const entry_count = std.math.add(usize, current.len, tombstones.items.len) catch return error.FragmentManifestTooLarge; try writeCount(writer, entry_count); for (current) |entry| { try writer.writeByte(1); try writeBytes(writer, entry.path); try writer.writeAll(&entry.digest); } const empty_key: Digest = @splat(0); for (tombstones.items) |path| { try writer.writeByte(0); try writeBytes(writer, path); try writer.writeAll(&empty_key); } if (encoded.written().len > limits.max_manifest_bytes - checksum_bytes) { return error.FragmentManifestTooLarge; } var checksum: [checksum_bytes]u8 = undefined; std.crypto.hash.sha2.Sha256.hash(encoded.written(), &checksum, .{}); try writer.writeAll(&checksum); const directory = try cacheDirectory(scratch, root); try sys.fs.createDirPath(directory); const destination = try cachePath(scratch, root, manifest_file_name); const temporary_name = try std.fmt.allocPrint( scratch, "{s}.{d}", .{ manifest_temp_file_name, try sys.process.currentProcessId() }, ); const temporary = try cachePath(scratch, root, temporary_name); errdefer std.Io.Dir.deleteFileAbsolute(fs_io, temporary) catch {}; { var file = try std.Io.Dir.createFileAbsolute(fs_io, temporary, .{ .truncate = true }); defer file.close(fs_io); try file.writeStreamingAll(fs_io, encoded.written()); try file.sync(fs_io); } try sys.fs.rename(temporary, destination); _ = allocator; return tombstones.items.len;}fn writeEvidence(writer: *std.Io.Writer, evidence: Evidence) !void { try writeCount(writer, evidence.files); try writeCount(writer, evidence.nodes_added); try writeCounts(writer, evidence.lang_counts); try writeCounts(writer, evidence.type_counts);}fn readEvidence(allocator: std.mem.Allocator, reader: *Reader) !?Evidence { const files = reader.count() orelse return null; const nodes_added = reader.count() orelse return null; const lang_counts = (try readCounts(allocator, reader)) orelse return null; const type_counts = (try readCounts(allocator, reader)) orelse return null; return .{ .files = files, .nodes_added = nodes_added, .lang_counts = lang_counts, .type_counts = type_counts, };}pub fn mergeEvidence( allocator: std.mem.Allocator, stats: *core.Stats, evidence: Evidence,) !void { stats.files += evidence.files; stats.nodes_added += evidence.nodes_added; try mergeCounts(allocator, &stats.lang_counts, evidence.lang_counts); try mergeCounts(allocator, &stats.type_counts, evidence.type_counts);}pub fn mergeNode(graph: *graph_mod.Graph, node: model.Node) !void { if (std.mem.eql(u8, node.type, model.NodeType.package) and node.file == null and graph_mod.getNode(graph, node.name) != null) { return; } try graph_mod.addNode(graph, node);}pub fn mergeDeferredEdge( allocator: std.mem.Allocator, graph: *graph_mod.Graph, stats: *core.Stats, edge: model.Edge,) !void { if (std.mem.eql(u8, edge.rel, model.RelType.imports) and graph_mod.getNode(graph, edge.target) != null) { const resolved = model.Edge{ .source = edge.source, .rel = edge.rel, .target = edge.target, .metadata = try core.scanEdgeMetadata(allocator, edge.metadata), }; if (try graph_mod.addEdgeTracked(graph, resolved)) stats.edges_added += 1; return; } try stats.deferred.append(edge);}fn mergeCounts( allocator: std.mem.Allocator, target: *std.ArrayList(core.Count), source: []const core.Count,) !void { for (source) |incoming| { for (target.items) |*existing| { if (!std.mem.eql(u8, existing.name, incoming.name)) continue; existing.count += incoming.count; break; } else { try target.append(allocator, incoming); } }}fn writeCounts(writer: *std.Io.Writer, counts: []const core.Count) !void { try writeCount(writer, counts.len); for (counts) |count| { try writeBytes(writer, count.name); try writeCount(writer, count.count); }}fn readCounts(allocator: std.mem.Allocator, reader: *Reader) !?[]const core.Count { const count = reader.count() orelse return null; if (count > reader.remaining() / (@sizeOf(u32) + @sizeOf(u64))) return null; const values = try allocator.alloc(core.Count, count); for (values) |*value| { value.name = try reader.owned(allocator) orelse return null; value.count = reader.count() orelse return null; } return values;}fn writeNode(writer: *std.Io.Writer, node: model.Node) !void { try writeBytes(writer, node.name); try writeBytes(writer, node.type); try writeOptionalBytes(writer, node.file); try writeOptionalInt(writer, node.line); try writeOptionalInt(writer, node.end_line); try writeOptionalBytes(writer, node.docstring); try writePairs(writer, node.metadata);}fn readNode(allocator: std.mem.Allocator, reader: *Reader) !?model.Node { const name = try reader.owned(allocator) orelse return null; const node_type = try reader.owned(allocator) orelse return null; const file = try reader.optionalOwned(allocator) orelse return null; const line = reader.optionalInt() orelse return null; const end_line = reader.optionalInt() orelse return null; const docstring = try reader.optionalOwned(allocator) orelse return null; const metadata = try readPairs(allocator, reader) orelse return null; return .{ .name = name, .type = node_type, .file = file.value, .line = line.value, .end_line = end_line.value, .docstring = docstring.value, .metadata = metadata, };}fn writeEdge(writer: *std.Io.Writer, edge: model.Edge) !void { try writeBytes(writer, edge.source); try writeBytes(writer, edge.rel); try writeBytes(writer, edge.target); try writePairs(writer, edge.metadata);}fn readEdge(allocator: std.mem.Allocator, reader: *Reader) !?model.Edge { const source = try reader.owned(allocator) orelse return null; const rel = try reader.owned(allocator) orelse return null; const target = try reader.owned(allocator) orelse return null; const metadata = try readPairs(allocator, reader) orelse return null; return .{ .source = source, .rel = rel, .target = target, .metadata = metadata };}fn writePairs(writer: *std.Io.Writer, pairs: []const model.Pair) !void { try writeCount(writer, pairs.len); for (pairs) |pair| { try writeBytes(writer, pair.key); try writeBytes(writer, pair.value); try writer.writeByte(@intFromBool(pair.json)); }}fn readPairs(allocator: std.mem.Allocator, reader: *Reader) !?[]const model.Pair { const count = reader.count() orelse return null; if (count > reader.remaining() / (2 * @sizeOf(u32) + 1)) return null; const pairs = try allocator.alloc(model.Pair, count); for (pairs) |*pair| { pair.key = try reader.owned(allocator) orelse return null; pair.value = try reader.owned(allocator) orelse return null; pair.json = switch (reader.byte() orelse return null) { 0 => false, 1 => true, else => return null, }; } return pairs;}fn writeOptionalBytes(writer: *std.Io.Writer, value: ?[]const u8) !void { if (value) |bytes| { try writer.writeByte(1); try writeBytes(writer, bytes); } else { try writer.writeByte(0); }}fn writeOptionalInt(writer: *std.Io.Writer, value: ?i64) !void { if (value) |integer| { try writer.writeByte(1); try writeInteger(i64, writer, integer); } else { try writer.writeByte(0); }}fn writeBytes(writer: *std.Io.Writer, bytes: []const u8) !void { if (bytes.len > std.math.maxInt(u32)) return error.FragmentFieldTooLarge; try writeInteger(u32, writer, @intCast(bytes.len)); try writer.writeAll(bytes);}fn writeCount(writer: *std.Io.Writer, count: usize) !void { if (count > std.math.maxInt(u64)) return error.FragmentCountTooLarge; try writeInteger(u64, writer, @intCast(count));}fn writeInteger(comptime T: type, writer: *std.Io.Writer, value: T) !void { var encoded: [@sizeOf(T)]u8 = undefined; std.mem.writeInt(T, &encoded, value, .big); try writer.writeAll(&encoded);}const OptionalBytes = struct { value: ?[]const u8 };const OptionalInt = struct { value: ?i64 };const Reader = struct { bytes: []const u8, position: usize = 0, fn init(bytes: []const u8) Reader { return .{ .bytes = bytes }; } fn remaining(self: Reader) usize { return self.bytes.len - self.position; } fn expect(self: *Reader, expected: []const u8) bool { const actual = self.take(expected.len) orelse return false; return std.mem.eql(u8, actual, expected); } fn digest(self: *Reader) ?Digest { const bytes = self.take(@sizeOf(Digest)) orelse return null; var value: Digest = undefined; @memcpy(&value, bytes); return value; } fn count(self: *Reader) ?usize { const value = self.integer(u64) orelse return null; return std.math.cast(usize, value); } fn byte(self: *Reader) ?u8 { return (self.take(1) orelse return null)[0]; } fn owned(self: *Reader, allocator: std.mem.Allocator) !?[]const u8 { const length = self.integer(u32) orelse return null; const bytes = self.take(length) orelse return null; return try allocator.dupe(u8, bytes); } fn optionalOwned(self: *Reader, allocator: std.mem.Allocator) !?OptionalBytes { return switch (self.byte() orelse return null) { 0 => .{ .value = null }, 1 => .{ .value = try self.owned(allocator) orelse return null }, else => null, }; } fn optionalInt(self: *Reader) ?OptionalInt { return switch (self.byte() orelse return null) { 0 => .{ .value = null }, 1 => .{ .value = self.integer(i64) orelse return null }, else => null, }; } fn skipBytes(self: *Reader) bool { const length = self.integer(u32) orelse return false; _ = self.take(length) orelse return false; return true; } fn skipOptionalBytes(self: *Reader) bool { return switch (self.byte() orelse return false) { 0 => true, 1 => self.skipBytes(), else => false, }; } fn skipOptionalInt(self: *Reader) bool { return switch (self.byte() orelse return false) { 0 => true, 1 => self.integer(i64) != null, else => false, }; } fn integer(self: *Reader, comptime T: type) ?T { const bytes = self.take(@sizeOf(T)) orelse return null; return std.mem.readInt(T, bytes[0..@sizeOf(T)], .big); } fn take(self: *Reader, length: usize) ?[]const u8 { if (length > self.remaining()) return null; const start = self.position; self.position += length; return self.bytes[start..self.position]; }};fn validChecksum(encoded: []const u8) bool { if (encoded.len < checksum_bytes) return false; var computed: [checksum_bytes]u8 = undefined; std.crypto.hash.sha2.Sha256.hash(encoded[0 .. encoded.len - checksum_bytes], &computed, .{}); return std.mem.eql(u8, &computed, encoded[encoded.len - checksum_bytes ..]);}fn cacheDirectory(allocator: std.mem.Allocator, root: []const u8) ![]const u8 { return try std.fs.path.join(allocator, &.{ root, ".smg", cache_directory_name });}fn validFragment(encoded: []const u8, executable: Digest, key: Digest) bool { if (!validChecksum(encoded)) return false; var reader = payloadReader(encoded, executable, key) orelse return false; if (!skipEvidence(&reader)) return false; const node_count = reader.count() orelse return false; if (node_count > reader.remaining() / 3) return false; for (0..node_count) |_| if (!skipNode(&reader)) return false; const edge_count = reader.count() orelse return false; if (edge_count > reader.remaining() / 3) return false; for (0..edge_count) |_| if (!skipEdge(&reader)) return false; const deferred_count = reader.count() orelse return false; if (deferred_count > reader.remaining() / 3) return false; for (0..deferred_count) |_| if (!skipEdge(&reader)) return false; return reader.remaining() == 0;}fn payloadReader(encoded: []const u8, executable: Digest, key: Digest) ?Reader { if (encoded.len < fragment_magic.len + 2 * @sizeOf(Digest) + checksum_bytes) return null; var reader = Reader.init(encoded[0 .. encoded.len - checksum_bytes]); if (!reader.expect(fragment_magic)) return null; const stored_executable = reader.digest() orelse return null; if (!std.mem.eql(u8, &stored_executable, &executable)) return null; const stored_key = reader.digest() orelse return null; if (!std.mem.eql(u8, &stored_key, &key)) return null; return reader;}fn skipEvidence(reader: *Reader) bool { _ = reader.count() orelse return false; _ = reader.count() orelse return false; return skipCounts(reader) and skipCounts(reader);}fn skipCounts(reader: *Reader) bool { const count = reader.count() orelse return false; if (count > reader.remaining() / (@sizeOf(u32) + @sizeOf(u64))) return false; for (0..count) |_| { if (!reader.skipBytes()) return false; _ = reader.count() orelse return false; } return true;}fn skipNode(reader: *Reader) bool { return reader.skipBytes() and reader.skipBytes() and reader.skipOptionalBytes() and reader.skipOptionalInt() and reader.skipOptionalInt() and reader.skipOptionalBytes() and skipPairs(reader);}fn skipEdge(reader: *Reader) bool { return reader.skipBytes() and reader.skipBytes() and reader.skipBytes() and skipPairs(reader);}fn skipPairs(reader: *Reader) bool { const count = reader.count() orelse return false; if (count > reader.remaining() / (2 * @sizeOf(u32) + 1)) return false; for (0..count) |_| { if (!reader.skipBytes() or !reader.skipBytes()) return false; const json = reader.byte() orelse return false; if (json > 1) return false; } return true;}fn executableDirectory( allocator: std.mem.Allocator, root: []const u8, executable: Digest,) ![]const u8 { const hex = std.fmt.bytesToHex(executable, .lower); return try std.fs.path.join(allocator, &.{ root, ".smg", cache_directory_name, &hex });}fn fragmentPath( allocator: std.mem.Allocator, root: []const u8, executable: Digest, key: Digest,) ![]const u8 { const hex = std.fmt.bytesToHex(key, .lower); const file_name = try std.fmt.allocPrint(allocator, "{s}.bin", .{&hex}); return try std.fs.path.join( allocator, &.{ try executableDirectory(allocator, root, executable), file_name }, );}fn cachePath(allocator: std.mem.Allocator, root: []const u8, name: []const u8) ![]const u8 { return try std.fs.path.join(allocator, &.{ root, ".smg", name });}fn elapsedNanoseconds(started: *i128) u64 { const finished = sys.time.nanoTimestamp(); std.debug.assert(finished >= started.*); const elapsed: u64 = @intCast(finished - started.*); started.* = finished; return elapsed;}fn sinceNanoseconds(started: i128) u64 { const finished = sys.time.nanoTimestamp(); std.debug.assert(finished >= started); return @intCast(finished - started);}test "manifest identifies stable changed added and removed sources" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const first: Digest = @splat(1); const second: Digest = @splat(2); var manifest = Manifest.empty(allocator); defer manifest.deinit(); const empty = try manifest.changedPaths(allocator, "/repo", &.{}); try std.testing.expectEqual(@as(usize, 0), empty.len); manifest.project = .{ .digest = first, .files = 4 }; try manifest.entries.put("same.py", .{ .key = first, .live = true }); try manifest.entries.put("edit.py", .{ .key = first, .live = true }); try manifest.entries.put("gone.py", .{ .key = first, .live = true }); try manifest.entries.put("dead.py", .{ .key = @splat(0), .live = false }); const current = [_]scan_files.SourceIdentity{ .{ .path = "added.py", .digest = first }, .{ .path = "edit.py", .digest = second }, .{ .path = "same.py", .digest = first }, }; const changed = try manifest.changedPaths(allocator, "/repo", ¤t); try std.testing.expect(manifest.matchesProject(first)); try std.testing.expect(!manifest.matchesProject(second)); try std.testing.expectEqual(@as(usize, 3), changed.len); try std.testing.expectEqualStrings("/repo/added.py", changed[0]); try std.testing.expectEqualStrings("/repo/edit.py", changed[1]); try std.testing.expectEqualStrings("/repo/gone.py", changed[2]);}Source: tools/smg/src/scan/root.zig:5
zig
pub const fragment = @import("fragment.zig");Complete call list for scan.fragment.loadInto
11 direct calls.
tools.smg.src.scan.fragment.elapsedNanoseconds[function] — private; no exact target attools/smg/src/scan/fragment.zig:740in nearest public ownertiny.smg.scan.fragmenttools.smg.src.scan.fragment.fragmentPath[function] — private; no exact target attools/smg/src/scan/fragment.zig:722in nearest public ownertiny.smg.scan.fragmenttiny.smg.scan.fragment.mergeDeferredEdge[function] attools/smg/src/scan/fragment.zig:378tiny.smg.scan.fragment.mergeEvidence[function] attools/smg/src/scan/fragment.zig:357tiny.smg.scan.fragment.mergeNode[function] attools/smg/src/scan/fragment.zig:368tools.smg.src.scan.fragment.payloadReader[function] — private; no exact target attools/smg/src/scan/fragment.zig:658in nearest public ownertiny.smg.scan.fragmenttools.smg.src.scan.fragment.readEdge[function] — private; no exact target attools/smg/src/scan/fragment.zig:470in nearest public ownertiny.smg.scan.fragmenttools.smg.src.scan.fragment.readEvidence[function] — private; no exact target attools/smg/src/scan/fragment.zig:344in nearest public ownertiny.smg.scan.fragmenttools.smg.src.scan.fragment.readNode[function] — private; no exact target attools/smg/src/scan/fragment.zig:444in nearest public ownertiny.smg.scan.fragmenttools.smg.src.scan.fragment.sinceNanoseconds[function] — private; no exact target attools/smg/src/scan/fragment.zig:748in nearest public ownertiny.smg.scan.fragmenttools.smg.src.scan.fragment.validFragment[function] — private; no exact target attools/smg/src/scan/fragment.zig:642in nearest public ownertiny.smg.scan.fragment
Complete call list for scan.fragment.loadManifest
11 direct calls.
tiny.smg.scan.fragment.Manifest.empty[function] attools/smg/src/scan/fragment.zig:45tools.smg.src.scan.fragment.Reader.byte[method] — private; no exact target attools/smg/src/scan/fragment.zig:570in nearest public ownertiny.smg.scan.fragmenttools.smg.src.scan.fragment.Reader.count[method] — private; no exact target attools/smg/src/scan/fragment.zig:565in nearest public ownertiny.smg.scan.fragmenttools.smg.src.scan.fragment.Reader.digest[method] — private; no exact target attools/smg/src/scan/fragment.zig:558in nearest public ownertiny.smg.scan.fragmenttools.smg.src.scan.fragment.Reader.expect[method] — private; no exact target attools/smg/src/scan/fragment.zig:553in nearest public ownertiny.smg.scan.fragmenttools.smg.src.scan.fragment.Reader.init[function] — private; no exact target attools/smg/src/scan/fragment.zig:545in nearest public ownertiny.smg.scan.fragmenttools.smg.src.scan.fragment.Reader.owned[method] — private; no exact target attools/smg/src/scan/fragment.zig:574in nearest public ownertiny.smg.scan.fragmenttools.smg.src.scan.fragment.Reader.remaining[method] — private; no exact target attools/smg/src/scan/fragment.zig:549in nearest public ownertiny.smg.scan.fragmenttools.smg.src.scan.fragment.cachePath[function] — private; no exact target attools/smg/src/scan/fragment.zig:736in nearest public ownertiny.smg.scan.fragmenttools.smg.src.scan.fragment.invalidManifest[function] — private; no exact target attools/smg/src/scan/fragment.zig:260in nearest public ownertiny.smg.scan.fragmenttools.smg.src.scan.fragment.validChecksum[function] — private; no exact target attools/smg/src/scan/fragment.zig:631in nearest public ownertiny.smg.scan.fragment
Audit
| Definitions | 18 |
|---|---|
| Public names | 18 |
| Members | 12 |
| Version | 26.7.0 |
| Revision | daab053ee433 |