1use 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
14pub 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 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 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 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 let bytes =
142 fetch(&clone1_dir.into(), "master", None, None).unwrap();
143 assert!(bytes > 0);
144
145 assert_eq!(
147 branch_compare_upstream(&clone1_dir.into(), "master")
148 .unwrap()
149 .behind,
150 1
151 );
152
153 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 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 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 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 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 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 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 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}