use std::sync::atomic::{AtomicBool, Ordering};
use super::{Operation, Options, Replay, Status};
use crate::{Commit, ObjectId, Objects, Signature};
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct RebaseOptions {
pub replay: Options,
pub max_commits: usize,
}
impl Default for RebaseOptions {
fn default() -> Self {
Self {
replay: Options::default(),
max_commits: 10_000,
}
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct RebasedCommit {
pub source: ObjectId,
pub destination: ObjectId,
pub replay: Replay,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct Rebase {
pub onto: ObjectId,
pub tip: ObjectId,
pub completed: Vec<RebasedCommit>,
pub stopped: Option<Replay>,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum RebaseError {
#[error("rebase commit count exceeds configured bound")]
Limit,
#[error("rebase cancelled")]
Cancelled,
#[error("reading rebase commit")]
Read(#[from] crate::ReadCommitError),
#[error("replaying rebase input")]
Replay(#[from] super::Error),
}
impl Objects {
pub fn rebase(
&self,
commits: &[ObjectId],
onto: ObjectId,
committer: Signature,
options: RebaseOptions,
cancel: &AtomicBool,
) -> Result<Rebase, RebaseError> {
run_rebase(self, commits, onto, committer, options, cancel, None)
}
pub fn rebase_with_driver(
&self,
commits: &[ObjectId],
onto: ObjectId,
committer: Signature,
options: RebaseOptions,
cancel: &AtomicBool,
driver: &mut dyn crate::merge::TreeDriver,
) -> Result<Rebase, RebaseError> {
run_rebase(
self,
commits,
onto,
committer,
options,
cancel,
Some(driver),
)
}
}
fn run_rebase<'a, 'd>(
objects: &'a Objects,
commits: &[ObjectId],
onto: ObjectId,
committer: Signature,
options: RebaseOptions,
cancel: &'a AtomicBool,
mut driver: Option<&'a mut (dyn crate::merge::TreeDriver + 'd)>,
) -> Result<Rebase, RebaseError> {
if commits.len() > options.max_commits {
return Err(RebaseError::Limit);
}
let mut current: Commit = objects.read_commit(onto)?;
let mut memory = crate::merge::ReplayTrees::default();
let mut result = Rebase {
onto,
tip: onto,
completed: Vec::new(),
stopped: None,
};
for &source in commits {
if cancel.load(Ordering::Relaxed) {
return Err(RebaseError::Cancelled);
}
let original = objects.read_commit(source)?;
let operation = Operation {
source,
original: &original,
onto: ¤t,
committer: committer.clone(),
options: options.replay,
reverse: false,
};
let replay = operation.compute(objects, cancel, &mut memory, driver.as_deref_mut())?;
if matches!(replay.status, Status::Conflicted | Status::Empty(_)) {
result.stopped = Some(replay);
return Ok(result);
}
if let Some(commit) = &replay.commit {
current = commit.clone();
}
result.tip = current.id();
result.completed.push(RebasedCommit {
source,
destination: result.tip,
replay,
});
}
Ok(result)
}