╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭───────────────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
40 var ours_iter = try LineIterator.initFromTestBuffer(gpa, ours); 41 defer ours_iter.deinit(); 42 var theirs_iter = try LineIterator.initFromTestBuffer(gpa, theirs); 43 defer theirs_iter.deinit(); 44 45 // invalid utf-8 degrades an iterator to .binary with no lines, which would 46 // read as an empty side; treat the whole text as one conflict instead 47 if (base_iter.source == .binary or ours_iter.source == .binary or theirs_iter.source == .binary) { 48 const list = try aa.alloc(Chunk, 1); 49 list[0] = .{ .conflict = .{ 50 .ours = if (ours.len > 0) try aa.dupe(u8, ours) else null, 51 .theirs = if (theirs.len > 0) try aa.dupe(u8, theirs) else null, 52 } }; 53 return list; 54 } 55 56 var diff3_iter = try df.Diff3Iterator(repo_kind, repo_opts).init(gpa, &base_iter, &ours_iter, &theirs_iter); 57 defer diff3_iter.deinit(); 58 59 var list: std.ArrayList(Chunk) = .empty; 60 while (try diff3_iter.next()) |chunk| { 61 switch (chunk) { 62 .clean => |range| try list.append(aa, .{ .same = (try joinRange(aa, &base_iter, range)) orelse unreachable }), 63 .conflict => |ranges| { 64 // the iterator marks every non-identical chunk a conflict, so 65 // one-sided changes are classified here, like xit's merge does 66 const base_text = try joinRange(aa, &base_iter, ranges.o_range); 67 const ours_text = try joinRange(aa, &ours_iter, ranges.a_range); 68 const theirs_text = try joinRange(aa, &theirs_iter, ranges.b_range); 69 if (textEqual(ours_text, theirs_text)) { 70 // the same change on both sides; nothing left = no chunk 71 if (ours_text) |text| try list.append(aa, .{ .same = text }); 72 } else if (textEqual(base_text, ours_text)) { 73 try list.append(aa, .{ .auto = .{ .text = theirs_text, .theirs = true } }); 74 } else if (textEqual(base_text, theirs_text)) { 75 try list.append(aa, .{ .auto = .{ .text = ours_text, .theirs = false } }); 76 } else { 77 try list.append(aa, .{ .conflict = .{ .ours = ours_text, .theirs = theirs_text } }); 78 } 79 }, 80 } 81 } 82 return try list.toOwnedSlice(aa); 83 } 84 85 // join the chunks back into one document. `resolutions` holds the final text 86 // of each conflict chunk in order; an empty resolution drops the hunk's lines. 87 pub fn assemble( 88 allocator: std.mem.Allocator, 89 chunk_list: []const Chunk, 90 resolutions: []const []const u8, 91 ) ![]u8 { 92 var out: std.ArrayList(u8) = .empty; 93 errdefer out.deinit(allocator); 94 var resolution_index: usize = 0; 95 for (chunk_list) |chunk| { 96 const piece: ?[]const u8 = switch (chunk) { 97 .same => |text| text, 98 .auto => |auto| auto.text, 99 .conflict => blk: { 100 const resolution = resolutions[resolution_index]; 101 resolution_index += 1; 102 break :blk if (resolution.len > 0) resolution else null; 103 }, 104 }; 105 if (piece) |text| { 106 if (out.items.len > 0) try out.append(allocator, '\n'); 107 try out.appendSlice(allocator, text); 108 } 109 } 110 return try out.toOwnedSlice(allocator); 111 } 112 113 // a range's lines joined with newlines, or null when it spans no lines 114 fn joinRange( 115 aa: std.mem.Allocator, 116 iter: *LineIterator, 117 range_maybe: ?df.Diff3Iterator(repo_kind, repo_opts).Range, 118 ) !?[]const u8 { 119 const range = range_maybe orelse return null; 120 if (range.end <= range.begin) return null; 121 var out: std.ArrayList(u8) = .empty; 122 for (range.begin..range.end) |line_num| { 123 // buffer-backed lines are borrowed, so no free is needed 124 const line = try iter.get(line_num); 125 if (line_num > range.begin) try out.append(aa, '\n'); 126 try out.appendSlice(aa, line); 127 } 128 return try out.toOwnedSlice(aa); 129 } 130 131 fn textEqual(a: ?[]const u8, b: ?[]const u8) bool { 132 const a_text = a orelse return b == null; 133 const b_text = b orelse return false; 134 return std.mem.eql(u8, a_text, b_text); 135 } 136