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
33 changes: 26 additions & 7 deletions asyncgit/src/sync/staging/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,20 @@ use std::{collections::HashSet, fs::File, io::Read};

const NEWLINE: char = '\n';

#[derive(Default)]
struct NewFromOldContent {
lines: Vec<String>,
old_index: usize,
trailing_newline: bool,
}

impl Default for NewFromOldContent {
fn default() -> Self {
Self {
lines: Vec::new(),
old_index: 0,
trailing_newline: true,
}
}
}

impl NewFromOldContent {
Expand Down Expand Up @@ -57,14 +67,11 @@ impl NewFromOldContent {
for line in old_lines.iter().skip(self.old_index) {
self.lines.push((*line).to_string());
}
let lines = self.lines.join("\n");
if lines.ends_with(NEWLINE) {
lines
} else {
let mut lines = lines;
let mut lines = self.lines.join("\n");
if self.trailing_newline && !lines.ends_with(NEWLINE) {
lines.push(NEWLINE);
lines
}
lines
}
}

Expand Down Expand Up @@ -133,6 +140,18 @@ pub fn apply_selection(
|| hunk_line.origin_value()
== DiffLineType::AddEOFNL
{
let applying = (is_staged && !selected_line)
|| (!is_staged && selected_line);
new_content.trailing_newline =
if hunk_line.origin_value()
== DiffLineType::DeleteEOFNL
{
// old had newline, new doesn't; applying removal → no newline
!applying
} else {
// AddEOFNL: old had no newline, new does; applying addition → newline
applying
};
break;
}

Expand Down
42 changes: 42 additions & 0 deletions asyncgit/src/sync/staging/stage_tracked.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,48 @@ c = 4";
assert_eq!(&*diff.hunks[0].lines[0].content, "@@ -1,2 +1 @@");
}

#[test]
fn test_stage_preserves_no_trailing_newline() {
// Both files have no trailing newline
static FILE_1: &str = "line1\nline2";
static FILE_2: &str = "line1_changed\nline2";

let (path, repo) = repo_init().unwrap();
let path: &RepoPath = &path.path().to_str().unwrap().into();

write_commit_file(&repo, "test.txt", FILE_1, "c1");
repo_write_file(&repo, "test.txt", FILE_2).unwrap();

// Stage only the changed first line
stage_lines(
path,
"test.txt",
false,
&[DiffLinePosition {
old_lineno: Some(1),
new_lineno: Some(1),
}],
)
.unwrap();

// Read the staged blob and verify it has no trailing newline
let git_repo = repo(path).unwrap();
let mut index = git_repo.index().unwrap();
index.read(true).unwrap();
let entry = index
.get_path(Path::new("test.txt"), 0)
.expect("entry not found");
let blob = git_repo.find_blob(entry.id).unwrap();
let staged = std::str::from_utf8(blob.content()).unwrap();

assert!(
!staged.ends_with('\n'),
"staged content must not gain a trailing newline; got: {:?}",
staged
);
assert_eq!(staged, "line1_changed\nline2");
}

#[test]
fn test_unstage() {
static FILE_1: &str = r"0
Expand Down