╭ changed ──╮ │src/try.zig│ ╰───────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │953 // lines stay byte-identical across revisions, so a bump in `c` is a scatter of │ │954 // small hunks rather than a full rewrite. │ │955 fn writeScatterFile(io: std.Io, allocator: std.mem.Allocator, repo_dir: std.Io.Dir, path: []const u8, fi: usize, c: usize) !void { │ │956 + const content = try scatterContent(allocator, fi, c, null); │ │957 + defer allocator.free(content); │ │958 const file = try repo_dir.createFile(io, path, .{}); │ │959 defer file.close(io); │ │960 + try file.writeStreamingAll(io, content); │ │961 +} │ │962 + │ │963 +fn scatterContent(allocator: std.mem.Allocator, fi: usize, c: usize, replacement: ?struct { line: usize, text: []const u8 }) ![]u8 {│ │964 var writer = std.Io.Writer.Allocating.init(allocator); │ │965 defer writer.deinit(); │ │966 for (0..40) |line| { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │967 + if (replacement) |edit| if (line == edit.line) { │ │968 + try writer.writer.print("{s}\n", .{edit.text}); │ │969 + continue; │ │970 + }; │ │971 const start = writer.written().len; │ │972 if ((line + fi + c) % 8 == 0) { │ │973 const tag = scatter_words[(line + fi + c * 3) % scatter_words.len];│ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────╮ │986 } │ │987 try writer.writer.writeByte('\n'); │ │988 } │ │979 - try file.writeStreamingAll(io, writer.written()); │ │989 + return writer.toOwnedSlice(); │ │990 } │ │991 │ │992 // tag the current HEAD as the next sequential version: v1, v2, and so on│ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │1022 author: evt.CommitAuthor, │ │1023 timestamp: u64, │ │1024 random: std.Random, │ │1025 + base_oid: [hash.hexLen(.sha1)]u8, │ │1026 + path: []const u8, │ │1027 + contents: []const []const u8, │ │1028 ) !void { │ │1029 const patch_hex = std.fmt.bytesToHex(patch_id.*, .lower); │ │1030 const fork_path = try fork.forkPath(allocator, repos_path, &patch_hex);│ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1032 var fork_repo = try rp.Repo(.xit, .{}).open(io, allocator, .{ .path = fork_path, .require_repo_root = true });│ │1033 defer fork_repo.deinit(io, allocator); │ │1034 │ │1022 - const base_oid = (try fork_repo.readRef(io, fork.ref)) orelse return error.NotFound; │ │1035 + try fork_repo.addConfig(io, allocator, .{ .name = "core.bare", .value = "false" }); │ │1036 + { │ │1037 + var reset = try fork_repo.reset(io, allocator, .{ .target = .{ .oid = &base_oid } }); │ │1038 + defer reset.deinit(); │ │1039 + } │ │1040 const base_tree_oid = try commitTree(io, allocator, &fork_repo, &base_oid); │ │1024 - try fork_repo.addConfig(io, allocator, .{ .name = "core.bare", .value = "false" }); │ │1041 var source_oid = base_oid; │ │1042 var fork_dir = try std.Io.Dir.cwd().openDir(io, fork_path, .{}); │ │1043 defer fork_dir.close(io); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1044 var writer = std.Io.Writer.Allocating.init(allocator); │ │1045 defer writer.deinit(); │ │1030 - for (0..3) |i| { │ │1031 - writer.clearRetainingCapacity(); │ │1032 - for (0..i + 1) |line| try writer.writer.print("{s}: part {d}\n", .{ title, line + 1 }); │ │1046 + if (std.fs.path.dirname(path)) |dir| try fork_dir.createDirPath(io, dir); │ │1047 + for (contents, 0..) |content, i| { │ │1048 { │ │1034 - const file = try fork_dir.createFile(io, "patch.txt", .{}); │ │1049 + const file = try fork_dir.createFile(io, path, .{}); │ │1050 defer file.close(io); │ │1036 - try file.writeStreamingAll(io, writer.written()); │ │1051 + try file.writeStreamingAll(io, content); │ │1052 } │ │1038 - try fork_repo.add(io, allocator, &.{"patch.txt"}); │ │1053 + try fork_repo.add(io, allocator, &.{path}); │ │1054 writer.clearRetainingCapacity(); │ │1040 - try writer.writer.print("{s} ({d}/3)", .{ title, i + 1 }); │ │1055 + try writer.writer.print("{s} ({d}/{d})", .{ title, i + 1, contents.len }); │ │1056 source_oid = try fork_repo.commit(io, allocator, .{ .message = writer.written(), .timestamp = timestamp + i });│ │1057 } │ │1058 try fork_repo.addConfig(io, allocator, .{ .name = "core.bare", .value = "true" }); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │1044 - try fork_dir.deleteFile(io, "patch.txt"); │ │1059 + try fork_dir.deleteFile(io, path); │ │1060 const revision_id = evt.EventWithId.randomId(random); │ │1061 const head_tree_oid = try commitTree(io, allocator, &fork_repo, &source_oid);│ │1062 const revision: evt.PatchRev = .{ │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────╮ │1107 description: []const u8, │ │1108 tags: []const u8, │ │1109 status: ?evt.Patch.StatusKind, │ │1110 + adjacent_edit: bool = false, │ │1111 }{ │ │1112 .{ │ │1113 .title = "Draft a faster dependency scanner",│ ╰───────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1131 .title = "Add structured output to the inspect command", │ │1132 .description = "Add a stable JSON representation of inspect results for scripts and editor integrations.",│ │1133 .tags = "enhancement cli", │ │1134 + .status = .open, │ │1135 + }, │ │1136 + .{ │ │1137 + .title = "Edit an adjacent line in alpha.txt", │ │1138 + .description = "This patch can be cleanly merged by haxy, while git throws a merge conflict!", │ │1139 + .tags = "merge", │ │1140 .status = .open, │ │1141 + .adjacent_edit = true, │ │1142 }, │ │1143 .{ │ │1144 .title = "Preserve file permissions during export", │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1169 }); │ │1170 allocator.free(path); │ │1171 │ │1149 - try seedPatchRevision(io, allocator, repos_path, &patch_ids[i], patch.title, patch_author, timestamp + 1, random); │ │1172 + // prepare the patch's base and file contents, then seed its commits and revision │ │1173 + { │ │1174 + var arena = std.heap.ArenaAllocator.init(allocator); │ │1175 + defer arena.deinit(); │ │1176 + const aa = arena.allocator(); │ │1177 + var base_oid = (try target_repo.readRef(io, .{ .kind = .head, .name = "master" })) orelse return error.NotFound; │ │1178 + var contents: [3][]const u8 = undefined; │ │1179 + const file_path: []const u8, const count: usize = if (patch.adjacent_edit) blk: { │ │1180 + // v30 precedes the last scatter commit, even after the sample merge │ │1181 + const tag_oid = (try target_repo.readRef(io, .{ .kind = .tag, .name = "v30" })) orelse return error.NotFound; │ │1182 + var moment = try target_repo.core.latestMoment(); │ │1183 + const state = rp.Repo(.xit, .{}).State(.read_only){ .core = &target_repo.core, .extra = .{ .moment = &moment } }; │ │1184 + var tag = try obj.Object(.xit, .{}).init(state, io, allocator, &tag_oid); │ │1185 + defer tag.deinit(); │ │1186 + base_oid = switch (tag.content) { │ │1187 + .tag => |value| value.target, │ │1188 + else => return error.InvalidObject, │ │1189 + }; │ │1190 + // rev 29 first changes line 4; edit the line above it in rev 28 │ │1191 + contents[0] = try scatterContent(aa, 0, 28, .{ .line = 2, .text = "adjust alpha beside the latest scatter edit" }); │ │1192 + break :blk .{ "src/alpha.txt", 1 }; │ │1193 + } else blk: { │ │1194 + var writer = std.Io.Writer.Allocating.init(allocator); │ │1195 + defer writer.deinit(); │ │1196 + for (&contents, 0..) |*content, part| { │ │1197 + try writer.writer.print("{s}: part {d}\n", .{ patch.title, part + 1 }); │ │1198 + content.* = try aa.dupe(u8, writer.written()); │ │1199 + } │ │1200 + break :blk .{ "patch.txt", contents.len }; │ │1201 + }; │ │1202 + try seedPatchRevision(io, allocator, repos_path, &patch_ids[i], patch.title, patch_author, timestamp + 1, random, base_oid, file_path, contents[0..count]);│ │1203 + } │ │1204 const status = patch.status orelse continue; │ │1205 const fork_path = try fork.forkPath(allocator, repos_path, &patch_hex); │ │1206 defer allocator.free(fork_path); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯