╭ changed ───────────╮ │src/ui/Repo/Undo.zig│ ╰────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │148 if (try pushUser(arena, haxy_moment, record.payload)) |button| try buttons.append(aa, button); │ │149 const count = try momentEventCount(opts, core, moment, history_index) orelse 0; │ │150 if (count > 0) try buttons.append(aa, try eventsButton(arena, identity, history_index, count));│ │151 + try pushRefs(arena, identity, record.payload, &buttons); │ │152 return .{ .action = "push", .description = "received a push", .buttons = buttons.items }; │ │153 } │ │154 if (!std.mem.eql(u8, record.action_kind, evt.undo_action)) return null; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │208 }; │ │209 } │ │210 │ │211 +// a button per ref the push moved, naming what it did to it: a changed branch │ │212 +// links to the commits it added, anything still there to the ref itself │ │213 +fn pushRefs(arena: *std.heap.ArenaAllocator, identity: []const u8, payload: []const u8, buttons: *std.ArrayList(DetailButton)) !void { │ │214 + const head_prefix = "refs/heads/"; │ │215 + const tag_prefix = "refs/tags/"; │ │216 + const Payload = struct { │ │217 + const Ref = struct { name: []const u8, old: []const u8, new: []const u8 }; │ │218 + refs: []const Ref = &.{}, │ │219 + }; │ │220 + const parsed = std.json.parseFromSlice(Payload, arena.child_allocator, payload, .{ .ignore_unknown_fields = true }) catch |err| switch (err) {│ │221 + error.OutOfMemory => return err, │ │222 + else => return, │ │223 + }; │ │224 + defer parsed.deinit(); │ │225 + │ │226 + const aa = arena.allocator(); │ │227 + for (parsed.value.refs) |ref| { │ │228 + // anything that is neither a head nor a tag is skipped │ │229 + const head = std.mem.startsWith(u8, ref.name, head_prefix); │ │230 + const prefix = if (head) head_prefix else tag_prefix; │ │231 + if (!std.mem.startsWith(u8, ref.name, prefix)) continue; │ │232 + const name = ref.name[prefix.len..]; │ │233 + │ │234 + // a removed ref has nothing left to link to. a branch that was created │ │235 + // has no range to show either: what it added is whatever the other │ │236 + // refs did not already reach, which a base cannot express │ │237 + const removed = ref.new.len == 0; │ │238 + const created = ref.old.len == 0; │ │239 + const route = if (removed) │ │240 + null │ │241 + else if (head and !created) │ │242 + ui.RoutablePage.repoCommitsRoute(identity, .object, ref.new, 0, "", ref.old) orelse continue │ │243 + else │ │244 + ui.RoutablePage.repoFilesRoute(identity, if (head) .branch else .tag, try ui.urlEncodeRef(aa, name), "", 0) orelse continue; │ │245 + │ │246 + const action = if (removed) "removed" else if (created) "created" else "changed"; │ │247 + try buttons.append(aa, .{ │ │248 + .label = try std.fmt.allocPrint(aa, " {s} {s} ", .{ if (head) "branch" else "tag", action }), │ │249 + .text = try aa.dupe(u8, name), │ │250 + .link = if (route) |value| try std.fmt.allocPrint(aa, "a:{s}", .{try value.toUrl(arena)}) else null, │ │251 + }); │ │252 + } │ │253 +} │ │254 + │ │255 // the push record stores the email of the user whose key the server accepted. │ │256 // resolving it names them as they are now, and leaves the email when no user │ │257 // holds it, which is also what local mode sees with no admin db to read │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯