╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ try.zig ──────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
209 // template repo and copy that to each repo's location below rather than 210 // redoing the expensive commit work for every repo. 211 const template_path = try std.fs.path.join(arena.allocator(), &.{ cwd_path, temp_dir_name, "template" }); 212 { 213 var template_repo = try rp.Repo(.xit, .{}).init(io, allocator, .{ .path = template_path }); 214 defer template_repo.deinit(io, allocator); 215 216 try template_repo.addConfig(io, allocator, .{ .name = "user.name", .value = "haxy" }); 217 try template_repo.addConfig(io, allocator, .{ .name = "user.email", .value = "admin@haxy" }); 218 219 // a README plus a nested doc so the file tree has a directory to 220 // descend into 221 { 222 var repo_dir = try cwd.openDir(io, template_path, .{}); 223 defer repo_dir.close(io); 224 225 const readme = try repo_dir.createFile(io, "README.md", .{}); 226 defer readme.close(io); 227 try readme.writeStreamingAll(io, "# Sample Repo\n\nA repository seeded with test data.\n"); 228 229 try repo_dir.createDirPath(io, "docs/dev"); 230 const doc = try repo_dir.createFile(io, "docs/dev/contribute.md", .{}); 231 defer doc.close(io); 232 try doc.writeStreamingAll(io, "To contribute, please make a pull request"); 233 } 234 235 try template_repo.add(io, allocator, &.{ "README.md", "docs/dev/contribute.md" }); 236 _ = try template_repo.commit(io, allocator, .{ .message = "let there be light" }); 237 238 // tag every commit in creation order as v1, v2, and so on 239 var tag_num: usize = 1; 240 try addNextTag(&template_repo, io, allocator, &tag_num); 241 242 // a batch of commits so the commits tab has more than one page to 243 // paginate through. each rewrites a few files with scattered line 244 // edits, so every commit is a multi-file diff with several separate 245 // hunks to look at. stepped timestamps vary the date column. 246 const base_ts: u64 = 1_700_000_000; // 2023-11-14 247 const edit_files = [_][]const u8{ "src/alpha.txt", "src/beta.txt", "src/gamma.txt" }; 248 // commit subjects, cycled then padded to a varying length. 249 const subjects = [_][]const u8{ 250 "fix off-by-one in scatter loop", 251 "encode and decode buffers", 252 "tune kernel socket timeouts", 253 "render matrix vector product", 254 "gather falcon and otter stats", 255 "refactor badger walrus module", 256 "drop dead ferret marmot branch", 257 }; 258 var c: usize = 0; 259 while (c < 30) : (c += 1) { 260 { 261 var repo_dir = try cwd.openDir(io, template_path, .{}); 262 defer repo_dir.close(io); 263 try repo_dir.createDirPath(io, "src"); 264 for (edit_files, 0..) |path, fi| { 265 try writeScatterFile(io, allocator, repo_dir, path, fi, c); 266 } 267 } 268 try template_repo.add(io, allocator, &edit_files); 269 // a cycling subject padded to a c-varying length so the commit 270 // list shows messages of different widths, capped at 120. 271 var msg_writer = std.Io.Writer.Allocating.init(allocator); 272 defer msg_writer.deinit(); 273 try msg_writer.writer.print("{s}", .{subjects[c % subjects.len]}); 274 const msg_target = 16 + (c * 41) % 96; 275 var mw = c; 276 while (true) : (mw += 1) { 277 const word = scatter_words[mw % scatter_words.len]; 278 const len = msg_writer.written().len; 279 if (len >= msg_target or len + 1 + word.len > 120) break; 280 try msg_writer.writer.print(" {s}", .{word}); 281 } 282 const message = try arena.allocator().dupe(u8, msg_writer.written()); 283 _ = try template_repo.commit(io, allocator, .{ .message = message, .timestamp = base_ts + c * std.time.s_per_day }); 284 try addNextTag(&template_repo, io, allocator, &tag_num); 285 } 286 287 // two more branches forked off master, each adding a single commit 288 // that makes a scattered edit to one file. tag each new commit too, 289 // and switch back to master after each so the next branch also 290 // forks from master and the template ends up back on master. 291 const branch_data = [_]struct { 292 name: []const u8, 293 file: []const u8, 294 fi: usize, 295 message: []const u8, 296 rev: usize, 297 }{ 298 .{ .name = "extra", .file = "src/alpha.txt", .fi = 0, .message = "scatter alpha on the extra branch", .rev = 30 }, 299 .{ .name = "feature", .file = "src/beta.txt", .fi = 1, .message = "scatter beta on the feature branch", .rev = 31 }, 300 }; 301 for (branch_data) |b| { 302 try template_repo.addBranch(io, .{ .name = b.name }); 303 { 304 var to_branch = try template_repo.switchDir(io, allocator, .{ .target = .{ .ref = .{ .kind = .head, .name = b.name } } }); 305 defer to_branch.deinit(); 306 } 307 308 { 309 var repo_dir = try cwd.openDir(io, template_path, .{}); 310 defer repo_dir.close(io); 311 try writeScatterFile(io, allocator, repo_dir, b.file, b.fi, b.rev); 312 } 313 try template_repo.add(io, allocator, &.{b.file}); 314 _ = try template_repo.commit(io, allocator, .{ .message = b.message, .timestamp = base_ts + b.rev * std.time.s_per_day }); 315 try addNextTag(&template_repo, io, allocator, &tag_num); 316 317 { 318 var to_master = try template_repo.switchDir(io, allocator, .{ .target = .{ .ref = .{ .kind = .head, .name = "master" } } }); 319 defer to_master.deinit(); 320 } 321 } 322 } 323 324 // copy the template to each repo's on-disk location, named by its 325 // hex-encoded event id. the template repo is deinitialized above, so its 326 // db file is fully written before we copy it. 327 { 328 var template_dir = try cwd.openDir(io, template_path, .{ .iterate = true }); 329 defer template_dir.close(io); 330 331 for (repo_event_ids) |id_bytes| { 332 const repo_id = std.fmt.bytesToHex(id_bytes, .lower); 333 const repo_path = try std.fs.path.join(arena.allocator(), &.{ cwd_path, temp_dir_name, "server", "repos", &repo_id }); 334 var dest_dir = try cwd.createDirPathOpen(io, repo_path, .{}); 335 defer dest_dir.close(io); 336 try copyDir(io, template_dir, dest_dir); 337 } 338 } 339 340 break :blk try ui.Session.init(&session_arena, &repo, .{}); 341 }; 342 session.is_terminal = true; 343 // try uses the default serve options below, so the footer's url points at 344 // the default web UI port. 345 session.web_port = try (srv.Options{}).wuiPort(); 346 347 // start the server 348 349 var cli = false; 350 351 var arg_it = try init.minimal.args.iterateAllocator(allocator); 352 defer arg_it.deinit(); 353 _ = arg_it.skip(); 354 while (arg_it.next()) |arg| { 355 if (std.mem.eql(u8, "--cli", arg)) { 356 cli = true; 357 } 358 } 359 360 const server_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "server" }); 361 defer allocator.free(server_path); 362 363 // let the native TUI's page builders open the on-disk repos for the file tree 364 session.io = io; 365 session.repos_dir = try std.fs.path.join(session_arena.allocator(), &.{ server_path, "repos" }); 366 367 if (cli) { 368 var stdout_writer = std.Io.File.stdout().writer(io, &.{}); 369 var stderr_writer = std.Io.File.stderr().writer(io, &.{}); 370 const run_opts = hx.main.RunOpts{ .out = &stdout_writer.interface, .err = &stderr_writer.interface }; 371 372 const key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" }); 373 defer allocator.free(key_path); 374 375 const Runnable = struct { 376 io: std.Io, 377 key_path: []const u8, 378 379 pub fn run(self: @This()) !void { 380 std.debug.print( 381 \\ 382 \\connect to the TUI with: 383 \\ ssh -p 8022 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null localhost 384 \\ 385 \\create a git repo and push over SSH: 386 \\ mkdir -p temp-try/client/test 387 \\ cd temp-try/client/test 388 \\ git init 389 \\ echo "hello" > hello.txt 390 \\ git add hello.txt 391 \\ git commit -m "let there be light" 392 \\ GIT_SSH_COMMAND='ssh -p 8022 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -i {s}' git push localhost:admin/test HEAD:master 393 \\ 394 \\to quit, press enter. 395 \\ 396 , .{self.key_path}); 397 // portable stdin read via std.Io — equivalent to the 398 // std.posix.read(STDIN_FILENO, ...) that doesn't compile 399 // on windows. 400 var buf: [1]u8 = undefined; 401 var stdin_reader = std.Io.File.stdin().reader(self.io, &buf); 402 _ = stdin_reader.interface.takeByte() catch {}; 403 } 404 }; 405 406 try srv.run(.xit, .{}, io, allocator, cwd_path, .{ 407 .data_dir = server_path, 408 }, run_opts.err, Runnable{ .io = io, .key_path = key_path }); 409 } else { 410 var null_writer = std.Io.Writer.Discarding.init(&.{}); 411 const run_opts = hx.main.RunOpts{ .out = &null_writer.writer, .err = &null_writer.writer }; 412 413 const Runnable = struct { 414 io: std.Io, 415 allocator: std.mem.Allocator, 416 session: *ui.Session, 417 418 pub fn run(self: @This()) !void { 419 // launch the TUI 420 try hx.ui.run(self.io, self.allocator, self.session); 421 } 422 }; 423 424 try srv.run(.xit, .{}, io, allocator, cwd_path, .{ 425 .data_dir = server_path, 426 }, run_opts.err, Runnable{ .io = io, .allocator = allocator, .session = &session }); 427 } 428 } 429 430 // words cycled through to pad generated lines into prose-like content 431 const scatter_words = [_][]const u8{ 432 "lorem", "ipsum", "dolor", "sit", "amet", "consectetur", 433 "quantum", "vector", "matrix", "buffer", "kernel", "socket", 434 "falcon", "otter", "badger", "walrus", "ferret", "marmot", 435 "scatter", "gather", "encode", "decode", "render", "commit", 436 }; 437 438 // write `path` as 40 lines of mostly-stable filler. revision `c` and file 439 // index `fi` shift which lines change and the words used, so each (c, fi) 440 // yields a distinct multi-hunk diff against the previous revision. untouched 441 // lines stay byte-identical across revisions, so a bump in `c` is a scatter of 442 // small hunks rather than a full rewrite. 443 fn writeScatterFile(io: std.Io, allocator: std.mem.Allocator, repo_dir: std.Io.Dir, path: []const u8, fi: usize, c: usize) !void { 444 const file = try repo_dir.createFile(io, path, .{}); 445 defer file.close(io); 446 var writer = std.Io.Writer.Allocating.init(allocator); 447 defer writer.deinit(); 448 for (0..40) |line| { 449 const start = writer.written().len; 450 if ((line + fi + c) % 8 == 0) { 451 const tag = scatter_words[(line + fi + c * 3) % scatter_words.len]; 452 try writer.writer.print("rev {d} {s}", .{ c, tag }); 453 } 454 const target = 40 + (line * 17 + fi * 23) % 70; 455 var w = line + fi; 456 while (true) : (w += 1) { 457 const word = scatter_words[w % scatter_words.len]; 458 const len = writer.written().len - start; 459 if (len >= target or len + 1 + word.len > 120) break; 460 if (len == 0) 461 try writer.writer.print("{s}", .{word}) 462 else 463 try writer.writer.print(" {s}", .{word}); 464 } 465 try writer.writer.writeByte('\n'); 466 } 467 try file.writeStreamingAll(io, writer.written()); 468 } 469 470 // tag the current HEAD as the next sequential version: v1, v2, and so on 471 fn addNextTag(repo: *rp.Repo(.xit, .{}), io: std.Io, allocator: std.mem.Allocator, n: *usize) !void { 472 var buf: [16]u8 = undefined; 473 const name = try std.fmt.bufPrint(&buf, "v{d}", .{n.*}); 474 _ = try repo.addTag(io, allocator, .{ .name = name }); 475 n.* += 1; 476 } 477 478 // recursively copy the contents of src_dir into dest_dir 479 fn copyDir(io: std.Io, src_dir: std.Io.Dir, dest_dir: std.Io.Dir) !void { 480 var iter = src_dir.iterate(); 481 while (try iter.next(io)) |entry| { 482 switch (entry.kind) { 483 .file => try src_dir.copyFile(entry.name, dest_dir, entry.name, io, .{}), 484 .directory => { 485 try dest_dir.createDirPath(io, entry.name); 486 var dest_entry_dir = try dest_dir.openDir(io, entry.name, .{ .access_sub_paths = true, .iterate = true, .follow_symlinks = false }); 487 defer dest_entry_dir.close(io); 488 var src_entry_dir = try src_dir.openDir(io, entry.name, .{ .access_sub_paths = true, .iterate = true, .follow_symlinks = false }); 489 defer src_entry_dir.close(io); 490 try copyDir(io, src_entry_dir, dest_entry_dir); 491 }, 492 else => {}, 493 } 494 } 495 } 496