Skip to content

allow rebase with conflicts (WIP) #897

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Sep 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions asyncgit/src/sync/merge.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
use crate::{
error::{Error, Result},
sync::{
branch::merge_commit::commit_merge_with_head, reset_stage,
reset_workdir, utils, CommitId,
branch::merge_commit::commit_merge_with_head,
rebase::{
abort_rebase, continue_rebase, get_rebase_progress,
},
reset_stage, reset_workdir, utils, CommitId,
},
};
use git2::{BranchType, Commit, MergeOptions, Repository};
use scopetime::scope_time;

use super::rebase::{RebaseProgress, RebaseState};

///
pub fn mergehead_ids(repo_path: &str) -> Result<Vec<CommitId>> {
scope_time!("mergehead_ids");
Expand Down Expand Up @@ -51,6 +56,35 @@ pub fn merge_branch(repo_path: &str, branch: &str) -> Result<()> {
Ok(())
}

///
pub fn rebase_progress(repo_path: &str) -> Result<RebaseProgress> {
scope_time!("rebase_progress");

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

get_rebase_progress(&repo)
}

///
pub fn continue_pending_rebase(
repo_path: &str,
) -> Result<RebaseState> {
scope_time!("continue_pending_rebase");

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

continue_rebase(&repo)
}

///
pub fn abort_pending_rebase(repo_path: &str) -> Result<()> {
scope_time!("abort_pending_rebase");

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

abort_rebase(&repo)
}

///
pub fn merge_branch_repo(
repo: &Repository,
Expand Down
4 changes: 3 additions & 1 deletion asyncgit/src/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ pub use hunks::{reset_hunk, stage_hunk, unstage_hunk};
pub use ignore::add_to_ignore;
pub use logwalker::{LogWalker, LogWalkerFilter};
pub use merge::{
abort_merge, merge_branch, merge_commit, merge_msg, mergehead_ids,
abort_merge, abort_pending_rebase, continue_pending_rebase,
merge_branch, merge_commit, merge_msg, mergehead_ids,
rebase_progress,
};
pub use rebase::rebase_branch;
pub use remotes::{
Expand Down
209 changes: 200 additions & 9 deletions asyncgit/src/sync/rebase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use super::CommitId;
pub fn rebase_branch(
repo_path: &str,
branch: &str,
) -> Result<CommitId> {
) -> Result<RebaseState> {
scope_time!("rebase_branch");

let repo = utils::repo(repo_path)?;
Expand All @@ -23,13 +23,13 @@ pub fn rebase_branch(
fn rebase_branch_repo(
repo: &Repository,
branch_name: &str,
) -> Result<CommitId> {
) -> Result<RebaseState> {
let branch = repo.find_branch(branch_name, BranchType::Local)?;

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

conflict_free_rebase(repo, &annotated)
rebase(repo, &annotated)
}

/// rebase attempt which aborts and undo's rebase if any conflict appears
Expand Down Expand Up @@ -66,16 +66,133 @@ pub fn conflict_free_rebase(
})
}

///
#[derive(PartialEq, Debug)]
pub enum RebaseState {
///
Finished,
///
Conflicted,
}

/// rebase
pub fn rebase(
repo: &git2::Repository,
commit: &git2::AnnotatedCommit,
) -> Result<RebaseState> {
let mut rebase = repo.rebase(None, Some(commit), None, None)?;
let signature =
crate::sync::commit::signature_allow_undefined_name(repo)?;

while let Some(op) = rebase.next() {
let _op = op?;
// dbg!(op.id());

if repo.index()?.has_conflicts() {
return Ok(RebaseState::Conflicted);
}

rebase.commit(None, &signature, None)?;
}

if repo.index()?.has_conflicts() {
return Ok(RebaseState::Conflicted);
}

rebase.finish(Some(&signature))?;

Ok(RebaseState::Finished)
}

/// continue pending rebase
pub fn continue_rebase(
repo: &git2::Repository,
) -> Result<RebaseState> {
let mut rebase = repo.open_rebase(None)?;
let signature =
crate::sync::commit::signature_allow_undefined_name(repo)?;

if repo.index()?.has_conflicts() {
return Ok(RebaseState::Conflicted);
}

// try commit current rebase step
if !repo.index()?.is_empty() {
rebase.commit(None, &signature, None)?;
}

while let Some(op) = rebase.next() {
let _op = op?;
// dbg!(op.id());

if repo.index()?.has_conflicts() {
return Ok(RebaseState::Conflicted);
}

rebase.commit(None, &signature, None)?;
}

if repo.index()?.has_conflicts() {
return Ok(RebaseState::Conflicted);
}

rebase.finish(Some(&signature))?;

Ok(RebaseState::Finished)
}

///
#[derive(PartialEq, Debug)]
pub struct RebaseProgress {
///
pub steps: usize,
///
pub current: usize,
///
pub current_commit: Option<CommitId>,
}

///
pub fn get_rebase_progress(
repo: &git2::Repository,
) -> Result<RebaseProgress> {
let mut rebase = repo.open_rebase(None)?;

let current_commit: Option<CommitId> = rebase
.operation_current()
.and_then(|idx| rebase.nth(idx))
.map(|op| op.id().into());

let progress = RebaseProgress {
steps: rebase.len(),
current: rebase.operation_current().unwrap_or_default(),
current_commit,
};

Ok(progress)
}

///
pub fn abort_rebase(repo: &git2::Repository) -> Result<()> {
let mut rebase = repo.open_rebase(None)?;

rebase.abort()?;

Ok(())
}

#[cfg(test)]
mod tests {
mod test_conflict_free_rebase {
use crate::sync::{
checkout_branch, create_branch,
rebase::rebase_branch,
rebase::{rebase_branch, RebaseState},
repo_state,
tests::{repo_init, write_commit_file},
CommitId, RepoState,
utils, CommitId, RepoState,
};
use git2::Repository;
use git2::{BranchType, Repository};

use super::conflict_free_rebase;

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

///
fn test_rebase_branch_repo(
repo_path: &str,
branch_name: &str,
) -> CommitId {
let repo = utils::repo(repo_path).unwrap();

let branch =
repo.find_branch(branch_name, BranchType::Local).unwrap();

let annotated = repo
.reference_to_annotated_commit(&branch.into_reference())
.unwrap();

conflict_free_rebase(&repo, &annotated).unwrap()
}

#[test]
fn test_smoke() {
let (_td, repo) = repo_init().unwrap();
Expand All @@ -111,7 +245,7 @@ mod tests {

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

let r = rebase_branch(repo_path, "master").unwrap();
let r = test_rebase_branch_repo(repo_path, "master");

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

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

assert!(res.is_err());
assert!(matches!(res.unwrap(), RebaseState::Conflicted));

assert_eq!(repo_state(repo_path).unwrap(), RepoState::Rebase);
}
}

#[cfg(test)]
mod test_rebase {
use crate::sync::{
checkout_branch, create_branch,
rebase::{
abort_rebase, get_rebase_progress, RebaseProgress,
RebaseState,
},
rebase_branch, repo_state,
tests::{repo_init, write_commit_file},
RepoState,
};

#[test]
fn test_conflicted_abort() {
let (_td, repo) = repo_init().unwrap();
let root = repo.path().parent().unwrap();
let repo_path = root.as_os_str().to_str().unwrap();

write_commit_file(&repo, "test.txt", "test1", "commit1");

create_branch(repo_path, "foo").unwrap();

let c =
write_commit_file(&repo, "test.txt", "test2", "commit2");

checkout_branch(repo_path, "refs/heads/master").unwrap();

write_commit_file(&repo, "test.txt", "test3", "commit3");

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

assert!(get_rebase_progress(&repo).is_err());

// rebase

let r = rebase_branch(repo_path, "master").unwrap();

assert_eq!(r, RebaseState::Conflicted);
assert_eq!(repo_state(repo_path).unwrap(), RepoState::Rebase);
assert_eq!(
get_rebase_progress(&repo).unwrap(),
RebaseProgress {
current: 0,
steps: 1,
current_commit: Some(c)
}
);

// abort

abort_rebase(&repo).unwrap();

assert_eq!(repo_state(repo_path).unwrap(), RepoState::Clean);
}
Expand Down
9 changes: 8 additions & 1 deletion asyncgit/src/sync/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub enum RepoState {
///
Merge,
///
Rebase,
///
Other,
}

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

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

Ok(repo.state().into())
let state = repo.state();

// dbg!(&state);

Ok(state.into())
}
4 changes: 4 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,10 @@ impl App {
self.status_tab.abort_merge();
flags.insert(NeedsUpdate::ALL);
}
Action::AbortRebase => {
self.status_tab.abort_rebase();
flags.insert(NeedsUpdate::ALL);
}
};

Ok(())
Expand Down
Loading