╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ undo.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
99 session.data.current_page = files; 100 const page = try ui.Repo.init(&arena, &session, files); 101 try std.testing.expectEqual(allowed, page.undo != null); 102 if (page.undo) |undo| try std.testing.expectEqual(user != null and std.mem.eql(u8, user.?, &owner), undo.handle.canUndo()); 103 var root = ui.Widget{ .repo = try ui.Repo.View.init(allocator, &page, &session) }; 104 defer root.deinit(allocator); 105 try root.build(allocator, constraint, root.getFocus()); 106 const html = try @import("../web.zig").generateHtml(allocator, &root, &session); 107 defer allocator.free(html); 108 try std.testing.expectEqual(allowed, std.mem.indexOf(u8, html, "/alice:demo/undo") != null); 109 const route = ui.RoutablePage.repoUndoRoute("alice:demo", null).?; 110 if (allowed) { 111 _ = try ui.Repo.init(&arena, &session, route); 112 } else { 113 try std.testing.expectError(error.NotFound, ui.Repo.init(&arena, &session, route)); 114 } 115 if (user == null or !std.mem.eql(u8, user.?, &owner)) { 116 try std.testing.expectError(error.Forbidden, Undo.perform(allocator, &session, ui.RoutablePage.repoUndoRoute("alice:demo", 2).?.repo_undo)); 117 } 118 } 119 var session = try ui.Session.init(&arena, &admin, .{ .user_id = &owner }); 120 session.io = io; 121 session.users_dir = users_dir; 122 try Undo.perform(allocator, &session, ui.RoutablePage.repoUndoRoute("alice:demo", 2).?.repo_undo); 123 try std.testing.expectEqual(null, session.next_page.?.repo_undo.index); 124 { 125 var repo = try xit.repo.Repo(.xit, .{}).open(io, allocator, .{ .path = path }); 126 defer repo.deinit(io, allocator); 127 try expectConfig(&repo, "first"); 128 const data = try Undo.init(.{}, &arena, &repo, null, "alice:demo", null, .{ .location = .{ .repo = "" } }); 129 try std.testing.expectEqualStrings("undo", data.items[0].action); 130 } 131 // keep the old session/page moment, but transfer ownership to the writer. 132 repo_event.user_id = writer_hex; 133 try evt.consume(.{ .server = .{ .users_dir = users_dir } }, .user, .xit, evt.user_repo_opts, io, allocator, &user_repo, evt.events_ref, &.{.{ .id = std.fmt.bytesToHex(repo_id, .lower), .author = author, .event = .{ .repo = repo_event } }}); 134 session.next_page = null; 135 Undo.handleRequest(allocator, &session, ui.RoutablePage.repoUndoRoute("alice:demo", 1).?.repo_undo); 136 try std.testing.expectEqualStrings("Forbidden", session.data.undo_failure.?); 137 try std.testing.expectEqual(null, session.next_page); 138 139 // public-write repos still require login for history and undo. 140 repo_event.user_id = std.fmt.bytesToHex(owner, .lower); 141 repo_event.write_access = .public; 142 try evt.consume(.{ .server = .{ .users_dir = users_dir } }, .user, .xit, evt.user_repo_opts, io, allocator, &user_repo, evt.events_ref, &.{.{ .id = std.fmt.bytesToHex(repo_id, .lower), .author = author, .event = .{ .repo = repo_event } }}); 143 var anonymous = try ui.Session.init(&arena, &admin, .{}); 144 anonymous.io = io; 145 anonymous.users_dir = users_dir; 146 try std.testing.expectError(error.NotFound, ui.Repo.init(&arena, &anonymous, ui.RoutablePage.repoUndoRoute("alice:demo", null).?)); 147 } 148 149 test "local git has no undo and local xit can undo and refresh" { 150 inline for ([_]xit.repo.RepoKind{ .git, .xit }) |kind| { 151 var temp = std.testing.tmpDir(.{}); 152 defer temp.cleanup(); 153 var arena = std.heap.ArenaAllocator.init(allocator); 154 defer arena.deinit(); 155 const path = try temp.dir.realPathFileAlloc(io, ".", arena.allocator()); 156 { 157 var repo = try xit.repo.Repo(kind, .{}).init(io, allocator, .{ .path = path }); 158 defer repo.deinit(io, allocator); 159 try repo.addConfig(io, allocator, .{ .name = "undo.value", .value = "first" }); 160 } 161 var session = ui.Session{ .arena = &arena, .page_arena = &arena, .io = io, .local = .{ .path = path, .repo_kind = kind }, .data = .{ .host_kind = .local } }; 162 const files = ui.RoutablePage.repoFilesRoute("", null, "", "", 0).?; 163 const data = try ui.Repo.init(&arena, &session, files); 164 try std.testing.expectEqual(kind == .xit, data.undo != null); 165 if (kind == .git) { 166 try std.testing.expectError(error.NotFound, ui.Repo.init(&arena, &session, ui.RoutablePage.repoUndoRoute("", null).?)); 167 try std.testing.expectError(error.NotFound, Undo.execute(io, allocator, session.local.?, 1)); 168 } else { 169 Undo.handleRequest(allocator, &session, ui.RoutablePage.repoUndoRoute("", 999).?.repo_undo); 170 try std.testing.expectEqualStrings("InvalidHistoryIndex", session.data.undo_failure.?); 171 try std.testing.expectEqual(null, session.next_page); 172 Undo.handleRequest(allocator, &session, ui.RoutablePage.repoUndoRoute("", 1).?.repo_undo); 173 try std.testing.expectEqual(null, session.data.undo_failure); 174 const refreshed = try ui.Repo.init(&arena, &session, session.next_page.?); 175 try std.testing.expectEqualStrings("undo", refreshed.undo.?.items[0].action); 176 } 177 } 178 } 179 180 test "commit undo records use the formatted description" { 181 var temp = std.testing.tmpDir(.{}); 182 defer temp.cleanup(); 183 var arena = std.heap.ArenaAllocator.init(allocator); 184 defer arena.deinit(); 185 const path = try temp.dir.realPathFileAlloc(io, ".", arena.allocator()); 186 const opts: xit.repo.RepoOpts(.xit) = .{ .is_test = true }; 187 var repo = try xit.repo.Repo(.xit, opts).init(io, allocator, .{ .path = path }); 188 defer repo.deinit(io, allocator); 189 _ = try repo.commitAtRef(io, allocator, .{ .message = "example commit" }, null, .{ .kind = .head, .name = "master" }); 190 const data = try Undo.init(opts, &arena, &repo, null, "", null, .{ .location = .{ .repo = "" } }); 191 try std.testing.expect(std.mem.startsWith(u8, data.items[0].description, "commit -m \"example commit\"")); 192 } 193 194 test "malformed undo history is shown as an error" { 195 var temp = std.testing.tmpDir(.{}); 196 defer temp.cleanup(); 197 var arena = std.heap.ArenaAllocator.init(allocator); 198 defer arena.deinit(); 199 const path = try temp.dir.realPathFileAlloc(io, ".", arena.allocator()); 200 const Repo = xit.repo.Repo(.xit, .{}); 201 var repo = try Repo.init(io, allocator, .{ .path = path }); 202 defer repo.deinit(io, allocator); 203 204 // append an invalid undo record without damaging the repository data. 205 const Corrupt = struct { 206 pub fn run(_: @This(), cursor: *Repo.DB.Cursor(.read_write)) !void { 207 const moment = try Repo.DB.HashMap(.read_write).init(cursor.*); 208 try moment.put(xit.hash.hashInt(.sha1, "undo"), .{ .bytes = "bad" }); 209 } 210 }; 211 const history = try Repo.DB.ArrayList(.read_write).init(repo.core.db.rootCursor()); 212 try history.appendContext(.{ .slot = try history.getSlot(-1) }, Corrupt{}); 213 214 var session = ui.Session{ .arena = &arena, .page_arena = &arena, .io = io, .local = .{ .path = path, .repo_kind = .xit }, .data = .{ .host_kind = .local } }; 215 // tabs switch in-page, so every tab carries the undo data and its failure 216 const files = try ui.Repo.init(&arena, &session, ui.RoutablePage.repoFilesRoute("", null, "", "", 0).?); 217 try std.testing.expectEqual(0, files.undo.?.items.len); 218 try std.testing.expectEqualStrings("InvalidUndoRecord", files.undo.?.failure.?); 219 const undo = try ui.Repo.init(&arena, &session, ui.RoutablePage.repoUndoRoute("", null).?); 220 try std.testing.expectEqualStrings("InvalidUndoRecord", undo.undo.?.failure.?); 221 222 var root = ui.Widget{ .repo_undo = try Undo.View.init(allocator, &undo.undo.?, &session) }; 223 defer root.deinit(allocator); 224 try root.build(allocator, constraint, root.getFocus()); 225 const html = try @import("../web.zig").generateHtml(allocator, &root, &session); 226 defer allocator.free(html); 227 try std.testing.expect(std.mem.indexOf(u8, html, "InvalidUndoRecord") != null); 228 } 229