From a84ae0950cb50549cde6cf9039eaf5668d2ced46 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Fri, 10 Jul 2020 11:25:00 +0200 Subject: [PATCH] `add_to_ignore` failed on files without a newline at EOF (closes #191) --- CHANGELOG.md | 1 + asyncgit/src/sync/ignore.rs | 106 +++++++++++++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05c2aaee..5dee7866 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - removed unmaintained dependency `spin` ([#172](https://github.com/extrawurst/gitui/issues/172)) - opening relative paths in external editor may fail in subpaths ([#184](https://github.com/extrawurst/gitui/issues/184)) - crashes in revlog with utf8 commit messages ([#188](https://github.com/extrawurst/gitui/issues/188)) +- `add_to_ignore` failed on files without a newline at EOF ([#191](https://github.com/extrawurst/gitui/issues/191)) ## [0.8.1] - 2020-07-07 diff --git a/asyncgit/src/sync/ignore.rs b/asyncgit/src/sync/ignore.rs index 38ce376b..c71ad462 100644 --- a/asyncgit/src/sync/ignore.rs +++ b/asyncgit/src/sync/ignore.rs @@ -1,7 +1,11 @@ use super::utils::{repo, work_dir}; use crate::error::Result; use scopetime::scope_time; -use std::{fs::OpenOptions, io::Write}; +use std::{ + fs::{File, OpenOptions}, + io::{Read, Seek, SeekFrom, Write}, + path::PathBuf, +}; static GITIGNORE: &str = ".gitignore"; @@ -16,12 +20,110 @@ pub fn add_to_ignore( let ignore_file = work_dir(&repo).join(GITIGNORE); + let optional_newline = ignore_file.exists() + && !file_ends_with_newline(&ignore_file)?; + let mut file = OpenOptions::new() .append(true) .create(true) .open(ignore_file)?; - writeln!(file, "{}", path_to_ignore)?; + writeln!( + file, + "{}{}", + if optional_newline { "\n" } else { "" }, + path_to_ignore + )?; Ok(()) } + +fn file_ends_with_newline(file: &PathBuf) -> Result { + let mut file = File::open(file)?; + let size = file.metadata()?.len(); + + file.seek(SeekFrom::Start(size.saturating_sub(1)))?; + let mut last_char = String::with_capacity(1); + file.read_to_string(&mut last_char)?; + + dbg!(&last_char); + + Ok(last_char == "\n") +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::sync::tests::repo_init; + use io::BufRead; + use std::{fs::File, io, path::Path}; + + #[test] + fn test_empty() -> Result<()> { + let ignore_file_path = Path::new(".gitignore"); + let file_path = Path::new("foo.txt"); + let (_td, repo) = repo_init()?; + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); + + File::create(&root.join(file_path))?.write_all(b"test")?; + + assert_eq!(root.join(ignore_file_path).exists(), false); + add_to_ignore(repo_path, file_path.to_str().unwrap())?; + assert_eq!(root.join(ignore_file_path).exists(), true); + + Ok(()) + } + + fn read_lines

( + filename: P, + ) -> io::Result>> + where + P: AsRef, + { + let file = File::open(filename)?; + Ok(io::BufReader::new(file).lines()) + } + + #[test] + fn test_append() -> Result<()> { + let ignore_file_path = Path::new(".gitignore"); + let file_path = Path::new("foo.txt"); + let (_td, repo) = repo_init()?; + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); + + File::create(&root.join(file_path))?.write_all(b"test")?; + File::create(&root.join(ignore_file_path))? + .write_all(b"foo\n")?; + + add_to_ignore(repo_path, file_path.to_str().unwrap())?; + + let mut lines = + read_lines(&root.join(ignore_file_path)).unwrap(); + assert_eq!(&lines.nth(1).unwrap().unwrap(), "foo.txt"); + + Ok(()) + } + + #[test] + fn test_append_no_newline_at_end() -> Result<()> { + let ignore_file_path = Path::new(".gitignore"); + let file_path = Path::new("foo.txt"); + let (_td, repo) = repo_init()?; + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); + + File::create(&root.join(file_path))?.write_all(b"test")?; + File::create(&root.join(ignore_file_path))? + .write_all(b"foo")?; + + add_to_ignore(repo_path, file_path.to_str().unwrap())?; + + let mut lines = + read_lines(&root.join(ignore_file_path)).unwrap(); + assert_eq!(&lines.nth(1).unwrap().unwrap(), "foo.txt"); + + Ok(()) + } +}