gnostr_asyncgit/sync/
ignore.rs

1use std::{
2	fs::{File, OpenOptions},
3	io::{Read, Seek, SeekFrom, Write},
4	path::Path,
5};
6
7use scopetime::scope_time;
8
9use super::{RepoPath, utils::work_dir};
10use crate::{
11	error::{Error, Result},
12	sync::repository::repo,
13};
14
15static GITIGNORE: &str = ".gitignore";
16
17/// add file or path to root ignore file
18pub fn add_to_ignore(
19	repo_path: &RepoPath,
20	path_to_ignore: &str,
21) -> Result<()> {
22	scope_time!("add_to_ignore");
23
24	let repo = repo(repo_path)?;
25
26	if Path::new(path_to_ignore).file_name()
27		== Path::new(GITIGNORE).file_name()
28	{
29		return Err(Error::Generic(String::from(
30			"cannot ignore gitignore",
31		)));
32	}
33
34	let ignore_file = work_dir(&repo)?.join(GITIGNORE);
35
36	let optional_newline = ignore_file.exists()
37		&& !file_ends_with_newline(&ignore_file)?;
38
39	let mut file = OpenOptions::new()
40		.append(true)
41		.create(true)
42		.open(ignore_file)?;
43
44	writeln!(
45		file,
46		"{}{}",
47		if optional_newline { "\n" } else { "" },
48		path_to_ignore
49	)?;
50
51	Ok(())
52}
53
54fn file_ends_with_newline(file: &Path) -> Result<bool> {
55	let mut file = File::open(file)?;
56	let size = file.metadata()?.len();
57
58	file.seek(SeekFrom::Start(size.saturating_sub(1)))?;
59	let mut last_char = String::with_capacity(1);
60	file.read_to_string(&mut last_char)?;
61
62	Ok(last_char == "\n")
63}
64
65#[cfg(test)]
66mod tests {
67	use std::{fs::File, io, path::Path};
68
69	use io::BufRead;
70	use pretty_assertions::assert_eq;
71
72	use super::*;
73	use crate::sync::{tests::repo_init, utils::repo_write_file};
74
75	#[test]
76	fn test_empty() -> Result<()> {
77		let ignore_file_path = Path::new(".gitignore");
78		let file_path = Path::new("foo.txt");
79		let (_td, repo) = repo_init()?;
80		let root = repo.path().parent().unwrap();
81		let repo_path: &RepoPath =
82			&root.as_os_str().to_str().unwrap().into();
83
84		File::create(root.join(file_path))?.write_all(b"test")?;
85
86		assert_eq!(root.join(ignore_file_path).exists(), false);
87		add_to_ignore(repo_path, file_path.to_str().unwrap())?;
88		assert_eq!(root.join(ignore_file_path).exists(), true);
89
90		Ok(())
91	}
92
93	fn read_lines<P>(
94		filename: P,
95	) -> io::Result<io::Lines<io::BufReader<File>>>
96	where
97		P: AsRef<Path>,
98	{
99		let file = File::open(filename)?;
100		Ok(io::BufReader::new(file).lines())
101	}
102
103	#[test]
104	fn test_append() -> Result<()> {
105		let ignore_file_path = Path::new(".gitignore");
106		let file_path = Path::new("foo.txt");
107		let (_td, repo) = repo_init()?;
108		let root = repo.path().parent().unwrap();
109		let repo_path: &RepoPath =
110			&root.as_os_str().to_str().unwrap().into();
111
112		File::create(root.join(file_path))?.write_all(b"test")?;
113		File::create(root.join(ignore_file_path))?
114			.write_all(b"foo\n")?;
115
116		add_to_ignore(repo_path, file_path.to_str().unwrap())?;
117
118		let mut lines =
119			read_lines(root.join(ignore_file_path)).unwrap();
120		assert_eq!(&lines.nth(1).unwrap().unwrap(), "foo.txt");
121
122		Ok(())
123	}
124
125	#[test]
126	fn test_append_no_newline_at_end() -> Result<()> {
127		let ignore_file_path = Path::new(".gitignore");
128		let file_path = Path::new("foo.txt");
129		let (_td, repo) = repo_init()?;
130		let root = repo.path().parent().unwrap();
131		let repo_path: &RepoPath =
132			&root.as_os_str().to_str().unwrap().into();
133
134		File::create(root.join(file_path))?.write_all(b"test")?;
135		File::create(root.join(ignore_file_path))?
136			.write_all(b"foo")?;
137
138		add_to_ignore(repo_path, file_path.to_str().unwrap())?;
139
140		let mut lines =
141			read_lines(root.join(ignore_file_path)).unwrap();
142		assert_eq!(&lines.nth(1).unwrap().unwrap(), "foo.txt");
143
144		Ok(())
145	}
146
147	#[test]
148	fn test_ignore_ignore() {
149		let ignore_file_path = Path::new(".gitignore");
150		let (_td, repo) = repo_init().unwrap();
151		let root = repo.path().parent().unwrap();
152		let repo_path: &RepoPath =
153			&root.as_os_str().to_str().unwrap().into();
154
155		repo_write_file(&repo, ".gitignore", "#foo").unwrap();
156
157		let res = add_to_ignore(repo_path, ".gitignore");
158		assert!(res.is_err());
159
160		let lines = read_lines(root.join(ignore_file_path)).unwrap();
161		assert_eq!(lines.count(), 1);
162	}
163}