╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │1843 return order_key[@sizeOf(u64)..][0..event_id_size].*; │ │1844 } │ │1845 │ │1846 -// split a pushed "<owner>/<repo>" path into its two components, or null if it │ │1847 -// isn't exactly two non-empty segments. │ │1846 +// split a pushed "<owner>:<repo>" path into its two components, or null if it │ │1847 +// isn't exactly two non-empty parts with no "/". │ │1848 pub fn parseOwnerRepoPath(path: []const u8) ?struct { owner: []const u8, name: []const u8 } {│ │1849 - var it = std.mem.splitScalar(u8, path, '/'); │ │1849 + if (std.mem.indexOfScalar(u8, path, '/') != null) return null; │ │1850 + var it = std.mem.splitScalar(u8, path, ':'); │ │1851 const owner = it.next() orelse return null; │ │1852 const name = it.next() orelse return null; │ │1853 if (it.next() != null) return null; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │1866 repo_id: [event_id_size]u8, │ │1867 }; │ │1868 │ │1868 -// resolve a pushed `<owner>/<repo>` to the ids that locate its on-disk directory│ │1869 +// resolve a pushed `<owner>:<repo>` to the ids that locate its on-disk directory│ │1870 pub fn resolveOrCreateRepo( │ │1871 io: std.Io, │ │1872 allocator: std.mem.Allocator, │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/event/User.zig│ ╰──────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │42 if (name.len < name_min_len) return error.NameTooShort; │ │43 if (name.len > name_max_len) return error.NameTooLong; │ │44 if (name[0] == '-' or name[name.len - 1] == '-') return error.InvalidName;│ │45 + // reserved for the top-level pages' url segment │ │46 + if (std.ascii.eqlIgnoreCase(name, "home")) return error.InvalidName; │ │47 │ │48 var previous_was_hyphen = false; │ │49 for (name) |c| { │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───╮ │src/fork.zig│ ╰────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │18 id: [evt.event_id_size * 2]u8, │ │19 }; │ │20 │ │21 +// parse a "<forker>:<repo>+<patch id>" fork path │ │22 pub fn parseRoute(route_path: []const u8) ?Route { │ │22 - const patch_segment = "/patch:"; │ │23 - const patch_start = std.mem.indexOf(u8, route_path, patch_segment) orelse return null;│ │24 - const identity = route_path[0..patch_start]; │ │25 - const id_text = route_path[patch_start + patch_segment.len ..]; │ │23 + const plus = std.mem.indexOfScalar(u8, route_path, '+') orelse return null; │ │24 + const identity = route_path[0..plus]; │ │25 + const id_text = route_path[plus + 1 ..]; │ │26 if (identity.len == 0 or id_text.len != evt.event_id_size * 2) return null; │ │27 const id_bytes = evt.parseEventId(id_text) catch return null; │ │28 return .{ .identity = identity, .id = std.fmt.bytesToHex(id_bytes, .lower) }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────╮ │src/serve_common.zig│ ╰────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │ 6 // the http and ssh paths each map the cases to their own error responses. │ │ 7 pub const RepoPath = union(enum) { │ │ 8 ok: []const u8, // resolved on-disk path; the caller owns and frees it │ │ 9 - invalid, // a <owner>/<repo> path was expected but not given │ │ 9 + invalid, // a <owner>:<repo> path was expected but not given │ │10 not_found, // unknown owner, or the repo doesn't exist and we aren't creating│ │11 }; │ │12 │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │13 // resolve a requested repo path to its on-disk directory, parsed as │ │14 -// <owner>/<repo> through the event store, minting a repo event for a fresh push.│ │14 +// <owner>:<repo> through the event store, minting a repo event for a fresh push.│ │15 pub fn resolveRepoPath( │ │16 io: std.Io, │ │17 allocator: std.mem.Allocator, │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/serve_http.zig│ ╰──────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │121 │ │122 const owner_repo = evt.parseOwnerRepoPath(repo_rel) orelse { │ │123 if (http_server.reader.state == .received_head) http_server.reader.state = .ready; │ │124 - try writeSimpleResponse(http_server, 400, "Bad Request", "text/plain", "repo path must be <owner>/<repo>");│ │124 + try writeSimpleResponse(http_server, 400, "Bad Request", "text/plain", "repo path must be <owner>:<repo>");│ │125 return; │ │126 }; │ │127 const repo_path = blk: { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │370 │ │371 const parsed = parseGitCommand(allocator, exec.command) catch return writeError(sess, "unsupported command (expected git-upload-pack or git-receive-pack)"); │ │372 defer parsed.deinit(allocator); │ │373 - const path = std.mem.trimStart(u8, parsed.dir, "/"); │ │374 - const fork_prefix = "fork/"; │ │373 + const repo_identity = std.mem.trimStart(u8, parsed.dir, "/"); │ │374 │ │376 - if (std.mem.startsWith(u8, path, fork_prefix)) { │ │377 - return runForkSession(handler, sess, path[fork_prefix.len..], parsed.service, protocol_version, &reader.interface, &writer.interface); │ │375 + // a "+" names a fork's patch draft │ │376 + if (std.mem.indexOfScalar(u8, repo_identity, '+') != null) { │ │377 + return runForkSession(handler, sess, repo_identity, parsed.service, protocol_version, &reader.interface, &writer.interface); │ │378 } │ │379 │ │380 - const repo_prefix = "repo/"; │ │381 - const repo_identity = if (std.mem.startsWith(u8, path, repo_prefix)) path[repo_prefix.len..] else path; │ │382 - │ │380 const create_if_missing = parsed.service == .receive_pack; │ │381 │ │385 - const owner_repo = evt.parseOwnerRepoPath(repo_identity) orelse return writeError(sess, "repo path must be <owner>/<repo>"); │ │382 + const owner_repo = evt.parseOwnerRepoPath(repo_identity) orelse return writeError(sess, "repo path must be <owner>:<repo>"); │ │383 var author_arena = std.heap.ArenaAllocator.init(allocator); │ │384 defer author_arena.deinit(); │ │385 const author = switch (try authorizeRepoKey(io, &author_arena, handler.admin_repo_path, handler.users_dir, owner_repo.owner, owner_repo.name, parsed.service, &sess.fingerprint)) {│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │423 │ │424 const route = fork.parseRoute(fork_path) orelse return writeError(sess, "invalid fork path"); │ │425 // the route names the forker and the target repo │ │429 - const forker_repo = evt.parseOwnerRepoPath(route.identity) orelse return writeError(sess, "repo path must be <forker>/<repo>");│ │426 + const forker_repo = evt.parseOwnerRepoPath(route.identity) orelse return writeError(sess, "repo path must be <forker>:<repo>");│ │427 │ │428 var admin = try rp.Repo(.xit, evt.admin_repo_opts).open(io, allocator, .{ .path = handler.admin_repo_path }); │ │429 defer admin.deinit(io, allocator); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────╮ │src/test/net.zig│ ╰────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │570 var client = try rp.Repo(.xit, .{ .hash = hash_kind, .is_test = true }).clone(│ │571 io, │ │572 allocator, │ │573 - "git@localhost:admin/target", │ │573 + "git@localhost:admin:target", │ │574 client_path, │ │575 client_path, │ │576 null, │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯