gnostr_asyncgit/sync/branch/
merge_rebase.rs

1//! merging from upstream (rebase)
2
3use git2::BranchType;
4use scopetime::scope_time;
5
6use crate::{
7	error::{Error, Result},
8	sync::{
9		CommitId, RepoPath, rebase::conflict_free_rebase,
10		repository::repo,
11	},
12};
13
14/// tries merging current branch with its upstream using rebase
15pub fn merge_upstream_rebase(
16	repo_path: &RepoPath,
17	branch_name: &str,
18) -> Result<CommitId> {
19	scope_time!("merge_upstream_rebase");
20
21	let repo = repo(repo_path)?;
22	if super::get_branch_name_repo(&repo)? != branch_name {
23		return Err(Error::Generic(String::from(
24			"can only rebase in head branch",
25		)));
26	}
27
28	let branch = repo.find_branch(branch_name, BranchType::Local)?;
29	let upstream = branch.upstream()?;
30	let upstream_commit = upstream.get().peel_to_commit()?;
31	let annotated_upstream =
32		repo.find_annotated_commit(upstream_commit.id())?;
33
34	conflict_free_rebase(&repo, &annotated_upstream)
35}
36
37#[cfg(test)]
38mod test {
39	use git2::{Repository, Time};
40
41	use super::*;
42	use crate::sync::{
43		RepoState, branch_compare_upstream, get_commits_info,
44		remotes::{fetch, push::push_branch},
45		tests::{
46			debug_cmd_print, get_commit_ids, repo_clone,
47			repo_init_bare, write_commit_file, write_commit_file_at,
48		},
49	};
50
51	fn get_commit_msgs(r: &Repository) -> Vec<String> {
52		let commits = get_commit_ids(r, 10);
53		get_commits_info(
54			&r.workdir().unwrap().to_str().unwrap().into(),
55			&commits,
56			10,
57		)
58		.unwrap()
59		.into_iter()
60		.map(|c| c.message)
61		.collect()
62	}
63
64	#[test]
65	fn test_merge_normal() {
66		let (r1_dir, _repo) = repo_init_bare().unwrap();
67
68		let (clone1_dir, clone1) =
69			repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
70
71		let clone1_dir = clone1_dir.path().to_str().unwrap();
72
73		// clone1
74
75		let _commit1 = write_commit_file_at(
76			&clone1,
77			"test.txt",
78			"test",
79			"commit1",
80			git2::Time::new(0, 0),
81		);
82
83		assert_eq!(clone1.head_detached().unwrap(), false);
84
85		push_branch(
86			&clone1_dir.into(),
87			"origin",
88			"master",
89			false,
90			false,
91			None,
92			None,
93		)
94		.unwrap();
95
96		assert_eq!(clone1.head_detached().unwrap(), false);
97
98		// clone2
99
100		let (clone2_dir, clone2) =
101			repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
102
103		let clone2_dir = clone2_dir.path().to_str().unwrap();
104
105		let _commit2 = write_commit_file_at(
106			&clone2,
107			"test2.txt",
108			"test",
109			"commit2",
110			git2::Time::new(1, 0),
111		);
112
113		assert_eq!(clone2.head_detached().unwrap(), false);
114
115		push_branch(
116			&clone2_dir.into(),
117			"origin",
118			"master",
119			false,
120			false,
121			None,
122			None,
123		)
124		.unwrap();
125
126		assert_eq!(clone2.head_detached().unwrap(), false);
127
128		// clone1
129
130		let _commit3 = write_commit_file_at(
131			&clone1,
132			"test3.txt",
133			"test",
134			"commit3",
135			git2::Time::new(2, 0),
136		);
137
138		assert_eq!(clone1.head_detached().unwrap(), false);
139
140		//lets fetch from origin
141		let bytes =
142			fetch(&clone1_dir.into(), "master", None, None).unwrap();
143		assert!(bytes > 0);
144
145		//we should be one commit behind
146		assert_eq!(
147			branch_compare_upstream(&clone1_dir.into(), "master")
148				.unwrap()
149				.behind,
150			1
151		);
152
153		// debug_cmd_print(clone1_dir, "git status");
154
155		assert_eq!(clone1.head_detached().unwrap(), false);
156
157		merge_upstream_rebase(&clone1_dir.into(), "master").unwrap();
158
159		debug_cmd_print(&clone1_dir.into(), "git log");
160
161		let state =
162			crate::sync::repo_state(&clone1_dir.into()).unwrap();
163		assert_eq!(state, RepoState::Clean);
164
165		let commits = get_commit_msgs(&clone1);
166		assert_eq!(commits, vec![
167			String::from("commit3"),
168			String::from("commit2"),
169			String::from("commit1")
170		]);
171
172		assert_eq!(clone1.head_detached().unwrap(), false);
173	}
174
175	#[test]
176	fn test_merge_multiple() {
177		let (r1_dir, _repo) = repo_init_bare().unwrap();
178
179		let (clone1_dir, clone1) =
180			repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
181
182		let clone1_dir = clone1_dir.path().to_str().unwrap();
183
184		// clone1
185
186		write_commit_file_at(
187			&clone1,
188			"test.txt",
189			"test",
190			"commit1",
191			Time::new(0, 0),
192		);
193
194		push_branch(
195			&clone1_dir.into(),
196			"origin",
197			"master",
198			false,
199			false,
200			None,
201			None,
202		)
203		.unwrap();
204
205		// clone2
206
207		let (clone2_dir, clone2) =
208			repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
209
210		let clone2_dir = clone2_dir.path().to_str().unwrap();
211
212		write_commit_file_at(
213			&clone2,
214			"test2.txt",
215			"test",
216			"commit2",
217			Time::new(1, 0),
218		);
219
220		push_branch(
221			&clone2_dir.into(),
222			"origin",
223			"master",
224			false,
225			false,
226			None,
227			None,
228		)
229		.unwrap();
230
231		// clone1
232
233		write_commit_file_at(
234			&clone1,
235			"test3.txt",
236			"test",
237			"commit3",
238			Time::new(2, 0),
239		);
240		write_commit_file_at(
241			&clone1,
242			"test4.txt",
243			"test",
244			"commit4",
245			Time::new(3, 0),
246		);
247
248		//lets fetch from origin
249
250		fetch(&clone1_dir.into(), "master", None, None).unwrap();
251
252		merge_upstream_rebase(&clone1_dir.into(), "master").unwrap();
253
254		debug_cmd_print(&clone1_dir.into(), "git log");
255
256		let state =
257			crate::sync::repo_state(&clone1_dir.into()).unwrap();
258		assert_eq!(state, RepoState::Clean);
259
260		let commits = get_commit_msgs(&clone1);
261		assert_eq!(commits, vec![
262			String::from("commit4"),
263			String::from("commit3"),
264			String::from("commit2"),
265			String::from("commit1")
266		]);
267
268		assert_eq!(clone1.head_detached().unwrap(), false);
269	}
270
271	#[test]
272	fn test_merge_conflict() {
273		let (r1_dir, _repo) = repo_init_bare().unwrap();
274
275		let (clone1_dir, clone1) =
276			repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
277
278		let clone1_dir = clone1_dir.path().to_str().unwrap();
279
280		// clone1
281
282		let _commit1 =
283			write_commit_file(&clone1, "test.txt", "test", "commit1");
284
285		push_branch(
286			&clone1_dir.into(),
287			"origin",
288			"master",
289			false,
290			false,
291			None,
292			None,
293		)
294		.unwrap();
295
296		// clone2
297
298		let (clone2_dir, clone2) =
299			repo_clone(r1_dir.path().to_str().unwrap()).unwrap();
300
301		let clone2_dir = clone2_dir.path().to_str().unwrap();
302
303		let _commit2 = write_commit_file(
304			&clone2,
305			"test2.txt",
306			"test",
307			"commit2",
308		);
309
310		push_branch(
311			&clone2_dir.into(),
312			"origin",
313			"master",
314			false,
315			false,
316			None,
317			None,
318		)
319		.unwrap();
320
321		// clone1
322
323		let _commit3 =
324			write_commit_file(&clone1, "test2.txt", "foo", "commit3");
325
326		let bytes =
327			fetch(&clone1_dir.into(), "master", None, None).unwrap();
328		assert!(bytes > 0);
329
330		assert_eq!(
331			branch_compare_upstream(&clone1_dir.into(), "master")
332				.unwrap()
333				.behind,
334			1
335		);
336
337		let res = merge_upstream_rebase(&clone1_dir.into(), "master");
338		assert!(res.is_err());
339
340		let state =
341			crate::sync::repo_state(&clone1_dir.into()).unwrap();
342
343		assert_eq!(state, RepoState::Clean);
344
345		let commits = get_commit_msgs(&clone1);
346		assert_eq!(commits, vec![
347			String::from("commit3"),
348			String::from("commit1")
349		]);
350	}
351}