Skip to content
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

[#10] Add rebase on top of default branch #15

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
139 changes: 119 additions & 20 deletions src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
//
// SPDX-License-Identifier: MPL-2.0

use git2::RemoteCallbacks;
use git2::{BranchType, FetchOptions, PushOptions, Repository, ResetType, Signature};
use git2::{Rebase, RemoteCallbacks};
use std::collections::hash_map::DefaultHasher;
use std::fs::{create_dir, remove_dir_all};
use std::hash::{Hash, Hasher};
Expand Down Expand Up @@ -178,14 +178,43 @@ pub enum SetupUpdateBranchError {
FindDefaultBranch(git2::Error),
#[error("Error peeling to default branch commit: {0}")]
PeelDefaultBranchCommit(git2::Error),
#[error("Error peeling to update branch commit: {0}")]
PeelUpdateBranchCommit(git2::Error),
#[error("Error creating a new branch pointing to default branch commit: {0}")]
BranchToUpdateBranchWithDefault(git2::Error),
#[error("Error setting head to update branch: {0}")]
SetUpdateBranchHead(git2::Error),
#[error("There are human commits in the update branch")]
HumanCommitsInUpdateBranch,
#[error("Error setting head to default branch: {0}")]
SetDefaultBranchHead(git2::Error),
#[error("Error resetting to default branch commit: {0}")]
ResetToDefaultBranchCommit(git2::Error),
#[error("Error initializing rebase: {0}")]
InitializeRebase(git2::Error),
#[error("Error peeling to local update branch commit: {0}")]
PeelLocalUpdateBranchCommit(git2::Error),
#[error("Error finding annotated commit for update branch commit: {0}")]
FindAnnotatedUpdateBranchCommit(git2::Error),
#[error("Error creating signature for rebase commits: {0}")]
SigningForRebaseCommits(git2::Error),
#[error("Error finding annotated commit for default branch commit: {0}")]
FindAnnotatedDefaultBranchCommit(git2::Error),
#[error("Error getting next rebase patch: {0}")]
RebaseNext(git2::Error),
#[error("Error committing rebase patch: {0}")]
RebaseCommit(git2::Error),
#[error("Error finishing rebase: {0}")]
FinishRebase(git2::Error),
#[error("Error getting HEAD: {0}")]
GetHead(git2::Error),
#[error("Error peeling HEAD to commit: {0}")]
PeelHead(git2::Error),
#[error("Error creating a new branch pointing to remote update branch: {0}")]
BranchToUpdateBranchWithRemoteBranch(git2::Error),
}

fn safe_abort(rebase: &mut Rebase) {
match rebase.abort() {
Err(e) => error!("Rebase abort failed: {}", e),
_ => {}
}
}

pub fn setup_update_branch(
Expand All @@ -197,18 +226,6 @@ pub fn setup_update_branch(
BranchType::Remote,
);

if let Ok(b) = update_branch {
if b.into_reference()
.peel_to_commit()
.map_err(SetupUpdateBranchError::PeelUpdateBranchCommit)?
.author()
.email()
!= Some(&settings.author.email)
{
return Err(SetupUpdateBranchError::HumanCommitsInUpdateBranch);
}
}

let default_branch_commit = repo
.find_branch(
&format!("origin/{}", &settings.default_branch),
Expand All @@ -219,9 +236,91 @@ pub fn setup_update_branch(
.peel_to_commit()
.map_err(SetupUpdateBranchError::PeelDefaultBranchCommit)?;

// TODO: handle errors we care about here?
repo.branch(&settings.update_branch, &default_branch_commit, true)
.map_err(SetupUpdateBranchError::BranchToUpdateBranchWithDefault)?;
match update_branch {
Err(_) => {
// no update branch exists, creating new one from default
// TODO: handle errors we care about here?
repo.branch(&settings.update_branch, &default_branch_commit, true)
.map_err(SetupUpdateBranchError::BranchToUpdateBranchWithDefault)?;
}
Ok(remote_update_branch) => {
// update branch exists, we should try to:
// 1. rebase update branch on top of default

let update_branch_commit = &remote_update_branch
.into_reference()
.peel_to_commit()
.map_err(SetupUpdateBranchError::PeelLocalUpdateBranchCommit)?;

let update_annotated_commit = repo
.find_annotated_commit(update_branch_commit.id())
.map_err(SetupUpdateBranchError::FindAnnotatedUpdateBranchCommit)?;

let default_annotated_commit =
repo.find_annotated_commit(default_branch_commit.id())
.map_err(SetupUpdateBranchError::FindAnnotatedDefaultBranchCommit)?;

let mut rebase = repo
.rebase(
Some(&update_annotated_commit),
None,
Some(&default_annotated_commit),
None,
)
.map_err(SetupUpdateBranchError::InitializeRebase)?;

let committer = Signature::now(&settings.author.name, &settings.author.email)
.map_err(SetupUpdateBranchError::SigningForRebaseCommits)?;

let checkout_to_default = || -> Result<(), SetupUpdateBranchError> {
repo.set_head(&format!("refs/heads/{}", settings.default_branch))
.map_err(SetupUpdateBranchError::SetDefaultBranchHead)?;

repo.reset(default_branch_commit.as_object(), ResetType::Hard, None)
.map_err(SetupUpdateBranchError::ResetToDefaultBranchCommit)?;
Ok(())
};

while let Some(op) = rebase.next() {
match op {
Ok(_) => {}
Err(e) => {
safe_abort(&mut rebase);
checkout_to_default()?;
return Err(SetupUpdateBranchError::RebaseNext(e));
}
}

match rebase.commit(None, &committer, None) {
Ok(_) => {}
Err(e) => {
safe_abort(&mut rebase);
checkout_to_default()?;
return Err(SetupUpdateBranchError::RebaseCommit(e));
}
}
}

match rebase.finish(None) {
Ok(_) => {}
Err(e) => {
checkout_to_default()?;
return Err(SetupUpdateBranchError::FinishRebase(e));
}
}

let head = repo.head().map_err(SetupUpdateBranchError::GetHead)?;

repo.branch(
&settings.update_branch,
&head
.peel_to_commit()
.map_err(SetupUpdateBranchError::PeelHead)?,
true,
)
.map_err(SetupUpdateBranchError::BranchToUpdateBranchWithRemoteBranch)?;
}
};

repo.set_head(&format!("refs/heads/{}", settings.update_branch))
.map_err(SetupUpdateBranchError::SetUpdateBranchHead)?;
Expand Down