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