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