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
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 = 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
24pub 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) .remove_untracked(true)
34 .force()
35 .path(path);
36
37 repo.checkout_index(None, Some(&mut checkout_opts))?;
38 Ok(())
39}
40
41pub 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 {
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}