╭ changed ────╮ │src/patch.zig│ ╰─────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │110 }; │ │111 } │ │112 │ │113 -// refresh tracked branches outside the caller's transaction │ │113 +// refresh tracked branches and, on the server, check open patches for merges.│ │114 +// this runs outside the caller's transaction. │ │115 pub fn refreshBranches( │ │116 host_kind: evt.HostKind, │ │117 comptime repo_kind: rp.RepoKind, │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────╮ │120 allocator: std.mem.Allocator, │ │121 repo: *rp.Repo(repo_kind, repo_opts), │ │122 updates: ?[]const xit.net_server_receive_pack.AppliedRefUpdate,│ │123 + progress_ctx_maybe: ?repo_opts.ProgressCtx, │ │124 ) !void { │ │125 const DB = evt.EventDB(repo_opts.hash); │ │126 var arena = std.heap.ArenaAllocator.init(allocator); │ ╰────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │127 defer arena.deinit(); │ │126 - var patches: std.ArrayList(struct { id: [evt.event_id_size]u8, record: evt.Patch.Record }) = .empty; │ │128 + var patches: std.AutoArrayHashMapUnmanaged([evt.event_id_size]u8, ?evt.Patch.Record) = .empty; │ │129 defer patches.deinit(allocator); │ │128 - { │ │130 + collect: { │ │131 var local_db: ?evt.LocalEventDB(repo_opts.hash) = if (repo_kind == .git) try evt.LocalEventDB(repo_opts.hash).openReadOnly(io, allocator, repo.core.repo_dir) else null; │ │132 defer if (local_db) |*db| db.deinit(io, allocator); │ │131 - const moment = (if (local_db) |*db| evt.currentMomentFromDb(repo_opts.hash, db.db) else if (repo_kind == .git) return else evt.currentMoment(repo_opts, repo)) catch |err| switch (err) { │ │132 - error.NotFound => return, │ │133 + const moment = (if (local_db) |*db| evt.currentMomentFromDb(repo_opts.hash, db.db) else if (repo_kind == .git) break :collect else evt.currentMoment(repo_opts, repo)) catch |err| switch (err) {│ │134 + error.NotFound => break :collect, │ │135 else => return err, │ │136 }; │ │135 - const source_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, evt.Patch.source_to_id_set_key)) orelse return; │ │137 + if (repo_kind == .xit and host_kind == .server) open: { │ │138 + const statuses_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, evt.Patch.status_to_id_set_key)) orelse break :open; │ │139 + const statuses = try DB.SortedMap(.read_only).init(statuses_cursor); │ │140 + const open_cursor = try statuses.getCursor("open") orelse break :open; │ │141 + const open_patches = try DB.SortedSet(.read_only).init(open_cursor); │ │142 + var iter = try open_patches.iteratorFromIndex(0); │ │143 + while (try iter.next()) |cursor| try patches.put(allocator, try evt.readOrderKeyId(DB, cursor), null); │ │144 + } │ │145 + const source_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, evt.Patch.source_to_id_set_key)) orelse break :collect; │ │146 const sources = try DB.SortedMap(.read_only).init(source_cursor); │ │147 const conflicts = if (try moment.getCursor(hash.hashInt(repo_opts.hash, evt.Patch.conflicts_key))) |cursor| try DB.SortedMap(.read_only).init(cursor) else null; │ │148 var iter = try sources.iterator(); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │172 const record = (try evt.Patch.readById(DB, repo_opts.hash, moment, &arena, &id)) orelse continue; │ │173 if (conflicts) |map| if (try map.getCursor(&evt.orderKeyDesc(record.created_order, &id)) != null) continue; │ │174 if (record.event.revision) |revision| if (std.mem.eql(u8, revision.source_oid, &source)) continue; │ │165 - try patches.append(allocator, .{ .id = id, .record = record }); │ │175 + try patches.put(allocator, id, record); │ │176 } │ │177 } │ │178 + } │ │179 + if (repo_opts.ProgressCtx != void) { │ │180 + if (progress_ctx_maybe) |progress_ctx| progress_ctx.run(io, .{ .start = .{ .kind = .writing_patch, .estimated_total_items = patches.count() } }) catch {}; │ │181 } │ │169 - for (patches.items) |patch| { │ │170 - const author = evt.CommitAuthor{ .name = "haxy", .email = patch.record.author_email orelse "user@haxy" }; │ │171 - writeBranchPatch(host_kind, repo_kind, repo_opts, io, allocator, repo, std.fmt.bytesToHex(patch.id, .lower), patch.record.event, patch.record.event, author) catch |err| {│ │172 - std.log.warn("failed to refresh branch patch: {s}", .{@errorName(err)}); │ │173 - }; │ │182 + for (patches.keys(), patches.values()) |id, record_maybe| { │ │183 + if (record_maybe) |record| { │ │184 + const author = evt.CommitAuthor{ .name = "haxy", .email = record.author_email orelse "user@haxy" }; │ │185 + writeBranchPatch(host_kind, repo_kind, repo_opts, io, allocator, repo, std.fmt.bytesToHex(id, .lower), record.event, record.event, author) catch |err| { │ │186 + std.log.warn("failed to refresh branch patch: {s}", .{@errorName(err)}); │ │187 + }; │ │188 + } │ │189 + if (repo_kind == .xit and host_kind == .server) refreshMergeability(repo_opts, io, allocator, repo, id); │ │190 + if (repo_opts.ProgressCtx != void) { │ │191 + if (progress_ctx_maybe) |progress_ctx| progress_ctx.run(io, .{ .complete_one = .writing_patch }) catch {}; │ │192 + } │ │193 + } │ │194 + if (repo_opts.ProgressCtx != void) { │ │195 + if (progress_ctx_maybe) |progress_ctx| progress_ctx.run(io, .{ .end = .writing_patch }) catch {}; │ │196 } │ │197 } │ │198 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───╮ │src/push.zig│ ╰────────────╯ ╭────────────────────────────────────────────────────────╮ │13 pub const PushProgress = struct { │ │14 response: *xit.net_server_receive_pack.Response,│ │15 writer: *std.Io.Writer, │ │16 + label: []const u8 = "Processing commits", │ │17 count: usize = 0, │ │18 total: usize = 0, │ │19 │ ╰────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │33 }, │ │34 else => return, │ │35 } │ │36 + if (self.total == 0) return; │ │37 var buffer: [128]u8 = undefined; │ │36 - const percent = if (self.total == 0) 100 else @as(u128, self.count) * 100 / self.total; │ │37 - const text = try std.fmt.bufPrint(&buffer, "Processing commits: {d}% ({d}/{d}){s}", .{ percent, self.count, self.total, if (event == .end) "\n" else "\r" });│ │38 + const percent = @as(u128, self.count) * 100 / self.total; │ │39 + const text = try std.fmt.bufPrint(&buffer, "{s}: {d}% ({d}/{d}){s}", .{ self.label, percent, self.count, self.total, if (event == .end) "\n" else "\r" }); │ │40 try self.response.progress(self.writer, text); │ │41 } │ │42 }; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │51 response: *xit.net_server_receive_pack.Response, │ │52 writer: *std.Io.Writer, │ │53 ) !void { │ │52 - try response.progress(writer, "Processing commits...\n"); │ │54 var config = try xit.config.Config(.xit, repo_opts).init(state.readOnly(), io, allocator);│ │55 defer config.deinit(); │ │56 try config.add(state, io, .{ .name = "merge.algorithm", .value = "patch" }); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │150 return err; │ │151 }; │ │152 │ │152 - pch.refreshBranches(.server, .xit, repo_opts, io, allocator, repo, updates.items.items) catch |err| { │ │153 + var progress = PushProgress{ .response = &response, .writer = writer, .label = "Updating patches" }; │ │154 + pch.refreshBranches(.server, .xit, repo_opts, io, allocator, repo, updates.items.items, &progress) catch |err| {│ │155 serve_common.logError(io, error_writer, "failed to refresh branch patches: {s}\n", .{@errorName(err)}); │ │156 + pch.refreshMergeability(repo_opts, io, allocator, repo, null); │ │157 }; │ │155 - response.progress(writer, "Checking merges...\n") catch {}; │ │156 - pch.refreshMergeability(repo_opts, io, allocator, repo, null); │ │158 try response.finish(writer, null); │ │159 } │ │160 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │323 return err; │ │324 }; │ │325 │ │325 - response.progress(writer, "Checking merges...\n") catch {}; │ │326 + var progress = PushProgress{ .response = &response, .writer = writer, .label = "Updating patches" };│ │327 + progress.run(io, .{ .start = .{ .kind = .writing_patch, .estimated_total_items = 1 } }) catch {}; │ │328 │ │329 // best-effort update the published patch so it has the new revision │ │330 + var refreshed = false; │ │331 update: { │ │332 const revision_id = revision_id_maybe orelse break :update; │ │333 if (!published) break :update; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │354 serve_common.logError(io, error_writer, "failed to update published patch {s}: {s}\n", .{ id, @errorName(update_err) });│ │355 break :update; │ │356 }; │ │354 - // consume refreshed the published patch before acknowledging the push │ │355 - return response.finish(writer, null); │ │357 + // consume also refreshed mergeability │ │358 + refreshed = true; │ │359 } │ │360 │ │358 - pch.refreshMergeability(repo_opts, io, allocator, target_repo, patch_id); │ │361 + if (!refreshed) pch.refreshMergeability(repo_opts, io, allocator, target_repo, patch_id); │ │362 + progress.run(io, .{ .complete_one = .writing_patch }) catch {}; │ │363 + progress.run(io, .{ .end = .writing_patch }) catch {}; │ │364 │ │365 // the client may read either repo as soon as it gets the final response. │ │366 try response.finish(writer, null); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯