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
15pub 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}