╭ try.zig ──────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 //! start a server with some fake data and then launch 2 //! the TUI. this provides a nice way to test things 3 //! out safely. 4 5 const std = @import("std"); 6 const builtin = @import("builtin"); 7 const hx = @import("haxy"); 8 const srv = hx.serve; 9 const evt = hx.event; 10 const xit = hx.xit; 11 const rp = xit.repo; 12 const ui = hx.ui; 13 14 // cook the terminal before a panic/segfault trace is printed, so the trace 15 // isn't mangled by raw mode and the alternate buffer 16 pub const std_options_debug_io = xit.xitui.terminal.crash_debug_io; 17 18 pub fn main(init: std.process.Init) !void { 19 var gpa: std.heap.DebugAllocator(.{}) = .init; 20 defer _ = gpa.deinit(); 21 const allocator = gpa.allocator(); 22 23 var threaded = std.Io.Threaded.init(allocator, .{ 24 .environ = init.minimal.environ, 25 }); 26 defer threaded.deinit(); 27 const io = threaded.io(); 28 29 const temp_dir_name = "temp-try"; 30 31 // create the temp dir 32 const cwd = std.Io.Dir.cwd(); 33 var temp_dir_or_err = cwd.openDir(io, temp_dir_name, .{}); 34 if (temp_dir_or_err) |*temp_dir| { 35 temp_dir.close(io); 36 try cwd.deleteTree(io, temp_dir_name); 37 } else |_| {} 38 var temp_dir = try cwd.createDirPathOpen(io, temp_dir_name, .{}); 39 defer cwd.deleteTree(io, temp_dir_name) catch {}; 40 defer temp_dir.close(io); 41 42 // write the dev SSH private key so pushes can authenticate against the 43 // matching public key seeded on the admin account below 44 { 45 const priv_key_file = try temp_dir.createFile(io, "key", .{}); 46 defer priv_key_file.close(io); 47 try priv_key_file.writeStreamingAll(io, 48 \\-----BEGIN OPENSSH PRIVATE KEY----- 49 \\b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 50 \\QyNTUxOQAAACCniLPJiaooAWecvOCeAjoJwCSeWxzysvpTNkpYjF22JgAAAJA+7hikPu4Y 51 \\pAAAAAtzc2gtZWQyNTUxOQAAACCniLPJiaooAWecvOCeAjoJwCSeWxzysvpTNkpYjF22Jg 52 \\AAAEDVlopOMnKt/7by/IA8VZvQXUS/O6VLkixOqnnahUdPCKeIs8mJqigBZ5y84J4COgnA 53 \\JJ5bHPKy+lM2SliMXbYmAAAAC3JhZGFyQHJvYXJrAQI= 54 \\-----END OPENSSH PRIVATE KEY----- 55 \\ 56 ); 57 if (.windows != builtin.os.tag) { 58 try priv_key_file.setPermissions(io, @enumFromInt(0o600)); 59 } 60 } 61 62 const cwd_path = try std.process.currentPathAlloc(io, allocator); 63 defer allocator.free(cwd_path); 64 65 const work_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "server", "admin" }); 66 defer allocator.free(work_path); 67 68 const Repo = rp.Repo(.xit, evt.admin_repo_opts); 69 var repo = try Repo.init(io, allocator, .{ .path = work_path }); 70 defer repo.deinit(io, allocator); 71 72 try repo.addConfig(io, allocator, .{ .name = "user.name", .value = "haxy" }); 73 try repo.addConfig(io, allocator, .{ .name = "user.email", .value = "admin@haxy" }); 74 75 var session_arena = std.heap.ArenaAllocator.init(allocator); 76 defer session_arena.deinit(); 77 78 var session: ui.Session = blk: { 79 var arena = std.heap.ArenaAllocator.init(allocator); 80 defer arena.deinit(); 81 82 // define test events 83 84 var prng = std.Random.DefaultPrng.init(std.testing.random_seed); 85 86 const user_data = [_]struct { 87 name: []const u8, 88 display_name: []const u8, 89 email: []const u8, 90 }{ 91 .{ .name = "admin", .display_name = "Admin", .email = "admin@example.test" }, 92 .{ .name = "alice", .display_name = "Alice Tulley", .email = "alice@example.test" }, 93 .{ .name = "bob", .display_name = "Bob Smith", .email = "bob@example.test" }, 94 .{ .name = "carol", .display_name = "Carol Johnson", .email = "carol@example.test" }, 95 .{ .name = "dave", .display_name = "Dave Wilson", .email = "dave@example.test" }, 96 .{ .name = "eve", .display_name = "Eve Anderson", .email = "eve@example.test" }, 97 .{ .name = "frank", .display_name = "Frank Miller", .email = "frank@example.test" }, 98 .{ .name = "grace", .display_name = "Grace Lee", .email = "grace@example.test" }, 99 .{ .name = "henry", .display_name = "Henry Davis", .email = "henry@example.test" }, 100 .{ .name = "ivy", .display_name = "Ivy Martinez", .email = "ivy@example.test" }, 101 .{ .name = "jack", .display_name = "Jack Thompson", .email = "jack@example.test" }, 102 .{ .name = "kate", .display_name = "Kate Robinson", .email = "kate@example.test" }, 103 .{ .name = "liam", .display_name = "Liam Walker", .email = "liam@example.test" }, 104 .{ .name = "mona", .display_name = "Mona Patel", .email = "mona@example.test" }, 105 .{ .name = "noah", .display_name = "Noah Garcia", .email = "noah@example.test" }, 106 .{ .name = "olivia", .display_name = "Olivia Hernandez", .email = "olivia@example.test" }, 107 .{ .name = "peter", .display_name = "Peter Wright", .email = "peter@example.test" }, 108 .{ .name = "quinn", .display_name = "Quinn Foster", .email = "quinn@example.test" }, 109 .{ .name = "rachel", .display_name = "Rachel Bennett", .email = "rachel@example.test" }, 110 .{ .name = "sam", .display_name = "Sam Brooks", .email = "sam@example.test" }, 111 .{ .name = "tina", .display_name = "Tina Cooper", .email = "tina@example.test" }, 112 }; 113 114 const repo_data = [_]struct { 115 user_index: usize, 116 name: []const u8, 117 description: []const u8, 118 }{ 119 .{ .user_index = 1, .name = "ziglings", .description = "Learn the Zig programming language by fixing tiny broken programs" }, 120 .{ .user_index = 2, .name = "linux", .description = "Linux kernel source tree" }, 121 .{ .user_index = 3, .name = "kubernetes", .description = "Production-grade container orchestration" }, 122 .{ .user_index = 4, .name = "react", .description = "A declarative, efficient, and flexible JavaScript library for building user interfaces" }, 123 .{ .user_index = 5, .name = "typescript", .description = "TypeScript is a superset of JavaScript that compiles to clean JavaScript output" }, 124 .{ .user_index = 6, .name = "rust", .description = "Empowering everyone to build reliable and efficient software" }, 125 .{ .user_index = 7, .name = "go", .description = "The Go programming language" }, 126 .{ .user_index = 8, .name = "nodejs", .description = "Node.js JavaScript runtime" }, 127 .{ .user_index = 9, .name = "cpython", .description = "The Python programming language" }, 128 .{ .user_index = 10, .name = "docker", .description = "Container platform for developing, shipping, and running applications" }, 129 .{ .user_index = 1, .name = "vim", .description = "The ubiquitous text editor" }, 130 .{ .user_index = 2, .name = "neovim", .description = "Hyperextensible Vim-based text editor" }, 131 .{ .user_index = 3, .name = "emacs", .description = "GNU Emacs source code mirror" }, 132 .{ .user_index = 4, .name = "tmux", .description = "Terminal multiplexer" }, 133 .{ .user_index = 5, .name = "zsh", .description = "Mirror of the Z shell source code repository" }, 134 .{ .user_index = 6, .name = "git", .description = "Distributed version control system" }, 135 .{ .user_index = 7, .name = "mercurial", .description = "Source-control management tool" }, 136 .{ .user_index = 8, .name = "tensorflow", .description = "An end-to-end open source machine learning platform" }, 137 .{ .user_index = 9, .name = "pytorch", .description = "Tensors and dynamic neural networks in Python with strong GPU acceleration" }, 138 .{ .user_index = 10, .name = "numpy", .description = "The fundamental package for scientific computing with Python" }, 139 .{ .user_index = 1, .name = "pandas", .description = "Flexible and powerful data analysis and manipulation library for Python" }, 140 .{ .user_index = 2, .name = "scikit-learn", .description = "Machine learning in Python" }, 141 .{ .user_index = 3, .name = "nginx", .description = "High performance HTTP server and reverse proxy" }, 142 .{ .user_index = 4, .name = "redis", .description = "In-memory data structure store, used as a database, cache, and message broker" }, 143 .{ .user_index = 5, .name = "postgres", .description = "The world's most advanced open source relational database" }, 144 .{ .user_index = 6, .name = "sqlite", .description = "Self-contained, serverless, zero-configuration SQL database engine" }, 145 .{ .user_index = 7, .name = "mongodb", .description = "The MongoDB Database" }, 146 .{ .user_index = 8, .name = "elasticsearch", .description = "Free and open, distributed, RESTful search engine" }, 147 .{ .user_index = 9, .name = "kafka", .description = "Distributed event streaming platform" }, 148 .{ .user_index = 10, .name = "terraform", .description = "Infrastructure as code tool" }, 149 .{ .user_index = 11, .name = "svelte", .description = "Cybernetically enhanced web apps" }, 150 .{ .user_index = 12, .name = "vue", .description = "The progressive JavaScript framework" }, 151 .{ .user_index = 13, .name = "flask", .description = "The Python micro framework for building web applications" }, 152 .{ .user_index = 14, .name = "django", .description = "The web framework for perfectionists with deadlines" }, 153 .{ .user_index = 15, .name = "rails", .description = "Ruby on Rails web framework" }, 154 .{ .user_index = 16, .name = "phoenix", .description = "Peace of mind from prototype to production for Elixir web apps" }, 155 .{ .user_index = 17, .name = "laravel", .description = "The PHP framework for web artisans" }, 156 .{ .user_index = 18, .name = "prometheus", .description = "The Prometheus monitoring system and time series database" }, 157 .{ .user_index = 19, .name = "grafana", .description = "The open and composable observability and data visualization platform" }, 158 .{ .user_index = 20, .name = "ansible", .description = "Simple, agentless IT automation" }, 159 }; 160 161 var user_ids: [user_data.len][evt.event_id_size]u8 = undefined; 162 for (&user_ids) |*id| id.* = evt.EventWithId.randomId(prng.random()); 163 164 var repo_event_ids: [repo_data.len][evt.event_id_size]u8 = undefined; 165 for (&repo_event_ids) |*id| id.* = evt.EventWithId.randomId(prng.random()); 166 167 var password_hash_buf: [evt.User.password_hash_max_len]u8 = undefined; 168 const password_hash = try evt.User.hashPassword("password", &password_hash_buf, io); 169 170 // public key matching temp-try/key, given to admin so we can push as admin 171 const admin_ssh_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKeIs8mJqigBZ5y84J4COgnAJJ5bHPKy+lM2SliMXbYm radar@roark"; 172 173 var events_to_consume: [user_data.len + repo_data.len]evt.EventWithId = undefined; 174 for (user_data, 0..) |u, i| { 175 events_to_consume[i] = .{ 176 .id = std.fmt.bytesToHex(user_ids[i], .lower), 177 .event = .{ 178 .user = .{ 179 .name = u.name, 180 .display_name = u.display_name, 181 .email = u.email, 182 .password_hash = password_hash, 183 .ssh_keys = if (i == 0) admin_ssh_key else "", 184 }, 185 }, 186 }; 187 } 188 for (repo_data, 0..) |r, i| { 189 events_to_consume[user_data.len + i] = .{ 190 .id = std.fmt.bytesToHex(repo_event_ids[i], .lower), 191 .event = .{ 192 .repo = .{ 193 .user_id = &user_ids[r.user_index], 194 .name = r.name, 195 .description = r.description, 196 .enable_issue = true, 197 }, 198 }, 199 }; 200 } 201 202 // commit the seed events and consume them into the database 203 try evt.commitAndConsume(evt.admin_repo_opts, io, allocator, &repo, evt.events_ref, &events_to_consume); 204 205 // every repo gets the same generated history, so build it once into a 206 // template repo and copy that to each repo's location below rather than 207 // redoing the expensive commit work for every repo. 208 const template_path = try std.fs.path.join(arena.allocator(), &.{ cwd_path, temp_dir_name, "template" }); 209 { 210 var template_repo = try rp.Repo(.xit, .{}).init(io, allocator, .{ .path = template_path }); 211 defer template_repo.deinit(io, allocator); 212 213 try template_repo.addConfig(io, allocator, .{ .name = "user.name", .value = "haxy" }); 214 try template_repo.addConfig(io, allocator, .{ .name = "user.email", .value = "admin@haxy" }); 215 216 // a README plus a nested doc so the file tree has a directory to 217 // descend into 218 { 219 var repo_dir = try cwd.openDir(io, template_path, .{}); 220 defer repo_dir.close(io); 221 222 const readme = try repo_dir.createFile(io, "README.md", .{}); 223 defer readme.close(io); 224 try readme.writeStreamingAll(io, "# Sample Repo\n\nA repository seeded with test data.\n"); 225 226 try repo_dir.createDirPath(io, "docs/dev"); 227 const doc = try repo_dir.createFile(io, "docs/dev/contribute.md", .{}); 228 defer doc.close(io); 229 try doc.writeStreamingAll(io, "To contribute, please make a pull request"); 230 } 231 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 // a batch of commits so the commits tab has more than one page to 236 // paginate through. each rewrites a few files with scattered line 237 // edits, so every commit is a multi-file diff with several separate 238 // hunks to look at. stepped timestamps vary the date column. 239 const base_ts: u64 = 1_700_000_000; // 2023-11-14 240 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 }; 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 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()); 297 } 298 } 299 try template_repo.add(io, allocator, &edit_files); 300 // a cycling subject padded to a c-varying length so the commit 301 // list shows messages of different widths, capped at 120. 302 var msg_writer = std.Io.Writer.Allocating.init(allocator); 303 defer msg_writer.deinit(); 304 try msg_writer.writer.print("{s}", .{subjects[c % subjects.len]}); 305 const msg_target = 16 + (c * 41) % 96; 306 var mw = c; 307 while (true) : (mw += 1) { 308 const word = words[mw % words.len]; 309 const len = msg_writer.written().len; 310 if (len >= msg_target or len + 1 + word.len > 120) break; 311 try msg_writer.writer.print(" {s}", .{word}); 312 } 313 const message = try arena.allocator().dupe(u8, msg_writer.written()); 314 _ = try template_repo.commit(io, allocator, .{ .message = message, .timestamp = base_ts + c * std.time.s_per_day }); 315 } 316 } 317 318 // copy the template to each repo's on-disk location, named by its 319 // hex-encoded event id. the template repo is deinitialized above, so its 320 // db file is fully written before we copy it. 321 { 322 var template_dir = try cwd.openDir(io, template_path, .{ .iterate = true }); 323 defer template_dir.close(io); 324 325 for (repo_event_ids) |id_bytes| { 326 const repo_id = std.fmt.bytesToHex(id_bytes, .lower); 327 const repo_path = try std.fs.path.join(arena.allocator(), &.{ cwd_path, temp_dir_name, "server", "repos", &repo_id }); 328 var dest_dir = try cwd.createDirPathOpen(io, repo_path, .{}); 329 defer dest_dir.close(io); 330 try copyDir(io, template_dir, dest_dir); 331 } 332 } 333 334 break :blk try ui.Session.init(&session_arena, &repo, .{}); 335 }; 336 session.is_terminal = true; 337 // try uses the default serve options below, so the footer's url points at 338 // the default web UI port. 339 session.web_port = try (srv.Options{}).wuiPort(); 340 341 // start the server 342 343 var cli = false; 344 345 var arg_it = try init.minimal.args.iterateAllocator(allocator); 346 defer arg_it.deinit(); 347 _ = arg_it.skip(); 348 while (arg_it.next()) |arg| { 349 if (std.mem.eql(u8, "--cli", arg)) { 350 cli = true; 351 } 352 } 353 354 const server_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "server" }); 355 defer allocator.free(server_path); 356 357 // let the native TUI's page builders open the on-disk repos for the file tree 358 session.io = io; 359 session.repos_dir = try std.fs.path.join(session_arena.allocator(), &.{ server_path, "repos" }); 360 361 if (cli) { 362 var stdout_writer = std.Io.File.stdout().writer(io, &.{}); 363 var stderr_writer = std.Io.File.stderr().writer(io, &.{}); 364 const run_opts = hx.main.RunOpts{ .out = &stdout_writer.interface, .err = &stderr_writer.interface }; 365 366 const key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" }); 367 defer allocator.free(key_path); 368 369 const Runnable = struct { 370 io: std.Io, 371 key_path: []const u8, 372 373 pub fn run(self: @This()) !void { 374 std.debug.print( 375 \\ 376 \\connect to the TUI with: 377 \\ ssh -p 8022 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null localhost 378 \\ 379 \\create a git repo and push over SSH: 380 \\ mkdir -p temp-try/client/test 381 \\ cd temp-try/client/test 382 \\ git init 383 \\ echo "hello" > hello.txt 384 \\ git add hello.txt 385 \\ git commit -m "let there be light" 386 \\ 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 387 \\ 388 \\to quit, press enter. 389 \\ 390 , .{self.key_path}); 391 // portable stdin read via std.Io — equivalent to the 392 // std.posix.read(STDIN_FILENO, ...) that doesn't compile 393 // on windows. 394 var buf: [1]u8 = undefined; 395 var stdin_reader = std.Io.File.stdin().reader(self.io, &buf); 396 _ = stdin_reader.interface.takeByte() catch {}; 397 } 398 }; 399 400 try srv.run(.xit, .{}, io, allocator, cwd_path, .{ 401 .data_dir = server_path, 402 }, run_opts.err, Runnable{ .io = io, .key_path = key_path }); 403 } else { 404 var null_writer = std.Io.Writer.Discarding.init(&.{}); 405 const run_opts = hx.main.RunOpts{ .out = &null_writer.writer, .err = &null_writer.writer }; 406 407 const Runnable = struct { 408 io: std.Io, 409 allocator: std.mem.Allocator, 410 session: *ui.Session, 411 412 pub fn run(self: @This()) !void { 413 // launch the TUI 414 try hx.ui.run(self.io, self.allocator, self.session); 415 } 416 }; 417 418 try srv.run(.xit, .{}, io, allocator, cwd_path, .{ 419 .data_dir = server_path, 420 }, run_opts.err, Runnable{ .io = io, .allocator = allocator, .session = &session }); 421 } 422 } 423 424 // recursively copy the contents of src_dir into dest_dir 425 fn copyDir(io: std.Io, src_dir: std.Io.Dir, dest_dir: std.Io.Dir) !void { 426 var iter = src_dir.iterate(); 427 while (try iter.next(io)) |entry| { 428 switch (entry.kind) { 429 .file => try src_dir.copyFile(entry.name, dest_dir, entry.name, io, .{}), 430 .directory => { 431 try dest_dir.createDirPath(io, entry.name); 432 var dest_entry_dir = try dest_dir.openDir(io, entry.name, .{ .access_sub_paths = true, .iterate = true, .follow_symlinks = false }); 433 defer dest_entry_dir.close(io); 434 var src_entry_dir = try src_dir.openDir(io, entry.name, .{ .access_sub_paths = true, .iterate = true, .follow_symlinks = false }); 435 defer src_entry_dir.close(io); 436 try copyDir(io, src_entry_dir, dest_entry_dir); 437 }, 438 else => {}, 439 } 440 } 441 } 442