╭ changed ──────────╮ │src/event/Patch.zig│ ╰───────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │153 if (tag.len > tag_max_len) return false; │ │154 } │ │155 return true; │ │156 +} │ │157 + │ │158 +pub fn branchValid(branch: []const u8) bool { │ │159 + return rf.validateName(branch) and !std.mem.eql(u8, branch, evt.events_ref.name);│ │160 } │ │161 │ │162 pub fn consume( │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │184 var record = record_maybe orelse try evt.removedRecord(Record, DB, hash_kind, haxy_moment.readOnly(), existing_maybe);│ │185 │ │186 if (!fieldsValid(record.event.title, record.event.tags)) return error.InvalidPatch; │ │183 - if (!rf.validateName(record.event.target_branch)) return error.InvalidTarget; │ │187 + if (!branchValid(record.event.target_branch)) return error.InvalidTargetBranch; │ │188 if (record.event.source_branch) |branch| { │ │185 - if (!rf.validateName(branch) or std.mem.eql(u8, branch, evt.events_ref.name) or │ │186 - std.mem.eql(u8, record.event.target_branch, evt.events_ref.name)) return error.InvalidSourceBranch; │ │189 + if (!branchValid(branch)) return error.InvalidSourceBranch; │ │190 + if (std.mem.eql(u8, branch, record.event.target_branch)) return error.SameBranch; │ │191 } │ │192 const revision_id = if (record.event.revision) |*revision| blk: { │ │193 try evt.PatchRev.validateOid(hash_kind, revision.squash_oid); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │349 }; │ │350 }, │ │351 .fields => |fields| { │ │348 - if (!rf.validateName(fields.target_branch)) return error.InvalidTargetBranch; │ │352 + if (!branchValid(fields.target_branch)) return error.InvalidTargetBranch; │ │353 if ((try repo.readRef(io, .{ .kind = .head, .name = fields.target_branch })) == null) return error.InvalidTargetBranch;│ │354 updated.title = fields.title; │ │355 updated.tags = fields.tags; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───╮ │src/fork.zig│ ╰────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │49 input: CreateInput, │ │50 ) ![]u8 { │ │51 if (!evt.Patch.fieldsValid(input.title, input.tags)) return error.InvalidPatch; │ │52 + if (!evt.Patch.branchValid(input.target_branch)) return error.InvalidTargetBranch;│ │53 │ │54 // get the fork id and path │ │55 const fork_id = try evt.parseEventId(&input.id); │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │73 defer allocator.free(target_path); │ │74 var target_repo = try rp.Repo(.xit, repo_opts).open(io, allocator, .{ .path = target_path, .require_repo_root = true });│ │75 defer target_repo.deinit(io, allocator); │ │75 - if (!rf.validateName(input.target_branch)) return error.InvalidTarget; │ │76 if ((try target_repo.readRef(io, .{ .kind = .head, .name = input.target_branch })) == null) return error.TargetNotFound;│ │77 │ │78 // create the fork repo dir │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────╮ │src/patch.zig│ ╰─────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 91 ) ![2]evt.EventWithId { │ │ 92 const branch = patch.source_branch orelse return error.InvalidSourceBranch; │ │ 93 if (!evt.Patch.fieldsValid(patch.title, patch.tags)) return error.InvalidFields; │ │ 94 - if (!rf.validateName(branch) or std.mem.eql(u8, branch, evt.events_ref.name)) return error.InvalidSourceBranch; │ │ 95 - if (!rf.validateName(patch.target_branch) or std.mem.eql(u8, patch.target_branch, evt.events_ref.name)) return error.InvalidTargetBranch; │ │ 94 + if (!evt.Patch.branchValid(branch)) return error.InvalidSourceBranch; │ │ 95 + if (!evt.Patch.branchValid(patch.target_branch)) return error.InvalidTargetBranch; │ │ 96 const source = (try rf.readRecur(repo_kind, repo_opts, state.readOnly(), io, .{ .ref = .{ .kind = .head, .name = branch } })) orelse return error.InvalidSourceBranch; │ │ 97 const target = (try rf.readRecur(repo_kind, repo_opts, state.readOnly(), io, .{ .ref = .{ .kind = .head, .name = patch.target_branch } })) orelse return error.InvalidTargetBranch;│ │ 98 + if (std.mem.eql(u8, branch, patch.target_branch)) return error.SameBranch; │ │ 99 const base = mrg.commonAncestor(repo_kind, repo_opts, state.readOnly(), io, arena.allocator(), &target, &source) catch |err| switch (err) { │ │100 error.NoCommonAncestor => return error.UnrelatedBranches, │ │101 else => return err, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │542 input: EditDraftInput, │ │543 ) !bool { │ │544 if (!evt.Patch.fieldsValid(input.title, input.tags)) return error.InvalidFields; │ │545 + if (!evt.Patch.branchValid(input.target_branch)) return error.InvalidTargetBranch;│ │546 const patch_id = try evt.parseEventId(&input.id); │ │547 var arena = std.heap.ArenaAllocator.init(allocator); │ │548 defer arena.deinit(); │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────────╮ │src/test/event/patch.zig│ ╰────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯