From cfe0eceff38291202878ed7a9545455d2258c9c1 Mon Sep 17 00:00:00 2001 From: b84330808 Date: Thu, 22 Oct 2026 12:48:56 +0900 Subject: [PATCH] Show the "No newline at end of file" marker as a plain diff line libgit2 reports the "\ No newline at end of file" marker as AddEOFNL when the newline is being added and as DeleteEOFNL when it is being removed. The marker itself follows the line of the other side, though: AddEOFNL comes right after the deleted line and DeleteEOFNL right after the added one. Mapping them to Add/Delete therefore coloured the marker green below a removed line and red below an added line. The marker is not a change of its own, so map it to DiffLineType::None and render it like git does, as a plain line. Fixes #2223 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + asyncgit/src/sync/diff.rs | 73 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1ca0eaac2..5f89b7dcf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * crash when opening submodule ([#2895](https://github.com/gitui-org/gitui/issues/2895)) * when staging the last file in a directory, the first item after the directory is no longer skipped [[@Tillerino](https://github.com/Tillerino)] ([#2748](https://github.com/gitui-org/gitui/issues/2748)) * index-out-of-bounds panic when unstaging lines near the end of a diff ([#2953](https://github.com/gitui-org/gitui/issues/2953)) +* show the "\ No newline at end of file" marker as a plain line in diffs instead of colouring it as an addition/deletion of the wrong side [[@b84330808](https://github.com/b84330808)] ([#2223](https://github.com/gitui-org/gitui/issues/2223)) ## [0.28.1] - 2026-03-21 diff --git a/asyncgit/src/sync/diff.rs b/asyncgit/src/sync/diff.rs index c13fc476c7..2a6d946a26 100644 --- a/asyncgit/src/sync/diff.rs +++ b/asyncgit/src/sync/diff.rs @@ -39,10 +39,13 @@ impl From for DiffLineType { fn from(line_type: git2::DiffLineType) -> Self { match line_type { git2::DiffLineType::HunkHeader => Self::Header, - git2::DiffLineType::DeleteEOFNL - | git2::DiffLineType::Deletion => Self::Delete, - git2::DiffLineType::AddEOFNL - | git2::DiffLineType::Addition => Self::Add, + git2::DiffLineType::Deletion => Self::Delete, + git2::DiffLineType::Addition => Self::Add, + // `AddEOFNL`/`DeleteEOFNL` are the "\ No newline at end + // of file" marker. It is not a change of its own and it + // follows the line of the *other* side (e.g. `AddEOFNL` + // comes after the deleted line), so show it like git + // does: as a plain line. _ => Self::None, } } @@ -415,7 +418,7 @@ fn new_file_content(path: &Path) -> Option> { #[cfg(test)] mod tests { - use super::{get_diff, get_diff_commit}; + use super::{get_diff, get_diff_commit, DiffLineType}; use crate::{ error::Result, sync::{ @@ -666,4 +669,64 @@ mod tests { Ok(()) } + + fn no_newline_marker_line_type( + old: &[u8], + new: &[u8], + ) -> Result { + let file_path = Path::new("bar.txt"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path: &RepoPath = + &root.as_os_str().to_str().unwrap().into(); + + File::create(root.join(file_path))?.write_all(old)?; + stage_add_file(repo_path, file_path).unwrap(); + commit(repo_path, "commit").unwrap(); + + File::create(root.join(file_path))?.write_all(new)?; + + let diff = get_diff( + repo_path, + file_path.to_str().unwrap(), + false, + None, + ) + .unwrap(); + + let markers = diff + .hunks + .iter() + .flat_map(|hunk| hunk.lines.iter()) + .filter(|line| line.content.starts_with('\\')) + .collect::>(); + dbg!(&diff); + assert_eq!(markers.len(), 1); + + Ok(markers[0].line_type) + } + + #[test] + fn test_no_newline_marker_when_adding_newline() -> Result<()> { + // the marker belongs to the old side here, so it must not be + // shown as an added line + assert_eq!( + no_newline_marker_line_type(b"foo", b"foo\n")?, + DiffLineType::None + ); + + Ok(()) + } + + #[test] + fn test_no_newline_marker_when_removing_newline() -> Result<()> { + // the marker belongs to the new side here, so it must not be + // shown as a deleted line + assert_eq!( + no_newline_marker_line_type(b"foo\n", b"foo")?, + DiffLineType::None + ); + + Ok(()) + } }