rustic_git/commands/
add.rs

1use std::path::Path;
2
3use crate::{Repository, Result};
4use crate::utils::git;
5
6impl Repository {
7    /// Add specific files or paths to the staging area.
8    ///
9    /// # Arguments
10    ///
11    /// * `paths` - The file paths to add to the staging area
12    ///
13    /// # Returns
14    ///
15    /// A `Result` indicating success or a `GitError` if the operation fails.
16    pub fn add<P: AsRef<Path>>(&self, paths: &[P]) -> Result<()> {
17        Self::ensure_git()?;
18
19        if paths.is_empty() {
20            return Ok(());
21        }
22
23        let mut args = vec!["add"];
24        let path_strings: Vec<String> = paths
25            .iter()
26            .map(|p| p.as_ref().to_string_lossy().to_string())
27            .collect();
28        
29        for path_str in &path_strings {
30            args.push(path_str);
31        }
32
33        let _stdout = git(&args, Some(self.repo_path()))?;
34        Ok(())
35    }
36
37    /// Add all changes to the staging area (equivalent to `git add .`).
38    ///
39    /// # Returns
40    ///
41    /// A `Result` indicating success or a `GitError` if the operation fails.
42    pub fn add_all(&self) -> Result<()> {
43        Self::ensure_git()?;
44        let _stdout = git(&["add", "."], Some(self.repo_path()))?;
45        Ok(())
46    }
47
48    /// Add all tracked files that have been modified (equivalent to `git add -u`).
49    ///
50    /// # Returns
51    ///
52    /// A `Result` indicating success or a `GitError` if the operation fails.
53    pub fn add_update(&self) -> Result<()> {
54        Self::ensure_git()?;
55        let _stdout = git(&["add", "-u"], Some(self.repo_path()))?;
56        Ok(())
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use std::fs;
64    use std::path::Path;
65
66    fn create_test_repo(path: &str) -> Repository {
67        // Clean up if exists
68        if Path::new(path).exists() {
69            fs::remove_dir_all(path).unwrap();
70        }
71        
72        Repository::init(path, false).unwrap()
73    }
74
75    fn create_test_file(repo_path: &str, filename: &str, content: &str) {
76        let file_path = format!("{}/{}", repo_path, filename);
77        fs::write(file_path, content).unwrap();
78    }
79
80    #[test]
81    fn test_add_specific_files() {
82        let test_path = "/tmp/test_add_repo";
83        let repo = create_test_repo(test_path);
84
85        // Create some test files
86        create_test_file(test_path, "file1.txt", "content 1");
87        create_test_file(test_path, "file2.txt", "content 2");
88
89        // Add specific files
90        let result = repo.add(&["file1.txt"]);
91        assert!(result.is_ok());
92
93        // Verify file1.txt is staged by checking status
94        let status = repo.status().unwrap();
95        let added_files: Vec<_> = status.files.iter()
96            .filter(|(s, _)| matches!(s, crate::FileStatus::Added))
97            .map(|(_, f)| f.as_str())
98            .collect();
99        
100        assert!(added_files.contains(&"file1.txt"));
101
102        // Clean up
103        fs::remove_dir_all(test_path).unwrap();
104    }
105
106    #[test]
107    fn test_add_multiple_files() {
108        let test_path = "/tmp/test_add_multiple_repo";
109        let repo = create_test_repo(test_path);
110
111        // Create test files
112        create_test_file(test_path, "file1.txt", "content 1");
113        create_test_file(test_path, "file2.txt", "content 2");
114        create_test_file(test_path, "file3.txt", "content 3");
115
116        // Add multiple files
117        let result = repo.add(&["file1.txt", "file2.txt"]);
118        assert!(result.is_ok());
119
120        // Verify files are staged
121        let status = repo.status().unwrap();
122        let added_files: Vec<_> = status.files.iter()
123            .filter(|(s, _)| matches!(s, crate::FileStatus::Added))
124            .map(|(_, f)| f.as_str())
125            .collect();
126        
127        assert!(added_files.contains(&"file1.txt"));
128        assert!(added_files.contains(&"file2.txt"));
129        assert_eq!(added_files.len(), 2);
130
131        // Clean up
132        fs::remove_dir_all(test_path).unwrap();
133    }
134
135    #[test]
136    fn test_add_all() {
137        let test_path = "/tmp/test_add_all_repo";
138        let repo = create_test_repo(test_path);
139
140        // Create test files
141        create_test_file(test_path, "file1.txt", "content 1");
142        create_test_file(test_path, "file2.txt", "content 2");
143        fs::create_dir(format!("{}/subdir", test_path)).unwrap();
144        create_test_file(test_path, "subdir/file3.txt", "content 3");
145
146        // Add all files
147        let result = repo.add_all();
148        assert!(result.is_ok());
149
150        // Verify all files are staged
151        let status = repo.status().unwrap();
152        let added_files: Vec<_> = status.files.iter()
153            .filter(|(s, _)| matches!(s, crate::FileStatus::Added))
154            .map(|(_, f)| f.as_str())
155            .collect();
156        
157        assert!(added_files.contains(&"file1.txt"));
158        assert!(added_files.contains(&"file2.txt"));
159        assert!(added_files.contains(&"subdir/file3.txt"));
160
161        // Clean up
162        fs::remove_dir_all(test_path).unwrap();
163    }
164
165    #[test]
166    fn test_add_empty_paths() {
167        let test_path = "/tmp/test_add_empty_repo";
168        let repo = create_test_repo(test_path);
169
170        // Adding empty paths should succeed without error
171        let result = repo.add::<&str>(&[]);
172        assert!(result.is_ok());
173
174        // Clean up
175        fs::remove_dir_all(test_path).unwrap();
176    }
177
178    #[test]
179    fn test_add_nonexistent_file() {
180        let test_path = "/tmp/test_add_nonexistent_repo";
181        let repo = create_test_repo(test_path);
182
183        // Adding non-existent file should fail
184        let result = repo.add(&["nonexistent.txt"]);
185        assert!(result.is_err());
186
187        // Clean up
188        fs::remove_dir_all(test_path).unwrap();
189    }
190}