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