Skip to content

Commit e4c7867

Browse files
author
Stephan Dilly
authored
allow rebase with conflicts (#897)
1 parent 9f8fc6b commit e4c7867

File tree

11 files changed

+411
-50
lines changed

11 files changed

+411
-50
lines changed

asyncgit/src/sync/merge.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
use crate::{
22
error::{Error, Result},
33
sync::{
4-
branch::merge_commit::commit_merge_with_head, reset_stage,
5-
reset_workdir, utils, CommitId,
4+
branch::merge_commit::commit_merge_with_head,
5+
rebase::{
6+
abort_rebase, continue_rebase, get_rebase_progress,
7+
},
8+
reset_stage, reset_workdir, utils, CommitId,
69
},
710
};
811
use git2::{BranchType, Commit, MergeOptions, Repository};
912
use scopetime::scope_time;
1013

14+
use super::rebase::{RebaseProgress, RebaseState};
15+
1116
///
1217
pub fn mergehead_ids(repo_path: &str) -> Result<Vec<CommitId>> {
1318
scope_time!("mergehead_ids");
@@ -51,6 +56,35 @@ pub fn merge_branch(repo_path: &str, branch: &str) -> Result<()> {
5156
Ok(())
5257
}
5358

59+
///
60+
pub fn rebase_progress(repo_path: &str) -> Result<RebaseProgress> {
61+
scope_time!("rebase_progress");
62+
63+
let repo = utils::repo(repo_path)?;
64+
65+
get_rebase_progress(&repo)
66+
}
67+
68+
///
69+
pub fn continue_pending_rebase(
70+
repo_path: &str,
71+
) -> Result<RebaseState> {
72+
scope_time!("continue_pending_rebase");
73+
74+
let repo = utils::repo(repo_path)?;
75+
76+
continue_rebase(&repo)
77+
}
78+
79+
///
80+
pub fn abort_pending_rebase(repo_path: &str) -> Result<()> {
81+
scope_time!("abort_pending_rebase");
82+
83+
let repo = utils::repo(repo_path)?;
84+
85+
abort_rebase(&repo)
86+
}
87+
5488
///
5589
pub fn merge_branch_repo(
5690
repo: &Repository,

asyncgit/src/sync/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ pub use hunks::{reset_hunk, stage_hunk, unstage_hunk};
5858
pub use ignore::add_to_ignore;
5959
pub use logwalker::{LogWalker, LogWalkerFilter};
6060
pub use merge::{
61-
abort_merge, merge_branch, merge_commit, merge_msg, mergehead_ids,
61+
abort_merge, abort_pending_rebase, continue_pending_rebase,
62+
merge_branch, merge_commit, merge_msg, mergehead_ids,
63+
rebase_progress,
6264
};
6365
pub use rebase::rebase_branch;
6466
pub use remotes::{

asyncgit/src/sync/rebase.rs

Lines changed: 200 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use super::CommitId;
1212
pub fn rebase_branch(
1313
repo_path: &str,
1414
branch: &str,
15-
) -> Result<CommitId> {
15+
) -> Result<RebaseState> {
1616
scope_time!("rebase_branch");
1717

1818
let repo = utils::repo(repo_path)?;
@@ -23,13 +23,13 @@ pub fn rebase_branch(
2323
fn rebase_branch_repo(
2424
repo: &Repository,
2525
branch_name: &str,
26-
) -> Result<CommitId> {
26+
) -> Result<RebaseState> {
2727
let branch = repo.find_branch(branch_name, BranchType::Local)?;
2828

2929
let annotated =
3030
repo.reference_to_annotated_commit(&branch.into_reference())?;
3131

32-
conflict_free_rebase(repo, &annotated)
32+
rebase(repo, &annotated)
3333
}
3434

3535
/// rebase attempt which aborts and undo's rebase if any conflict appears
@@ -66,16 +66,133 @@ pub fn conflict_free_rebase(
6666
})
6767
}
6868

69+
///
70+
#[derive(PartialEq, Debug)]
71+
pub enum RebaseState {
72+
///
73+
Finished,
74+
///
75+
Conflicted,
76+
}
77+
78+
/// rebase
79+
pub fn rebase(
80+
repo: &git2::Repository,
81+
commit: &git2::AnnotatedCommit,
82+
) -> Result<RebaseState> {
83+
let mut rebase = repo.rebase(None, Some(commit), None, None)?;
84+
let signature =
85+
crate::sync::commit::signature_allow_undefined_name(repo)?;
86+
87+
while let Some(op) = rebase.next() {
88+
let _op = op?;
89+
// dbg!(op.id());
90+
91+
if repo.index()?.has_conflicts() {
92+
return Ok(RebaseState::Conflicted);
93+
}
94+
95+
rebase.commit(None, &signature, None)?;
96+
}
97+
98+
if repo.index()?.has_conflicts() {
99+
return Ok(RebaseState::Conflicted);
100+
}
101+
102+
rebase.finish(Some(&signature))?;
103+
104+
Ok(RebaseState::Finished)
105+
}
106+
107+
/// continue pending rebase
108+
pub fn continue_rebase(
109+
repo: &git2::Repository,
110+
) -> Result<RebaseState> {
111+
let mut rebase = repo.open_rebase(None)?;
112+
let signature =
113+
crate::sync::commit::signature_allow_undefined_name(repo)?;
114+
115+
if repo.index()?.has_conflicts() {
116+
return Ok(RebaseState::Conflicted);
117+
}
118+
119+
// try commit current rebase step
120+
if !repo.index()?.is_empty() {
121+
rebase.commit(None, &signature, None)?;
122+
}
123+
124+
while let Some(op) = rebase.next() {
125+
let _op = op?;
126+
// dbg!(op.id());
127+
128+
if repo.index()?.has_conflicts() {
129+
return Ok(RebaseState::Conflicted);
130+
}
131+
132+
rebase.commit(None, &signature, None)?;
133+
}
134+
135+
if repo.index()?.has_conflicts() {
136+
return Ok(RebaseState::Conflicted);
137+
}
138+
139+
rebase.finish(Some(&signature))?;
140+
141+
Ok(RebaseState::Finished)
142+
}
143+
144+
///
145+
#[derive(PartialEq, Debug)]
146+
pub struct RebaseProgress {
147+
///
148+
pub steps: usize,
149+
///
150+
pub current: usize,
151+
///
152+
pub current_commit: Option<CommitId>,
153+
}
154+
155+
///
156+
pub fn get_rebase_progress(
157+
repo: &git2::Repository,
158+
) -> Result<RebaseProgress> {
159+
let mut rebase = repo.open_rebase(None)?;
160+
161+
let current_commit: Option<CommitId> = rebase
162+
.operation_current()
163+
.and_then(|idx| rebase.nth(idx))
164+
.map(|op| op.id().into());
165+
166+
let progress = RebaseProgress {
167+
steps: rebase.len(),
168+
current: rebase.operation_current().unwrap_or_default(),
169+
current_commit,
170+
};
171+
172+
Ok(progress)
173+
}
174+
175+
///
176+
pub fn abort_rebase(repo: &git2::Repository) -> Result<()> {
177+
let mut rebase = repo.open_rebase(None)?;
178+
179+
rebase.abort()?;
180+
181+
Ok(())
182+
}
183+
69184
#[cfg(test)]
70-
mod tests {
185+
mod test_conflict_free_rebase {
71186
use crate::sync::{
72187
checkout_branch, create_branch,
73-
rebase::rebase_branch,
188+
rebase::{rebase_branch, RebaseState},
74189
repo_state,
75190
tests::{repo_init, write_commit_file},
76-
CommitId, RepoState,
191+
utils, CommitId, RepoState,
77192
};
78-
use git2::Repository;
193+
use git2::{BranchType, Repository};
194+
195+
use super::conflict_free_rebase;
79196

80197
fn parent_ids(repo: &Repository, c: CommitId) -> Vec<CommitId> {
81198
let foo = repo
@@ -88,6 +205,23 @@ mod tests {
88205
foo
89206
}
90207

208+
///
209+
fn test_rebase_branch_repo(
210+
repo_path: &str,
211+
branch_name: &str,
212+
) -> CommitId {
213+
let repo = utils::repo(repo_path).unwrap();
214+
215+
let branch =
216+
repo.find_branch(branch_name, BranchType::Local).unwrap();
217+
218+
let annotated = repo
219+
.reference_to_annotated_commit(&branch.into_reference())
220+
.unwrap();
221+
222+
conflict_free_rebase(&repo, &annotated).unwrap()
223+
}
224+
91225
#[test]
92226
fn test_smoke() {
93227
let (_td, repo) = repo_init().unwrap();
@@ -111,7 +245,7 @@ mod tests {
111245

112246
checkout_branch(repo_path, "refs/heads/foo").unwrap();
113247

114-
let r = rebase_branch(repo_path, "master").unwrap();
248+
let r = test_rebase_branch_repo(repo_path, "master");
115249

116250
assert_eq!(parent_ids(&repo, r), vec![c3]);
117251
}
@@ -136,7 +270,64 @@ mod tests {
136270

137271
let res = rebase_branch(repo_path, "master");
138272

139-
assert!(res.is_err());
273+
assert!(matches!(res.unwrap(), RebaseState::Conflicted));
274+
275+
assert_eq!(repo_state(repo_path).unwrap(), RepoState::Rebase);
276+
}
277+
}
278+
279+
#[cfg(test)]
280+
mod test_rebase {
281+
use crate::sync::{
282+
checkout_branch, create_branch,
283+
rebase::{
284+
abort_rebase, get_rebase_progress, RebaseProgress,
285+
RebaseState,
286+
},
287+
rebase_branch, repo_state,
288+
tests::{repo_init, write_commit_file},
289+
RepoState,
290+
};
291+
292+
#[test]
293+
fn test_conflicted_abort() {
294+
let (_td, repo) = repo_init().unwrap();
295+
let root = repo.path().parent().unwrap();
296+
let repo_path = root.as_os_str().to_str().unwrap();
297+
298+
write_commit_file(&repo, "test.txt", "test1", "commit1");
299+
300+
create_branch(repo_path, "foo").unwrap();
301+
302+
let c =
303+
write_commit_file(&repo, "test.txt", "test2", "commit2");
304+
305+
checkout_branch(repo_path, "refs/heads/master").unwrap();
306+
307+
write_commit_file(&repo, "test.txt", "test3", "commit3");
308+
309+
checkout_branch(repo_path, "refs/heads/foo").unwrap();
310+
311+
assert!(get_rebase_progress(&repo).is_err());
312+
313+
// rebase
314+
315+
let r = rebase_branch(repo_path, "master").unwrap();
316+
317+
assert_eq!(r, RebaseState::Conflicted);
318+
assert_eq!(repo_state(repo_path).unwrap(), RepoState::Rebase);
319+
assert_eq!(
320+
get_rebase_progress(&repo).unwrap(),
321+
RebaseProgress {
322+
current: 0,
323+
steps: 1,
324+
current_commit: Some(c)
325+
}
326+
);
327+
328+
// abort
329+
330+
abort_rebase(&repo).unwrap();
140331

141332
assert_eq!(repo_state(repo_path).unwrap(), RepoState::Clean);
142333
}

asyncgit/src/sync/state.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ pub enum RepoState {
1010
///
1111
Merge,
1212
///
13+
Rebase,
14+
///
1315
Other,
1416
}
1517

@@ -18,6 +20,7 @@ impl From<RepositoryState> for RepoState {
1820
match state {
1921
RepositoryState::Clean => Self::Clean,
2022
RepositoryState::Merge => Self::Merge,
23+
RepositoryState::RebaseMerge => Self::Rebase,
2124
_ => Self::Other,
2225
}
2326
}
@@ -29,5 +32,9 @@ pub fn repo_state(repo_path: &str) -> Result<RepoState> {
2932

3033
let repo = utils::repo(repo_path)?;
3134

32-
Ok(repo.state().into())
35+
let state = repo.state();
36+
37+
// dbg!(&state);
38+
39+
Ok(state.into())
3340
}

src/app.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,10 @@ impl App {
821821
self.status_tab.abort_merge();
822822
flags.insert(NeedsUpdate::ALL);
823823
}
824+
Action::AbortRebase => {
825+
self.status_tab.abort_rebase();
826+
flags.insert(NeedsUpdate::ALL);
827+
}
824828
};
825829

826830
Ok(())

0 commit comments

Comments
 (0)