gnostr_asyncgit/sync/
merge.rs1use git2::{BranchType, Commit, MergeOptions, Repository};
2use scopetime::scope_time;
3
4use super::{
5 rebase::{RebaseProgress, RebaseState},
6 RepoPath,
7};
8use crate::{
9 error::{Error, Result},
10 sync::{
11 branch::merge_commit::commit_merge_with_head,
12 rebase::{abort_rebase, continue_rebase, get_rebase_progress},
13 repository::repo,
14 reset_stage, reset_workdir, CommitId,
15 },
16};
17
18pub fn mergehead_ids(repo_path: &RepoPath) -> Result<Vec<CommitId>> {
20 scope_time!("mergehead_ids");
21
22 let mut repo = repo(repo_path)?;
23
24 let mut ids: Vec<CommitId> = Vec::new();
25 repo.mergehead_foreach(|id| {
26 ids.push(CommitId::from(*id));
27 true
28 })?;
29
30 Ok(ids)
31}
32
33pub fn abort_pending_state(repo_path: &RepoPath) -> Result<()> {
38 scope_time!("abort_pending_state");
39
40 let repo = repo(repo_path)?;
41
42 reset_stage(repo_path, "*")?;
43 reset_workdir(repo_path, "*")?;
44
45 repo.cleanup_state()?;
46
47 Ok(())
48}
49
50pub fn merge_branch(repo_path: &RepoPath, branch: &str, branch_type: BranchType) -> Result<()> {
52 scope_time!("merge_branch");
53
54 let repo = repo(repo_path)?;
55
56 merge_branch_repo(&repo, branch, branch_type)?;
57
58 Ok(())
59}
60
61pub fn rebase_progress(repo_path: &RepoPath) -> Result<RebaseProgress> {
63 scope_time!("rebase_progress");
64
65 let repo = repo(repo_path)?;
66
67 get_rebase_progress(&repo)
68}
69
70pub fn continue_pending_rebase(repo_path: &RepoPath) -> Result<RebaseState> {
72 scope_time!("continue_pending_rebase");
73
74 let repo = repo(repo_path)?;
75
76 continue_rebase(&repo)
77}
78
79pub fn abort_pending_rebase(repo_path: &RepoPath) -> Result<()> {
81 scope_time!("abort_pending_rebase");
82
83 let repo = repo(repo_path)?;
84
85 abort_rebase(&repo)
86}
87
88pub fn merge_branch_repo(repo: &Repository, branch: &str, branch_type: BranchType) -> Result<()> {
90 let branch = repo.find_branch(branch, branch_type)?;
91
92 let annotated = repo.reference_to_annotated_commit(&branch.into_reference())?;
93
94 let (analysis, _) = repo.merge_analysis(&[&annotated])?;
95
96 if analysis.is_unborn() {
98 return Err(Error::Generic("head is unborn".into()));
99 }
100
101 let mut opt = MergeOptions::default();
102
103 repo.merge(&[&annotated], Some(&mut opt), None)?;
104
105 Ok(())
106}
107
108pub fn merge_msg(repo_path: &RepoPath) -> Result<String> {
110 scope_time!("merge_msg");
111
112 let repo = repo(repo_path)?;
113 let content = repo.message()?;
114
115 Ok(content)
116}
117
118pub fn merge_commit(repo_path: &RepoPath, msg: &str, ids: &[CommitId]) -> Result<CommitId> {
120 scope_time!("merge_commit");
121
122 let repo = repo(repo_path)?;
123
124 let mut commits: Vec<Commit> = Vec::new();
125
126 for id in ids {
127 commits.push(repo.find_commit((*id).into())?);
128 }
129
130 let id = commit_merge_with_head(&repo, &commits, msg)?;
131
132 Ok(id)
133}
134
135#[cfg(test)]
136mod tests {
137 use pretty_assertions::assert_eq;
138
139 use super::*;
140 use crate::sync::{
141 create_branch,
142 tests::{repo_init, write_commit_file},
143 RepoPath,
144 };
145
146 #[test]
147 fn test_smoke() {
148 let (_td, repo) = repo_init().unwrap();
149 let root = repo.path().parent().unwrap();
150 let repo_path: &RepoPath = &root.as_os_str().to_str().unwrap().into();
151
152 let c1 = write_commit_file(&repo, "test.txt", "test", "commit1");
153
154 create_branch(repo_path, "foo").unwrap();
155
156 write_commit_file(&repo, "test.txt", "test2", "commit2");
157
158 merge_branch(repo_path, "master", BranchType::Local).unwrap();
159
160 let msg = merge_msg(repo_path).unwrap();
161
162 assert_eq!(&msg[0..12], "Merge branch");
163
164 let mergeheads = mergehead_ids(repo_path).unwrap();
165
166 assert_eq!(mergeheads[0], c1);
167 }
168}