╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ event.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
861 try std.testing.expect(.success == merge.result); 862 } 863 864 try evt.consume(.local, .repo, .xit, repo_opts, io, allocator, &repo, evt.events_ref, &.{}); 865 866 const haxy_moment = try evt.currentMoment(repo_opts, &repo); 867 868 const conflicts_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Issue.conflicts_key)) orelse return error.NotFound; 869 const conflicts = try Repo.DB.SortedMap(.read_only).init(conflicts_cursor); 870 try std.testing.expectEqual(0, try conflicts.count()); 871 872 // and the resolution stands 873 const issue = try readIssue(Repo.DB, repo_opts.hash, haxy_moment, &arena, &issue_id); 874 try std.testing.expectEqualStrings("bug priority-medium ui", issue.event.labels); 875 } 876 877 // 878 // diverge again, with each side editing different fields of one issue and 879 // only the other side touching a second issue 880 // 881 882 const second_issue = events_to_consume[1].event.issue orelse return error.NotFound; 883 const third_issue = events_to_consume[2].event.issue orelse return error.NotFound; 884 885 var retitle_oid: [hash.byteLen(repo_opts.hash)]u8 = undefined; 886 var rewrite_oid: [hash.byteLen(repo_opts.hash)]u8 = undefined; 887 888 { 889 const base_oid = try repo.readRef(io, evt.events_ref) orelse return error.NotFound; 890 891 var json: std.Io.Writer.Allocating = .init(allocator); 892 defer json.deinit(); 893 894 // the events branch only retitles the second issue 895 { 896 const event = evt.EventWithId{ .id = events_to_consume[1].id, .author = author, .event = .{ .issue = .{ 897 .title = "Search results ignore the archived project filter", 898 .description = second_issue.description, 899 .labels = second_issue.labels, 900 } } }; 901 try std.json.Stringify.value(event, .{}, &json.writer); 902 const oid = try repo.commitAtRef(io, allocator, .{ .message = json.written() }, null, evt.events_ref); 903 _ = try std.fmt.hexToBytes(&retitle_oid, &oid); 904 } 905 906 // the other branch rewrites the second issue's description and drops one 907 // of its labels, then relabels the third issue 908 { 909 json.clearRetainingCapacity(); 910 const event = evt.EventWithId{ .id = events_to_consume[1].id, .author = author, .event = .{ .issue = .{ 911 .title = second_issue.title, 912 .description = "Archived projects are ranked before the flag is applied.", 913 .labels = "bug search", 914 } } }; 915 try std.json.Stringify.value(event, .{}, &json.writer); 916 const oid = try repo.commitAtRef(io, allocator, .{ .parent_oids = &.{base_oid}, .message = json.written() }, null, other_events_ref); 917 _ = try std.fmt.hexToBytes(&rewrite_oid, &oid); 918 } 919 { 920 json.clearRetainingCapacity(); 921 const event = evt.EventWithId{ .id = events_to_consume[2].id, .author = author, .event = .{ .issue = .{ 922 .title = third_issue.title, 923 .description = third_issue.description, 924 .labels = "enhancement roadmap", 925 } } }; 926 try std.json.Stringify.value(event, .{}, &json.writer); 927 _ = try repo.commitAtRef(io, allocator, .{ .message = json.written() }, null, other_events_ref); 928 } 929 } 930 931 { 932 var result = try repo.switchDir(io, allocator, .{ .target = .{ .ref = evt.events_ref } }); 933 defer result.deinit(); 934 935 var merge = try repo.merge(io, allocator, .{ .kind = .full, .action = .{ .new = .{ .source = &.{.{ .ref = other_events_ref }} } } }, null); 936 defer merge.deinit(); 937 938 try std.testing.expect(.success == merge.result); 939 } 940 941 { 942 try evt.consume(.local, .repo, .xit, repo_opts, io, allocator, &repo, evt.events_ref, &.{}); 943 944 const haxy_moment = try evt.currentMoment(repo_opts, &repo); 945 946 var second_id: [evt.event_id_size]u8 = undefined; 947 _ = try std.fmt.hexToBytes(&second_id, &events_to_consume[1].id); 948 949 // each side's edit to a different field survives, without conflicting 950 const merged = try readIssue(Repo.DB, repo_opts.hash, haxy_moment, &arena, &second_id); 951 try std.testing.expectEqualStrings("Search results ignore the archived project filter", merged.event.title); 952 try std.testing.expectEqualStrings("Archived projects are ranked before the flag is applied.", merged.event.description); 953 try std.testing.expectEqualStrings("bug search", merged.event.labels); 954 955 // each merged field is attributed to the branch it came from 956 try std.testing.expectEqualSlices(u8, &retitle_oid, &try readOid(Repo.DB, repo_opts.hash, haxy_moment, &second_id, "title")); 957 try std.testing.expectEqualSlices(u8, &rewrite_oid, &try readOid(Repo.DB, repo_opts.hash, haxy_moment, &second_id, "description")); 958 959 const conflicts_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Issue.conflicts_key)) orelse return error.NotFound; 960 const conflicts = try Repo.DB.SortedMap(.read_only).init(conflicts_cursor); 961 try std.testing.expectEqual(0, try conflicts.count()); 962 963 // labels the merged-in side dropped don't survive in the label index, 964 // whether we edited that issue or left it alone 965 const label_to_issues_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "label+status->issue-id-set")) orelse return error.NotFound; 966 const label_to_issues = try Repo.DB.SortedMap(.read_only).init(label_to_issues_cursor); 967 968 var third_id: [evt.event_id_size]u8 = undefined; 969 _ = try std.fmt.hexToBytes(&third_id, &events_to_consume[2].id); 970 const relabeled = try readIssue(Repo.DB, repo_opts.hash, haxy_moment, &arena, &third_id); 971 try std.testing.expectEqualStrings("enhancement roadmap", relabeled.event.labels); 972 973 try std.testing.expect(null == try label_to_issues.getCursor("backend open")); 974 try std.testing.expect(null == try label_to_issues.getCursor("preferences open")); 975 try std.testing.expect(null != try label_to_issues.getCursor("roadmap open")); 976 try std.testing.expect(null != try label_to_issues.getCursor("enhancement open")); 977 } 978 979 // 980 // a removal on the other branch carries over, unless we edited the issue, 981 // in which case the edit wins 982 // 983 984 { 985 const edited_issue = events_to_consume2[1].event.issue orelse return error.NotFound; 986 987 const base_oid = try repo.readRef(io, evt.events_ref) orelse return error.NotFound; 988 989 var json: std.Io.Writer.Allocating = .init(allocator); 990 defer json.deinit(); 991 992 // we retitle one of the two the other branch is about to drop 993 { 994 const event = evt.EventWithId{ .id = events_to_consume2[1].id, .author = author, .event = .{ .issue = .{ 995 .title = "Kept by the edit", 996 .description = edited_issue.description, 997 .labels = edited_issue.labels, 998 } } }; 999 try std.json.Stringify.value(event, .{}, &json.writer); 1000 _ = try repo.commitAtRef(io, allocator, .{ .message = json.written() }, null, evt.events_ref); 1001 } 1002 1003 // the other branch removes both 1004 { 1005 json.clearRetainingCapacity(); 1006 const event = evt.EventWithId{ .id = events_to_consume2[0].id, .author = author, .event = .{ .issue = null } }; 1007 try std.json.Stringify.value(event, .{}, &json.writer); 1008 _ = try repo.commitAtRef(io, allocator, .{ .parent_oids = &.{base_oid}, .message = json.written() }, null, other_events_ref); 1009 } 1010 { 1011 json.clearRetainingCapacity(); 1012 const event = evt.EventWithId{ .id = events_to_consume2[1].id, .author = author, .event = .{ .issue = null } }; 1013 try std.json.Stringify.value(event, .{}, &json.writer); 1014 _ = try repo.commitAtRef(io, allocator, .{ .message = json.written() }, null, other_events_ref); 1015 } 1016 1017 { 1018 var result = try repo.switchDir(io, allocator, .{ .target = .{ .ref = evt.events_ref } }); 1019 defer result.deinit(); 1020 1021 var merge = try repo.merge(io, allocator, .{ .kind = .full, .action = .{ .new = .{ .source = &.{.{ .ref = other_events_ref }} } } }, null); 1022 defer merge.deinit(); 1023 1024 try std.testing.expect(.success == merge.result); 1025 } 1026 1027 try evt.consume(.local, .repo, .xit, repo_opts, io, allocator, &repo, evt.events_ref, &.{}); 1028 1029 const haxy_moment = try evt.currentMoment(repo_opts, &repo); 1030 1031 // the one we left alone is removed, and its labels left the index 1032 var dropped_id: [evt.event_id_size]u8 = undefined; 1033 _ = try std.fmt.hexToBytes(&dropped_id, &events_to_consume2[0].id); 1034 const dropped = try readIssue(Repo.DB, repo_opts.hash, haxy_moment, &arena, &dropped_id); 1035 try std.testing.expect(dropped.removed); 1036 1037 const label_to_issues_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "label+status->issue-id-set")) orelse return error.NotFound; 1038 const label_to_issues = try Repo.DB.SortedMap(.read_only).init(label_to_issues_cursor); 1039 try std.testing.expect(null == try label_to_issues.getCursor("kanban open")); 1040 1041 // the one we edited survives, with our edit 1042 var kept_id: [evt.event_id_size]u8 = undefined; 1043 _ = try std.fmt.hexToBytes(&kept_id, &events_to_consume2[1].id); 1044 const kept = try readIssue(Repo.DB, repo_opts.hash, haxy_moment, &arena, &kept_id); 1045 try std.testing.expect(!kept.removed); 1046 try std.testing.expectEqualStrings("Kept by the edit", kept.event.title); 1047 } 1048 } 1049 1050 // the commit that last set `field_name` on the issue `id` names 1051 fn readOid( 1052 comptime DB: type, 1053 comptime hash_kind: hash.HashKind, 1054 haxy_moment: DB.HashMap(.read_only), 1055 id: *const [evt.event_id_size]u8, 1056 comptime field_name: []const u8, 1057 ) ![hash.byteLen(hash_kind)]u8 { 1058 const id_to_field_to_oid_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, evt.Issue.id_to_field_to_oid_key)) orelse return error.NotFound; 1059 const id_to_field_to_oid = try DB.HashMap(.read_only).init(id_to_field_to_oid_cursor); 1060 const field_oids_cursor = try id_to_field_to_oid.getCursor(hash.hashInt(hash_kind, id)) orelse return error.NotFound; 1061 const field_oids = try DB.SortedMap(.read_only).init(field_oids_cursor); 1062 const cursor = try field_oids.getCursor(field_name) orelse return error.NotFound; 1063 var oid: [hash.byteLen(hash_kind)]u8 = undefined; 1064 _ = try cursor.readBytes(&oid); 1065 return oid; 1066 } 1067 1068 // the issue `id` names, read out of `event-id->issue` 1069 fn readIssue( 1070 comptime DB: type, 1071 comptime hash_kind: hash.HashKind, 1072 haxy_moment: DB.HashMap(.read_only), 1073 arena: *std.heap.ArenaAllocator, 1074 id: *const [evt.event_id_size]u8, 1075 ) !evt.Issue.Record { 1076 const event_id_to_issue_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, "event-id->issue")) orelse return error.NotFound; 1077 const event_id_to_issue = try DB.HashMap(.read_only).init(event_id_to_issue_cursor); 1078 const issue_cursor = try event_id_to_issue.getCursor(hash.hashInt(hash_kind, id)) orelse return error.NotFound; 1079 const issue_map = try DB.HashMap(.read_only).init(issue_cursor); 1080 return try evt.read(evt.Issue.Record, DB, hash_kind, arena, issue_map); 1081 } 1082