╭ changed ──╮ │src/try.zig│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │232 try template_repo.add(io, allocator, &.{ "README.md", "docs/dev/contribute.md" });│ │233 _ = try template_repo.commit(io, allocator, .{ .message = "let there be light" });│ │234 │ │235 + // tag every commit in creation order as v1, v2, and so on │ │236 + var tag_num: usize = 1; │ │237 + try addNextTag(&template_repo, io, allocator, &tag_num); │ │238 + │ │239 // a batch of commits so the commits tab has more than one page to │ │240 // paginate through. each rewrites a few files with scattered line │ │241 // edits, so every commit is a multi-file diff with several separate │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ │242 // hunks to look at. stepped timestamps vary the date column. │ │243 const base_ts: u64 = 1_700_000_000; // 2023-11-14 │ │244 const edit_files = [_][]const u8{ "src/alpha.txt", "src/beta.txt", "src/gamma.txt" };│ │241 - // cycled through to pad each line into prose-like content. │ │242 - const words = [_][]const u8{ │ │243 - "lorem", "ipsum", "dolor", "sit", "amet", "consectetur", │ │244 - "quantum", "vector", "matrix", "buffer", "kernel", "socket", │ │245 - "falcon", "otter", "badger", "walrus", "ferret", "marmot", │ │246 - "scatter", "gather", "encode", "decode", "render", "commit", │ │247 - }; │ │245 // commit subjects, cycled then padded to a varying length. │ │246 const subjects = [_][]const u8{ │ │247 "fix off-by-one in scatter loop", │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │259 defer repo_dir.close(io); │ │260 try repo_dir.createDirPath(io, "src"); │ │261 for (edit_files, 0..) |path, fi| { │ │265 - const file = try repo_dir.createFile(io, path, .{}); │ │266 - defer file.close(io); │ │267 - var writer = std.Io.Writer.Allocating.init(allocator); │ │268 - defer writer.deinit(); │ │269 - // every 8th line is edited by this commit; the stride │ │270 - // offset shifts with c so consecutive commits touch │ │271 - // different lines, and the edited text carries a │ │272 - // c-dependent word, so each commit's diff is a distinct │ │273 - // scatter of hunks. untouched lines stay byte-identical │ │274 - // across commits (their padding ignores c) so they don't │ │275 - // all change every commit. each line is padded with │ │276 - // cycled words to a per-line length, capped at 120. │ │277 - for (0..40) |line| { │ │278 - const start = writer.written().len; │ │279 - if ((line + fi + c) % 8 == 0) { │ │280 - const tag = words[(line + fi + c * 3) % words.len]; │ │281 - try writer.writer.print("rev {d} {s}", .{ c, tag }); │ │282 - } │ │283 - const target = 40 + (line * 17 + fi * 23) % 70; │ │284 - var w = line + fi; │ │285 - while (true) : (w += 1) { │ │286 - const word = words[w % words.len]; │ │287 - const len = writer.written().len - start; │ │288 - if (len >= target or len + 1 + word.len > 120) break;│ │289 - if (len == 0) │ │290 - try writer.writer.print("{s}", .{word}) │ │291 - else │ │292 - try writer.writer.print(" {s}", .{word}); │ │293 - } │ │294 - try writer.writer.writeByte('\n'); │ │295 - } │ │296 - try file.writeStreamingAll(io, writer.written()); │ │262 + try writeScatterFile(io, allocator, repo_dir, path, fi, c); │ │263 } │ │264 } │ │265 try template_repo.add(io, allocator, &edit_files); │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │271 const msg_target = 16 + (c * 41) % 96; │ │272 var mw = c; │ │273 while (true) : (mw += 1) { │ │308 - const word = words[mw % words.len]; │ │274 + const word = scatter_words[mw % scatter_words.len]; │ │275 const len = msg_writer.written().len; │ │276 if (len >= msg_target or len + 1 + word.len > 120) break;│ │277 try msg_writer.writer.print(" {s}", .{word}); │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │278 } │ │279 const message = try arena.allocator().dupe(u8, msg_writer.written()); │ │280 _ = try template_repo.commit(io, allocator, .{ .message = message, .timestamp = base_ts + c * std.time.s_per_day }); │ │281 + try addNextTag(&template_repo, io, allocator, &tag_num); │ │282 } │ │283 + │ │284 + // two more branches forked off master, each adding a single commit │ │285 + // that makes a scattered edit to one file. tag each new commit too, │ │286 + // and switch back to master after each so the next branch also │ │287 + // forks from master and the template ends up back on master. │ │288 + const branch_data = [_]struct { │ │289 + name: []const u8, │ │290 + file: []const u8, │ │291 + fi: usize, │ │292 + message: []const u8, │ │293 + rev: usize, │ │294 + }{ │ │295 + .{ .name = "extra", .file = "src/alpha.txt", .fi = 0, .message = "scatter alpha on the extra branch", .rev = 30 }, │ │296 + .{ .name = "feature", .file = "src/beta.txt", .fi = 1, .message = "scatter beta on the feature branch", .rev = 31 }, │ │297 + }; │ │298 + for (branch_data) |b| { │ │299 + try template_repo.addBranch(io, .{ .name = b.name }); │ │300 + { │ │301 + var to_branch = try template_repo.switchDir(io, allocator, .{ .target = .{ .ref = .{ .kind = .head, .name = b.name } } }); │ │302 + defer to_branch.deinit(); │ │303 + } │ │304 + │ │305 + { │ │306 + var repo_dir = try cwd.openDir(io, template_path, .{}); │ │307 + defer repo_dir.close(io); │ │308 + try writeScatterFile(io, allocator, repo_dir, b.file, b.fi, b.rev); │ │309 + } │ │310 + try template_repo.add(io, allocator, &.{b.file}); │ │311 + _ = try template_repo.commit(io, allocator, .{ .message = b.message, .timestamp = base_ts + b.rev * std.time.s_per_day }); │ │312 + try addNextTag(&template_repo, io, allocator, &tag_num); │ │313 + │ │314 + { │ │315 + var to_master = try template_repo.switchDir(io, allocator, .{ .target = .{ .ref = .{ .kind = .head, .name = "master" } } });│ │316 + defer to_master.deinit(); │ │317 + } │ │318 + } │ │319 } │ │320 │ │321 // copy the template to each repo's on-disk location, named by its │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │424 } │ │425 } │ │426 │ │427 +// words cycled through to pad generated lines into prose-like content │ │428 +const scatter_words = [_][]const u8{ │ │429 + "lorem", "ipsum", "dolor", "sit", "amet", "consectetur", │ │430 + "quantum", "vector", "matrix", "buffer", "kernel", "socket", │ │431 + "falcon", "otter", "badger", "walrus", "ferret", "marmot", │ │432 + "scatter", "gather", "encode", "decode", "render", "commit", │ │433 +}; │ │434 + │ │435 +// write `path` as 40 lines of mostly-stable filler. revision `c` and file │ │436 +// index `fi` shift which lines change and the words used, so each (c, fi) │ │437 +// yields a distinct multi-hunk diff against the previous revision. untouched │ │438 +// lines stay byte-identical across revisions, so a bump in `c` is a scatter of │ │439 +// small hunks rather than a full rewrite. │ │440 +fn writeScatterFile(io: std.Io, allocator: std.mem.Allocator, repo_dir: std.Io.Dir, path: []const u8, fi: usize, c: usize) !void {│ │441 + const file = try repo_dir.createFile(io, path, .{}); │ │442 + defer file.close(io); │ │443 + var writer = std.Io.Writer.Allocating.init(allocator); │ │444 + defer writer.deinit(); │ │445 + for (0..40) |line| { │ │446 + const start = writer.written().len; │ │447 + if ((line + fi + c) % 8 == 0) { │ │448 + const tag = scatter_words[(line + fi + c * 3) % scatter_words.len]; │ │449 + try writer.writer.print("rev {d} {s}", .{ c, tag }); │ │450 + } │ │451 + const target = 40 + (line * 17 + fi * 23) % 70; │ │452 + var w = line + fi; │ │453 + while (true) : (w += 1) { │ │454 + const word = scatter_words[w % scatter_words.len]; │ │455 + const len = writer.written().len - start; │ │456 + if (len >= target or len + 1 + word.len > 120) break; │ │457 + if (len == 0) │ │458 + try writer.writer.print("{s}", .{word}) │ │459 + else │ │460 + try writer.writer.print(" {s}", .{word}); │ │461 + } │ │462 + try writer.writer.writeByte('\n'); │ │463 + } │ │464 + try file.writeStreamingAll(io, writer.written()); │ │465 +} │ │466 + │ │467 +// tag the current HEAD as the next sequential version: v1, v2, and so on │ │468 +fn addNextTag(repo: *rp.Repo(.xit, .{}), io: std.Io, allocator: std.mem.Allocator, n: *usize) !void { │ │469 + var buf: [16]u8 = undefined; │ │470 + const name = try std.fmt.bufPrint(&buf, "v{d}", .{n.*}); │ │471 + _ = try repo.addTag(io, allocator, .{ .name = name }); │ │472 + n.* += 1; │ │473 +} │ │474 + │ │475 // recursively copy the contents of src_dir into dest_dir │ │476 fn copyDir(io: std.Io, src_dir: std.Io.Dir, dest_dir: std.Io.Dir) !void { │ │477 var iter = src_dir.iterate(); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯