diff options
| -rw-r--r-- | src/diff.zig | 32 |
1 files changed, 30 insertions, 2 deletions
diff --git a/src/diff.zig b/src/diff.zig index 9ab1bb23..4962cddc 100644 --- a/src/diff.zig +++ b/src/diff.zig @@ -299,6 +299,19 @@ pub const Target = struct { /// from the end of the file. In a section that deletes its file there is /// no new file, so a removed or context line opens the old one at its old /// line (it is there while the diff is not applied). +/// Whether the section `row` is in deletes its file by a line still ahead +/// of it (`deleted file mode`, `+++ /dev/null`): its `diff --git` and `---` +/// lines come first, and are about a file this diff deletes all the same. +fn deletesAhead(lines: []const []const u8, row: usize) bool { + for (lines[row + 1 ..]) |raw| { + const l = std.mem.trimEnd(u8, raw, "\r"); + if (std.mem.startsWith(u8, l, "diff ") or std.mem.startsWith(u8, l, "@@")) return false; + if (std.mem.startsWith(u8, l, "deleted file mode") or std.mem.eql(u8, l, "+++ /dev/null") or + (std.mem.startsWith(u8, l, "+++ /dev/null") and l.len > 13 and (l[13] == '\t' or l[13] == ' '))) return true; + } + return false; +} + pub fn targetAt(lines: []const []const u8, row: usize, continued_rows: bool) ?Target { if (row >= lines.len) return null; var w: Walk = .{ .continued_rows = continued_rows }; @@ -310,7 +323,7 @@ pub fn targetAt(lines: []const []const u8, row: usize, continued_rows: bool) ?Ta .other, .meta, .no_newline => { // `diff --git a/x b/y`: the file, as its `+++` would. if (got.kind == .meta and std.mem.startsWith(u8, line, "diff ") and w.git_path.len > 0) - return .{ .kind = got.kind, .path = w.git_path, .line = 0 }; + return .{ .kind = got.kind, .path = w.git_path, .line = 0, .old_side = deletesAhead(lines, row) }; return null; }, .old_path, .new_path => { @@ -324,7 +337,7 @@ pub fn targetAt(lines: []const []const u8, row: usize, continued_rows: bool) ?Ta if (!isDevNull(new) and (isDevNull(own) or !std.mem.eql(u8, stripSide(new), stripSide(own)))) return .{ .kind = got.kind, .path = new, .line = 0 }; } - if (!isDevNull(own)) return .{ .kind = got.kind, .path = own, .line = 0, .old_side = got.kind == .old_path and w.deleted() }; + if (!isDevNull(own)) return .{ .kind = got.kind, .path = own, .line = 0, .old_side = got.kind == .old_path and (w.deleted() or deletesAhead(lines, row)) }; if (w.path().len == 0) return null; return .{ .kind = got.kind, .path = w.path(), .line = 0, .old_side = w.deleted() }; }, @@ -389,6 +402,21 @@ const git_diff = \\ ; +test "diff every line of a section that deletes its file is about a file this diff deletes" { + const lines = splitLines(git_diff); + var rows: usize = 0; + for (lines, 0..) |line, row| { + if (row < 15) continue; // the deleting section: `diff --git a/old.py` on + const t = targetAt(&lines, row, false) orelse continue; + try testing.expectEqualStrings("old.py", stripSide(t.path)); + try testing.expect(t.old_side); + _ = line; + rows += 1; + } + // diff --git, ---, +++, @@ and the two removed lines. + try testing.expectEqual(@as(usize, 6), rows); +} + test "diff a walk follows the hunk counts, so a removed `--` line is no header" { const lines = splitLines(git_diff); var w: Walk = .{}; |
