╭ changed ────╮ │build.zig.zon│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 3 .version = "0.0.0", │ │ 4 .dependencies = .{ │ │ 5 .xit = .{ │ │ 6 - .url = "git+https://github.com/xit-vcs/xit#380d7e4fbea97f0c50c58836d9f62e68be95fc10",│ │ 7 - .hash = "xit-0.0.0-Ub3nDBpTHQCRGhTyvfdmQmnVURvN5qIE1QKC_oaMWT6m", │ │ 6 + .url = "git+https://github.com/xit-vcs/xit#c931aec1a03a452effa66b004f58315daf33ce72",│ │ 7 + .hash = "xit-0.0.0-Ub3nDAhnHQAyQ64doxEd2l6VQptCPDcotfvwLpBH2_05", │ │ 8 }, │ │ 9 }, │ │10 .paths = .{ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────╮ │src/progress.zig│ ╰────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │35 // fights with the client's own upload bar over the same row. │ │36 fn reported(kind: rp.ProgressKind) bool { │ │37 return switch (kind) { │ │38 - .writing_object, .checking_object, .walking_commit, .enumerating_object, .compressing_object, .writing_patch => true, │ │38 + .writing_object, .checking_object, .walking_commit, .enumerating_object, .compressing_object, .writing_patch, .pruning_database, .compacting_database => true,│ │39 .writing_object_from_pack, .sending_bytes, .receiving_bytes => false, │ │40 }; │ │41 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │52 .walking_commit => self.label = "Walking commits", │ │53 .enumerating_object => self.label = "Enumerating objects", │ │54 .compressing_object => self.label = "Compressing objects", │ │55 + .pruning_database => self.label = "Pruning database", │ │56 + .compacting_database => self.label = "Compacting database",│ │57 else => {}, │ │58 } │ │59 self.total = start.estimated_total_items; │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───╮ │src/push.zig│ ╰────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │44 // the action a received push records │ │45 pub const undo_action = "haxy/push"; │ │46 │ │47 +// deleting this ref, as a push's only update, clears the undo history. it │ │48 +// never exists, so a push has to name it in full. │ │49 +pub const clear_undo_ref = "refs/haxy/undo"; │ │50 + │ │51 // the fixture records a stand-in push with this too, so both write one shape│ │52 pub fn writeUndo( │ │53 comptime repo_opts: rp.RepoOpts(.xit), │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │161 receive_options.applied_ref_updates = ctx.updates; │ │162 receive_options.deferred_response = ctx.response; │ │163 try xit.net_server_receive_pack.run(.xit, repo_opts, state, ctx.io, ctx.allocator, ctx.reader, ctx.writer, receive_options, ctx.sideband);│ │164 + // receive-pack refuses the clear ref alongside any other update. │ │165 + // clearing writes nothing here, and runs once this transaction │ │166 + // lets go of the lock. │ │167 + const applied = ctx.updates.items.items; │ │168 + if (applied.len == 1 and std.mem.eql(u8, applied[0].ref_name, clear_undo_ref)) return error.ClearUndoHistory; │ │169 try writeReceivedPatches(repo_opts, state, ctx.io, ctx.allocator, ctx.response, ctx.writer); │ │170 if (ctx.sideband.gone) return error.ClientGone; │ │171 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │220 }, │ │221 ); │ │222 }; │ │214 - result catch |err| { │ │215 - response.finish(writer, @errorName(err)) catch {}; │ │216 - if (err == error.CancelTransaction) return; │ │217 - return err; │ │223 + result catch |err| switch (err) { │ │224 + // a refused push has already written the report saying why │ │225 + error.CancelTransaction => return response.finish(writer, null), │ │226 + error.ClearUndoHistory => { │ │227 + clearUndoHistory(repo_opts, io, allocator, repo, &sideband) catch |clear_err| { │ │228 + response.finish(writer, @errorName(clear_err)) catch {}; │ │229 + return clear_err; │ │230 + }; │ │231 + return response.finish(writer, null); │ │232 + }, │ │233 + else => { │ │234 + response.finish(writer, @errorName(err)) catch {}; │ │235 + return err; │ │236 + }, │ │237 }; │ │238 if (moment_index) |index| notify.deliverTransaction(io, allocator, server, repo_opts, repo, index, pusher_id, &sideband);│ │239 evt.bumpRepoActivityAt(io, allocator, server, repo.core.work_path); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │243 pch.refreshOpenMergeability(repo_opts, io, allocator, repo, server.users_dir, null, &sideband); │ │244 }; │ │245 try response.finish(writer, null); │ │246 +} │ │247 + │ │248 +// discard every earlier transaction by compacting the database. the patch │ │249 +// revisions events point at are unreachable from the refs, so they are named │ │250 +// as extra roots to survive the collection. │ │251 +fn clearUndoHistory(comptime repo_opts: rp.RepoOpts(.xit), io: std.Io, allocator: std.mem.Allocator, repo: *rp.Repo(.xit, repo_opts), sideband: *progress.Sideband) !void {│ │252 + // a repo that has consumed no events has no revisions to protect │ │253 + const roots = if (evt.currentMoment(repo_opts, repo)) |moment| │ │254 + try evt.PatchRev.gcRoots(rp.Repo(.xit, repo_opts).DB, repo_opts.hash, allocator, moment) │ │255 + else |_| │ │256 + &.{}; │ │257 + defer allocator.free(roots); │ │258 + _ = try repo.garbageCollect(io, allocator, .{ .extra_roots = roots }, sideband); │ │259 } │ │260 │ │261 pub fn receiveFork( │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │413 var sideband = progress.Sideband{ .writer = &writer.interface, .sess = sess }; │ │414 try repo.uploadPack(io, allocator, &reader.interface, &writer.interface, .{ .protocol_version = protocol_version }, &sideband); │ │415 }, │ │416 - .push => |pusher| try push.receivePackAndConsume(repo.self_repo_opts, io, allocator, repo, &reader.interface, &writer.interface, .{ .protocol_version = protocol_version, .protected_branches = pusher.protected_branches, .locked_branches = pusher.locked_branches }, pusher.author, &pusher.user_id, handler.server, handler.err, sess),│ │416 + .push => |pusher| try push.receivePackAndConsume(repo.self_repo_opts, io, allocator, repo, &reader.interface, &writer.interface, .{ │ │417 + .protocol_version = protocol_version, │ │418 + .protected_branches = pusher.protected_branches, │ │419 + .locked_branches = pusher.locked_branches, │ │420 + .pseudo_ref = .{ .name = push.clear_undo_ref, .deletable = pusher.owner }, │ │421 + }, pusher.author, &pusher.user_id, handler.server, handler.err, sess), │ │422 }, │ │423 } │ │424 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │456 var repo = try rp.Repo(.xit, any_repo_opts.toRepoOptsWithHash(kind)).open(io, allocator, .{ .path = repo_path }); │ │457 defer repo.deinit(io, allocator); │ │458 // stateless since the advertisement already went out │ │454 - try push.receivePackAndConsume(repo.self_repo_opts, io, allocator, &repo, reader, writer, .{ .protocol_version = protocol_version, .is_stateless = true }, creator.owner, &creator.owner_id, handler.server, handler.err, sess); │ │459 + try push.receivePackAndConsume(repo.self_repo_opts, io, allocator, &repo, reader, writer, .{ .protocol_version = protocol_version, .is_stateless = true, .pseudo_ref = .{ .name = push.clear_undo_ref, .deletable = true } }, creator.owner, &creator.owner_id, handler.server, handler.err, sess);│ │460 }, │ │461 } │ │462 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────╮ │565 author: evt.CommitAuthor, │ │566 protected_branches: []const []const u8, │ │567 locked_branches: []const []const u8, │ │568 + // only owners may clear the undo history│ │569 + owner: bool, │ │570 }; │ │571 │ │572 // the owner of a repo a push may create │ ╰──────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯