╭ changed ───╮ │src/push.zig│ ╰────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │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( │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯