╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ push.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
147 }; 148 149 response.progress(writer, "Checking merges...\n") catch {}; 150 pch.refreshMergeability(repo_opts, io, allocator, repo, null); 151 try response.finish(writer, null); 152 } 153 154 pub fn receiveFork( 155 comptime repo_opts: rp.RepoOpts(.xit), 156 io: std.Io, 157 allocator: std.mem.Allocator, 158 fork_repo: *rp.Repo(.xit, repo_opts), 159 target_repo: *rp.Repo(.xit, repo_opts), 160 id: *const [evt.event_id_size * 2]u8, 161 author: evt.CommitAuthor, 162 timestamp: u64, 163 reader: *std.Io.Reader, 164 writer: *std.Io.Writer, 165 error_writer: *std.Io.Writer, 166 ) !void { 167 const patch_id = try evt.parseEventId(id); 168 var arena = std.heap.ArenaAllocator.init(allocator); 169 defer arena.deinit(); 170 171 // load the fork and target patch state 172 const fork_moment = evt.currentMoment(repo_opts, fork_repo) catch return error.PatchDataUnavailable; 173 const fork_patch = (try evt.Patch.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_moment, &arena, &patch_id)) orelse return error.PatchDataUnavailable; 174 const target_moment = evt.currentMoment(repo_opts, target_repo) catch |err| switch (err) { 175 error.NotFound => null, 176 else => |other| return other, 177 }; 178 const target_patch = if (target_moment) |moment| 179 try evt.Patch.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, moment, &arena, &patch_id) 180 else 181 null; 182 if (fork_patch.removed and target_patch == null) return error.PatchDataUnavailable; 183 if (target_patch) |patch| { 184 if (!std.mem.eql(u8, patch.author_email orelse "", author.email)) return error.PatchDataUnavailable; 185 if (patch.event.status.kind() == .merged) return error.PatchAlreadyMerged; 186 } 187 const published = target_patch != null; 188 const active_patch = target_patch orelse fork_patch; 189 const title = active_patch.event.title; 190 191 // resolve the target and the newest fork revision 192 const target_branch = active_patch.event.target_branch; 193 const target_oid = (try target_repo.readRef(io, .{ .kind = .head, .name = target_branch })) orelse return error.TargetNotFound; 194 const newest = try evt.PatchRev.readNewest(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_moment, &arena); 195 var revision_id_maybe: ?[evt.event_id_size]u8 = null; 196 var response = xit.net_server_receive_pack.Response.init(allocator); 197 defer response.deinit(); 198 199 // execute a transaction that receives the push and materializes its revision 200 const result = blk: { 201 const DB = rp.Repo(.xit, repo_opts).DB; 202 const State = rp.Repo(.xit, repo_opts).State; 203 const Ctx = struct { 204 core: *rp.Repo(.xit, repo_opts).Core, 205 target_core: *rp.Repo(.xit, repo_opts).Core, 206 io: std.Io, 207 allocator: std.mem.Allocator, 208 reader: *std.Io.Reader, 209 writer: *std.Io.Writer, 210 response: *xit.net_server_receive_pack.Response, 211 patch_id: [evt.event_id_size * 2]u8, 212 patch: evt.Patch.Record, 213 published: bool, 214 target_oid: [hash.hexLen(repo_opts.hash)]u8, 215 title: []const u8, 216 author: evt.CommitAuthor, 217 timestamp: u64, 218 newest: ?evt.PatchRev.WithId, 219 revision_id_maybe: *?[evt.event_id_size]u8, 220 221 pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void { 222 // receive the branch update 223 var moment = try DB.HashMap(.read_write).init(cursor.*); 224 const state = State(.read_write){ .core = ctx.core, .extra = .{ .moment = &moment } }; 225 try xit.net_server_receive_pack.run(.xit, repo_opts, state, ctx.io, ctx.allocator, ctx.reader, ctx.writer, .{ .allowed_ref = "refs/heads/" ++ fork.ref.name, .deferred_response = ctx.response }); 226 try writeReceivedPatches(repo_opts, state, ctx.io, ctx.allocator, ctx.response, ctx.writer); 227 228 // copy the target history needed to preserve the merge base 229 const source_oid = (try rf.readRecur(.xit, repo_opts, state.readOnly(), ctx.io, .{ .ref = fork.ref })) orelse return error.CancelTransaction; 230 var target_repo_moment = try ctx.target_core.latestMoment(); 231 const target_state = State(.read_only){ .core = ctx.target_core, .extra = .{ .moment = &target_repo_moment } }; 232 var objects = try obj.ObjectIterator(.xit, repo_opts).init(target_state, ctx.io, ctx.allocator, .{ .kind = .all }); 233 defer objects.deinit(); 234 try objects.include(&ctx.target_oid); 235 try obj.copyFromObjectIterator(.xit, repo_opts, state, .xit, repo_opts, &objects, ctx.io, null); 236 237 const base_oid = try mrg.commonAncestor(.xit, repo_opts, state.readOnly(), ctx.io, ctx.allocator, &ctx.target_oid, &source_oid); 238 const existing_revision = if (ctx.newest) |latest| 239 if (std.mem.eql(u8, latest.record.event.base_oid, &base_oid) and 240 std.mem.eql(u8, latest.record.event.source_oid, &source_oid)) latest else null 241 else 242 null; 243 244 // reuse an identical revision or record a new one 245 var events: [2]evt.EventWithId = undefined; 246 var event_count: usize = 0; 247 var base_tree_oid: [hash.hexLen(repo_opts.hash)]u8 = undefined; 248 var head_tree_oid: [hash.hexLen(repo_opts.hash)]u8 = undefined; 249 var tree_entries: [2]evt.EventTreeEntry = undefined; 250 if (existing_revision) |latest| { 251 if (ctx.published) ctx.revision_id_maybe.* = latest.id; 252 } else { 253 var base_object = try obj.Object(.xit, repo_opts).initCommit(state.readOnly(), ctx.io, ctx.allocator, &base_oid); 254 defer base_object.deinit(); 255 var source_object = try obj.Object(.xit, repo_opts).initCommit(state.readOnly(), ctx.io, ctx.allocator, &source_oid); 256 defer source_object.deinit(); 257 base_tree_oid = base_object.content.commit.tree; 258 head_tree_oid = source_object.content.commit.tree; 259 260 var revision_id: [evt.event_id_size]u8 = undefined; 261 ctx.io.random(&revision_id); 262 const revision_hex = std.fmt.bytesToHex(revision_id, .lower); 263 const revision_event: evt.PatchRev = .{ 264 .base_oid = &base_oid, 265 .source_oid = &source_oid, 266 .message = ctx.title, 267 }; 268 const identity = try std.fmt.allocPrint(ctx.allocator, "{s} <{s}>", .{ ctx.author.name, ctx.author.email }); 269 defer ctx.allocator.free(identity); 270 const patch_oid = try evt.PatchRev.writeSquashCommit( 271 .xit, 272 repo_opts, 273 state, 274 ctx.io, 275 ctx.allocator, 276 revision_event, 277 &head_tree_oid, 278 identity, 279 identity, 280 ctx.timestamp, 281 ); 282 tree_entries = .{ 283 .{ .tree = .{ .name = "base", .oid = &base_tree_oid } }, 284 .{ .tree = .{ .name = "head", .oid = &head_tree_oid } }, 285 }; 286 events[event_count] = .{ 287 .id = revision_hex, 288 .timestamp = ctx.timestamp, 289 .author = ctx.author, 290 .tree_entries = &tree_entries, 291 .event = .{ .patchrev = revision_event }, 292 }; 293 event_count += 1; 294 ctx.revision_id_maybe.* = revision_id; 295 296 if (!ctx.published) { 297 var patch = ctx.patch.event; 298 patch.revision = .{ 299 .id = revision_hex, 300 .squash_oid = &patch_oid, 301 .source_oid = &source_oid, 302 }; 303 events[event_count] = .{ 304 .id = ctx.patch_id, 305 .timestamp = ctx.timestamp, 306 .author = ctx.author, 307 .event = .{ .patch = patch }, 308 }; 309 event_count += 1; 310 } 311 } 312 313 // published patches keep only revisions in the fork 314 if (ctx.published and !ctx.patch.removed) { 315 events[event_count] = .{ 316 .id = ctx.patch_id, 317 .timestamp = ctx.timestamp, 318 .author = ctx.author, 319 .event = .{ .patch = null }, 320 }; 321 event_count += 1; 322 } 323 324 // commit and index any new events 325 if (event_count > 0) { 326 try evt.commitEvents(.xit, repo_opts, state, ctx.io, ctx.allocator, evt.events_ref, events[0..event_count], null); 327 if (!try evt.consumeInTransaction(.fork, .xit, repo_opts, state, &ctx.core.db, &moment, ctx.io, ctx.allocator, evt.events_ref)) return error.CancelTransaction; 328 } 329 try xit.undo.writeMessage(repo_opts, state, .push); 330 } 331 }; 332 333 try fork_repo.core.db_file.lock(io, .exclusive); 334 defer fork_repo.core.db_file.unlock(io); 335 336 const history = try DB.ArrayList(.read_write).init(fork_repo.core.db.rootCursor()); 337 break :blk history.appendContext(.{ .slot = try history.getSlot(-1) }, Ctx{ 338 .core = &fork_repo.core, 339 .target_core = &target_repo.core, 340 .io = io, 341 .allocator = allocator, 342 .reader = reader, 343 .writer = writer, 344 .response = &response, 345 .patch_id = id.*, 346 .patch = fork_patch, 347 .published = published, 348 .target_oid = target_oid, 349 .title = title, 350 .author = author, 351 .timestamp = timestamp, 352 .newest = newest, 353 .revision_id_maybe = &revision_id_maybe, 354 }); 355 }; 356 result catch |err| { 357 response.finish(writer, @errorName(err)) catch {}; 358 if (err == error.CancelTransaction) return; 359 return err; 360 }; 361 362 response.progress(writer, "Checking merges...\n") catch {}; 363 364 // best-effort update the published patch so it has the new revision 365 update: { 366 const revision_id = revision_id_maybe orelse break :update; 367 if (!published) break :update; 368 var update_arena = std.heap.ArenaAllocator.init(allocator); 369 defer update_arena.deinit(); 370 const target_update_moment = evt.currentMoment(repo_opts, target_repo) catch break :update; 371 const published_patch = (evt.Patch.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, target_update_moment, &update_arena, &patch_id) catch break :update) orelse break :update; 372 if (published_patch.removed) break :update; 373 const fork_update_moment = evt.currentMoment(repo_opts, fork_repo) catch break :update; 374 const revision = (evt.PatchRev.readById(evt.EventDB(repo_opts.hash), repo_opts.hash, fork_update_moment, &update_arena, &revision_id) catch break :update) orelse break :update; 375 if (revision.removed) break :update; 376 const selected = evt.Patch.Revision.fromRecord(revision_id, revision); 377 if (published_patch.event.revision) |current| { 378 if (evt.fieldEqual(evt.Patch.Revision, current, selected)) break :update; 379 } 380 var patch = published_patch.event; 381 patch.revision = selected; 382 evt.consume(.server, .repo, .xit, repo_opts, io, allocator, target_repo, evt.events_ref, &.{.{ 383 .id = id.*, 384 .timestamp = timestamp, 385 .author = author, 386 .event = .{ .patch = patch }, 387 }}) catch |update_err| { 388 serve_common.logError(io, error_writer, "failed to update published patch {s}: {s}\n", .{ id, @errorName(update_err) }); 389 break :update; 390 }; 391 // consume refreshed the published patch before acknowledging the push 392 return response.finish(writer, null); 393 } 394 395 pch.refreshMergeability(repo_opts, io, allocator, target_repo, patch_id); 396 397 // the client may read either repo as soon as it gets the final response. 398 try response.finish(writer, null); 399 } 400