Skip to main content

asyncgit/sync/
ignore.rs

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