1use git2::{ObjectType, ResetType, build::CheckoutBuilder};
2use scopetime::scope_time;
3
4use super::{CommitId, RepoPath, utils::get_head_repo};
5use crate::{error::Result, sync::repository::repo};
6
7pub 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 =
15 repo.find_object(id.into(), Some(ObjectType::Commit))?;
16
17 repo.reset_default(Some(&obj), [path])?;
18 } else {
19 repo.reset_default(None, [path])?;
20 }
21
22 Ok(())
23}
24
25pub fn reset_workdir(repo_path: &RepoPath, path: &str) -> Result<()> {
27 scope_time!("reset_workdir");
28
29 let repo = repo(repo_path)?;
30
31 let mut checkout_opts = CheckoutBuilder::new();
32 checkout_opts
33 .update_index(true) .remove_untracked(true)
35 .force()
36 .path(path);
37
38 repo.checkout_index(None, Some(&mut checkout_opts))?;
39 Ok(())
40}
41
42pub fn reset_repo(
44 repo_path: &RepoPath,
45 commit: CommitId,
46 kind: ResetType,
47) -> Result<()> {
48 scope_time!("reset_repo");
49
50 let repo = repo(repo_path)?;
51
52 let c = repo.find_commit(commit.into())?;
53
54 repo.reset(c.as_object(), kind, None)?;
55
56 Ok(())
57}
58
59#[cfg(test)]
60mod tests {
61 use std::{
62 fs::{self, File},
63 io::Write,
64 path::Path,
65 };
66
67 use super::{reset_stage, reset_workdir};
68 use crate::{
69 error::Result,
70 sync::{
71 RepoPath, commit,
72 status::{StatusType, get_status},
73 tests::{
74 debug_cmd_print, get_statuses, repo_init,
75 repo_init_empty,
76 },
77 utils::{stage_add_all, stage_add_file},
78 },
79 };
80
81 static HUNK_A: &str = r"
821 start
832
843
854
865
876 middle
887
898
909
910
921 end";
93
94 static HUNK_B: &str = r"
951 start
962 newa
973
984
995
1006 middle
1017
1028
1039
1040 newb
1051 end";
106
107 #[test]
108 fn test_reset_only_unstaged() {
109 let (_td, repo) = repo_init().unwrap();
110 let root = repo.path().parent().unwrap();
111 let repo_path: &RepoPath =
112 &root.as_os_str().to_str().unwrap().into();
113
114 let res = get_status(repo_path, StatusType::WorkingDir, None)
115 .unwrap();
116 assert_eq!(res.len(), 0);
117
118 let file_path = root.join("bar.txt");
119
120 {
121 File::create(&file_path)
122 .unwrap()
123 .write_all(HUNK_A.as_bytes())
124 .unwrap();
125 }
126
127 debug_cmd_print(repo_path, "git status");
128
129 stage_add_file(repo_path, Path::new("bar.txt")).unwrap();
130
131 debug_cmd_print(repo_path, "git status");
132
133 {
135 File::create(&file_path)
136 .unwrap()
137 .write_all(HUNK_B.as_bytes())
138 .unwrap();
139 }
140
141 debug_cmd_print(repo_path, "git status");
142
143 assert_eq!(get_statuses(repo_path), (1, 1));
144
145 reset_workdir(repo_path, "bar.txt").unwrap();
146
147 debug_cmd_print(repo_path, "git status");
148
149 assert_eq!(get_statuses(repo_path), (0, 1));
150 }
151
152 #[test]
153 fn test_reset_untracked_in_subdir() {
154 let (_td, repo) = repo_init().unwrap();
155 let root = repo.path().parent().unwrap();
156 let repo_path: &RepoPath =
157 &root.as_os_str().to_str().unwrap().into();
158
159 {
160 fs::create_dir(root.join("foo")).unwrap();
161 File::create(root.join("foo/bar.txt"))
162 .unwrap()
163 .write_all(b"test\nfoo")
164 .unwrap();
165 }
166
167 debug_cmd_print(repo_path, "git status");
168
169 assert_eq!(get_statuses(repo_path), (1, 0));
170
171 reset_workdir(repo_path, "foo/bar.txt").unwrap();
172
173 debug_cmd_print(repo_path, "git status");
174
175 assert_eq!(get_statuses(repo_path), (0, 0));
176 }
177
178 #[test]
179 fn test_reset_folder() -> Result<()> {
180 let (_td, repo) = repo_init().unwrap();
181 let root = repo.path().parent().unwrap();
182 let repo_path: &RepoPath =
183 &root.as_os_str().to_str().unwrap().into();
184
185 {
186 fs::create_dir(root.join("foo"))?;
187 File::create(root.join("foo/file1.txt"))?
188 .write_all(b"file1")?;
189 File::create(root.join("foo/file2.txt"))?
190 .write_all(b"file1")?;
191 File::create(root.join("file3.txt"))?
192 .write_all(b"file3")?;
193 }
194
195 stage_add_all(repo_path, "*", None).unwrap();
196 commit(repo_path, "msg").unwrap();
197
198 {
199 File::create(root.join("foo/file1.txt"))?
200 .write_all(b"file1\nadded line")?;
201 fs::remove_file(root.join("foo/file2.txt"))?;
202 File::create(root.join("foo/file4.txt"))?
203 .write_all(b"file4")?;
204 File::create(root.join("foo/file5.txt"))?
205 .write_all(b"file5")?;
206 File::create(root.join("file3.txt"))?
207 .write_all(b"file3\nadded line")?;
208 }
209
210 assert_eq!(get_statuses(repo_path), (5, 0));
211
212 stage_add_file(repo_path, Path::new("foo/file5.txt"))
213 .unwrap();
214
215 assert_eq!(get_statuses(repo_path), (4, 1));
216
217 reset_workdir(repo_path, "foo").unwrap();
218
219 assert_eq!(get_statuses(repo_path), (1, 1));
220
221 Ok(())
222 }
223
224 #[test]
225 fn test_reset_untracked_in_subdir_and_index() {
226 let (_td, repo) = repo_init().unwrap();
227 let root = repo.path().parent().unwrap();
228 let repo_path: &RepoPath =
229 &root.as_os_str().to_str().unwrap().into();
230 let file = "foo/bar.txt";
231
232 {
233 fs::create_dir(root.join("foo")).unwrap();
234 File::create(root.join(file))
235 .unwrap()
236 .write_all(b"test\nfoo")
237 .unwrap();
238 }
239
240 debug_cmd_print(repo_path, "git status");
241
242 debug_cmd_print(repo_path, "git add .");
243
244 debug_cmd_print(repo_path, "git status");
245
246 {
247 File::create(root.join(file))
248 .unwrap()
249 .write_all(b"test\nfoo\nnewend")
250 .unwrap();
251 }
252
253 debug_cmd_print(repo_path, "git status");
254
255 assert_eq!(get_statuses(repo_path), (1, 1));
256
257 reset_workdir(repo_path, file).unwrap();
258
259 debug_cmd_print(repo_path, "git status");
260
261 assert_eq!(get_statuses(repo_path), (0, 1));
262 }
263
264 #[test]
265 fn unstage_in_empty_repo() {
266 let file_path = Path::new("foo.txt");
267 let (_td, repo) = repo_init_empty().unwrap();
268 let root = repo.path().parent().unwrap();
269 let repo_path: &RepoPath =
270 &root.as_os_str().to_str().unwrap().into();
271
272 File::create(root.join(file_path))
273 .unwrap()
274 .write_all(b"test\nfoo")
275 .unwrap();
276
277 assert_eq!(get_statuses(repo_path), (1, 0));
278
279 stage_add_file(repo_path, file_path).unwrap();
280
281 assert_eq!(get_statuses(repo_path), (0, 1));
282
283 reset_stage(repo_path, file_path.to_str().unwrap()).unwrap();
284
285 assert_eq!(get_statuses(repo_path), (1, 0));
286 }
287
288 #[test]
289 fn test_reset_untracked_in_subdir_with_cwd_in_subdir() {
290 let (_td, repo) = repo_init().unwrap();
291 let root = repo.path().parent().unwrap();
292 let repo_path: &RepoPath =
293 &root.as_os_str().to_str().unwrap().into();
294
295 {
296 fs::create_dir(root.join("foo")).unwrap();
297 File::create(root.join("foo/bar.txt"))
298 .unwrap()
299 .write_all(b"test\nfoo")
300 .unwrap();
301 }
302
303 debug_cmd_print(repo_path, "git status");
304
305 assert_eq!(get_statuses(repo_path), (1, 0));
306
307 reset_workdir(
308 &root.join("foo").as_os_str().to_str().unwrap().into(),
309 "foo/bar.txt",
310 )
311 .unwrap();
312
313 debug_cmd_print(repo_path, "git status");
314
315 assert_eq!(get_statuses(repo_path), (0, 0));
316 }
317
318 #[test]
319 fn test_reset_untracked_subdir() {
320 let (_td, repo) = repo_init().unwrap();
321 let root = repo.path().parent().unwrap();
322 let repo_path: &RepoPath =
323 &root.as_os_str().to_str().unwrap().into();
324
325 {
326 fs::create_dir_all(root.join("foo/bar")).unwrap();
327 File::create(root.join("foo/bar/baz.txt"))
328 .unwrap()
329 .write_all(b"test\nfoo")
330 .unwrap();
331 }
332
333 debug_cmd_print(repo_path, "git status");
334
335 assert_eq!(get_statuses(repo_path), (1, 0));
336
337 reset_workdir(repo_path, "foo/bar").unwrap();
338
339 debug_cmd_print(repo_path, "git status");
340
341 assert_eq!(get_statuses(repo_path), (0, 0));
342 }
343}