Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
73 changes: 68 additions & 5 deletions asyncgit/src/sync/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,13 @@ impl From<git2::DiffLineType> 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,
}
}
Expand Down Expand Up @@ -415,7 +418,7 @@ fn new_file_content(path: &Path) -> Option<Vec<u8>> {

#[cfg(test)]
mod tests {
use super::{get_diff, get_diff_commit};
use super::{get_diff, get_diff_commit, DiffLineType};
use crate::{
error::Result,
sync::{
Expand Down Expand Up @@ -666,4 +669,64 @@ mod tests {

Ok(())
}

fn no_newline_marker_line_type(
old: &[u8],
new: &[u8],
) -> Result<DiffLineType> {
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::<Vec<_>>();
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(())
}
}