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