╭ changed ───────────────╮ │src/test/event/patch.zig│ ╰────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │41 try repo.add(io, allocator, &.{"base.txt"}); │ │42 const base = try repo.commit(io, allocator, .{ .message = "base" }); │ │43 try repo.addBranch(io, .{ .name = "feature" }); │ │44 - const source = try repo.commitAtRef(io, allocator, .{ .message = "feature" }, null, .{ .kind = .head, .name = "feature" }); │ │44 const id = [_]u8{7} ** evt.event_id_size; │ │45 const patch = evt.Patch{ .title = "branch patch", .tags = "feature", .description = "existing branch", .source_branch = "feature", .target_branch = "master" };│ │46 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │48 - // submission stores the patch and revision in this repo │ │47 + // the same branch cannot be both source and target │ │48 + { │ │49 + var invalid = patch; │ │50 + invalid.target_branch = "feature"; │ │51 + try std.testing.expectError(error.SameBranch, pch.writeBranchPatch(.local, kind, opts, io, allocator, &repo, std.fmt.bytesToHex(id, .lower), invalid, null, author));│ │52 + try std.testing.expectEqual(null, try repo.readRef(io, evt.events_ref)); │ │53 + } │ │54 + │ │55 + // distinct branches at the same commit can still create a patch │ │56 try pch.writeBranchPatch(.local, kind, opts, io, allocator, &repo, std.fmt.bytesToHex(id, .lower), patch, null, author); │ │57 const first = (try evt.Patch.readFromRepo(kind, opts, io, allocator, &arena, &repo, &id)) orelse return error.NotFound; │ │58 const first_revision = first.event.revision orelse return error.NotFound; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │52 - try std.testing.expectEqualStrings(&source, first_revision.source_oid); │ │59 + try std.testing.expectEqualStrings(&base, first_revision.source_oid); │ │60 try std.testing.expectEqualStrings(&base, &(try repo.readRef(io, .{ .kind = .head, .name = "master" }) orelse return error.NotFound));│ │61 │ │62 // an unchanged refresh does not add events │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │63 const event_tip = (try repo.readRef(io, evt.events_ref)) orelse return error.NotFound; │ │64 try pch.refreshBranches(.local, kind, opts, io, allocator, &repo, null); │ │65 + try std.testing.expectEqualStrings(&event_tip, &(try repo.readRef(io, evt.events_ref) orelse return error.NotFound)); │ │66 + │ │67 + // retargeting to the source leaves the patch and revision unchanged │ │68 + try std.testing.expectError(error.SameBranch, evt.Patch.update(.local, kind, opts, io, allocator, &repo, &id, .{ .fields = .{│ │69 + .title = patch.title, │ │70 + .tags = patch.tags, │ │71 + .description = patch.description, │ │72 + .target_branch = "feature", │ │73 + } }, author)); │ │74 try std.testing.expectEqualStrings(&event_tip, &(try repo.readRef(io, evt.events_ref) orelse return error.NotFound)); │ │75 │ │76 // a source update replaces the revision, not the patch metadata │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │323 .{ .tree = .{ .name = "head", .oid = &head_tree_oid } }, │ │324 }; │ │325 │ │326 + // reject invalid branches even when the event bypasses patch creation │ │327 + { │ │328 + const cases = [_]struct { source: ?[]const u8, target: []const u8, expected_error: anyerror }{ │ │329 + .{ .source = "master", .target = "master", .expected_error = error.SameBranch }, │ │330 + .{ .source = null, .target = evt.events_ref.name, .expected_error = error.InvalidTargetBranch }, │ │331 + }; │ │332 + for (cases) |case| { │ │333 + var invalid = patch; │ │334 + invalid.source_branch = case.source; │ │335 + invalid.target_branch = case.target; │ │336 + try std.testing.expectError(case.expected_error, evt.consume(.local, .repo, .xit, repo_opts, io, allocator, &target, evt.events_ref, &.{.{│ │337 + .id = std.fmt.bytesToHex(patch_id, .lower), │ │338 + .timestamp = 3, │ │339 + .author = author, │ │340 + .event = .{ .patch = invalid }, │ │341 + }})); │ │342 + try std.testing.expectEqual(null, try target.readRef(io, evt.events_ref)); │ │343 + } │ │344 + } │ │345 + │ │346 // │ │347 // consume the patch revision and patch │ │348 // │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯