gnostr_asyncgit/sync/
reset.rs

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
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 =
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
25///
26pub 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) // windows: needs this to be true WTF?!
34		.remove_untracked(true)
35		.force()
36		.path(path);
37
38	repo.checkout_index(None, Some(&mut checkout_opts))?;
39	Ok(())
40}
41
42///
43pub 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		// overwrite with next content
134		{
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}