gnostr_asyncgit/sync/
reset.rs

1use git2::{build::CheckoutBuilder, ObjectType, ResetType};
2use scopetime::scope_time;
3
4use super::{utils::get_head_repo, CommitId, RepoPath};
5use crate::{error::Result, sync::repository::repo};
6
7///
8pub fn reset_stage(repo_path: &RepoPath, path: &str) -> Result<()> {
9    scope_time!("reset_stage");
10
11    let repo = repo(repo_path)?;
12
13    if let Ok(id) = get_head_repo(&repo) {
14        let obj = repo.find_object(id.into(), Some(ObjectType::Commit))?;
15
16        repo.reset_default(Some(&obj), [path])?;
17    } else {
18        repo.reset_default(None, [path])?;
19    }
20
21    Ok(())
22}
23
24///
25pub fn reset_workdir(repo_path: &RepoPath, path: &str) -> Result<()> {
26    scope_time!("reset_workdir");
27
28    let repo = repo(repo_path)?;
29
30    let mut checkout_opts = CheckoutBuilder::new();
31    checkout_opts
32        .update_index(true) // windows: needs this to be true WTF?!
33        .remove_untracked(true)
34        .force()
35        .path(path);
36
37    repo.checkout_index(None, Some(&mut checkout_opts))?;
38    Ok(())
39}
40
41///
42pub fn reset_repo(repo_path: &RepoPath, commit: CommitId, kind: ResetType) -> Result<()> {
43    scope_time!("reset_repo");
44
45    let repo = repo(repo_path)?;
46
47    let c = repo.find_commit(commit.into())?;
48
49    repo.reset(c.as_object(), kind, None)?;
50
51    Ok(())
52}
53
54#[cfg(test)]
55mod tests {
56    use std::{
57        fs::{self, File},
58        io::Write,
59        path::Path,
60    };
61
62    use super::{reset_stage, reset_workdir};
63    use crate::{
64        error::Result,
65        sync::{
66            commit,
67            status::{get_status, StatusType},
68            tests::{debug_cmd_print, get_statuses, repo_init, repo_init_empty},
69            utils::{stage_add_all, stage_add_file},
70            RepoPath,
71        },
72    };
73
74    static HUNK_A: &str = r"
751   start
762
773
784
795
806   middle
817
828
839
840
851   end";
86
87    static HUNK_B: &str = r"
881   start
892   newa
903
914
925
936   middle
947
958
969
970   newb
981   end";
99
100    #[test]
101    fn test_reset_only_unstaged() {
102        let (_td, repo) = repo_init().unwrap();
103        let root = repo.path().parent().unwrap();
104        let repo_path: &RepoPath = &root.as_os_str().to_str().unwrap().into();
105
106        let res = get_status(repo_path, StatusType::WorkingDir, None).unwrap();
107        assert_eq!(res.len(), 0);
108
109        let file_path = root.join("bar.txt");
110
111        {
112            File::create(&file_path)
113                .unwrap()
114                .write_all(HUNK_A.as_bytes())
115                .unwrap();
116        }
117
118        debug_cmd_print(repo_path, "git status");
119
120        stage_add_file(repo_path, Path::new("bar.txt")).unwrap();
121
122        debug_cmd_print(repo_path, "git status");
123
124        // overwrite with next content
125        {
126            File::create(&file_path)
127                .unwrap()
128                .write_all(HUNK_B.as_bytes())
129                .unwrap();
130        }
131
132        debug_cmd_print(repo_path, "git status");
133
134        assert_eq!(get_statuses(repo_path), (1, 1));
135
136        reset_workdir(repo_path, "bar.txt").unwrap();
137
138        debug_cmd_print(repo_path, "git status");
139
140        assert_eq!(get_statuses(repo_path), (0, 1));
141    }
142
143    #[test]
144    fn test_reset_untracked_in_subdir() {
145        let (_td, repo) = repo_init().unwrap();
146        let root = repo.path().parent().unwrap();
147        let repo_path: &RepoPath = &root.as_os_str().to_str().unwrap().into();
148
149        {
150            fs::create_dir(root.join("foo")).unwrap();
151            File::create(root.join("foo/bar.txt"))
152                .unwrap()
153                .write_all(b"test\nfoo")
154                .unwrap();
155        }
156
157        debug_cmd_print(repo_path, "git status");
158
159        assert_eq!(get_statuses(repo_path), (1, 0));
160
161        reset_workdir(repo_path, "foo/bar.txt").unwrap();
162
163        debug_cmd_print(repo_path, "git status");
164
165        assert_eq!(get_statuses(repo_path), (0, 0));
166    }
167
168    #[test]
169    fn test_reset_folder() -> Result<()> {
170        let (_td, repo) = repo_init().unwrap();
171        let root = repo.path().parent().unwrap();
172        let repo_path: &RepoPath = &root.as_os_str().to_str().unwrap().into();
173
174        {
175            fs::create_dir(root.join("foo"))?;
176            File::create(root.join("foo/file1.txt"))?.write_all(b"file1")?;
177            File::create(root.join("foo/file2.txt"))?.write_all(b"file1")?;
178            File::create(root.join("file3.txt"))?.write_all(b"file3")?;
179        }
180
181        stage_add_all(repo_path, "*", None).unwrap();
182        commit(repo_path, "msg").unwrap();
183
184        {
185            File::create(root.join("foo/file1.txt"))?.write_all(b"file1\nadded line")?;
186            fs::remove_file(root.join("foo/file2.txt"))?;
187            File::create(root.join("foo/file4.txt"))?.write_all(b"file4")?;
188            File::create(root.join("foo/file5.txt"))?.write_all(b"file5")?;
189            File::create(root.join("file3.txt"))?.write_all(b"file3\nadded line")?;
190        }
191
192        assert_eq!(get_statuses(repo_path), (5, 0));
193
194        stage_add_file(repo_path, Path::new("foo/file5.txt")).unwrap();
195
196        assert_eq!(get_statuses(repo_path), (4, 1));
197
198        reset_workdir(repo_path, "foo").unwrap();
199
200        assert_eq!(get_statuses(repo_path), (1, 1));
201
202        Ok(())
203    }
204
205    #[test]
206    fn test_reset_untracked_in_subdir_and_index() {
207        let (_td, repo) = repo_init().unwrap();
208        let root = repo.path().parent().unwrap();
209        let repo_path: &RepoPath = &root.as_os_str().to_str().unwrap().into();
210        let file = "foo/bar.txt";
211
212        {
213            fs::create_dir(root.join("foo")).unwrap();
214            File::create(root.join(file))
215                .unwrap()
216                .write_all(b"test\nfoo")
217                .unwrap();
218        }
219
220        debug_cmd_print(repo_path, "git status");
221
222        debug_cmd_print(repo_path, "git add .");
223
224        debug_cmd_print(repo_path, "git status");
225
226        {
227            File::create(root.join(file))
228                .unwrap()
229                .write_all(b"test\nfoo\nnewend")
230                .unwrap();
231        }
232
233        debug_cmd_print(repo_path, "git status");
234
235        assert_eq!(get_statuses(repo_path), (1, 1));
236
237        reset_workdir(repo_path, file).unwrap();
238
239        debug_cmd_print(repo_path, "git status");
240
241        assert_eq!(get_statuses(repo_path), (0, 1));
242    }
243
244    #[test]
245    fn unstage_in_empty_repo() {
246        let file_path = Path::new("foo.txt");
247        let (_td, repo) = repo_init_empty().unwrap();
248        let root = repo.path().parent().unwrap();
249        let repo_path: &RepoPath = &root.as_os_str().to_str().unwrap().into();
250
251        File::create(root.join(file_path))
252            .unwrap()
253            .write_all(b"test\nfoo")
254            .unwrap();
255
256        assert_eq!(get_statuses(repo_path), (1, 0));
257
258        stage_add_file(repo_path, file_path).unwrap();
259
260        assert_eq!(get_statuses(repo_path), (0, 1));
261
262        reset_stage(repo_path, file_path.to_str().unwrap()).unwrap();
263
264        assert_eq!(get_statuses(repo_path), (1, 0));
265    }
266
267    #[test]
268    fn test_reset_untracked_in_subdir_with_cwd_in_subdir() {
269        let (_td, repo) = repo_init().unwrap();
270        let root = repo.path().parent().unwrap();
271        let repo_path: &RepoPath = &root.as_os_str().to_str().unwrap().into();
272
273        {
274            fs::create_dir(root.join("foo")).unwrap();
275            File::create(root.join("foo/bar.txt"))
276                .unwrap()
277                .write_all(b"test\nfoo")
278                .unwrap();
279        }
280
281        debug_cmd_print(repo_path, "git status");
282
283        assert_eq!(get_statuses(repo_path), (1, 0));
284
285        reset_workdir(
286            &root.join("foo").as_os_str().to_str().unwrap().into(),
287            "foo/bar.txt",
288        )
289        .unwrap();
290
291        debug_cmd_print(repo_path, "git status");
292
293        assert_eq!(get_statuses(repo_path), (0, 0));
294    }
295
296    #[test]
297    fn test_reset_untracked_subdir() {
298        let (_td, repo) = repo_init().unwrap();
299        let root = repo.path().parent().unwrap();
300        let repo_path: &RepoPath = &root.as_os_str().to_str().unwrap().into();
301
302        {
303            fs::create_dir_all(root.join("foo/bar")).unwrap();
304            File::create(root.join("foo/bar/baz.txt"))
305                .unwrap()
306                .write_all(b"test\nfoo")
307                .unwrap();
308        }
309
310        debug_cmd_print(repo_path, "git status");
311
312        assert_eq!(get_statuses(repo_path), (1, 0));
313
314        reset_workdir(repo_path, "foo/bar").unwrap();
315
316        debug_cmd_print(repo_path, "git status");
317
318        assert_eq!(get_statuses(repo_path), (0, 0));
319    }
320}