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