Add rebase option for worktrees

This commit is contained in:
2021-12-29 19:02:42 +01:00
parent 7a2fa7ae3f
commit ef8a57c60e
5 changed files with 445 additions and 1 deletions

View File

@@ -91,6 +91,8 @@ pub enum WorktreeAction {
Fetch(WorktreeFetchArgs),
#[clap(about = "Fetch refs from remotes and update local branches")]
Pull(WorktreePullArgs),
#[clap(about = "Rebase worktree onto default branch")]
Rebase(WorktreeRebaseArgs),
}
#[derive(Parser)]
@@ -137,6 +139,14 @@ pub struct WorktreePullArgs {
pub rebase: bool,
}
#[derive(Parser)]
pub struct WorktreeRebaseArgs {
#[clap(long = "--pull", about = "Perform a pull before rebasing")]
pub pull: bool,
#[clap(long = "--rebase", about = "Perform a rebase when doing a pull")]
pub rebase: bool,
}
pub fn parse() -> Opts {
Opts::parse()
}

View File

@@ -362,6 +362,76 @@ fn main() {
}
}
}
cmd::WorktreeAction::Rebase(args) => {
if args.rebase && !args.pull {
print_error("There is no point in using --rebase without --pull");
process::exit(1);
}
let repo = grm::Repo::open(&cwd, true).unwrap_or_else(|error| {
if error.kind == grm::RepoErrorKind::NotFound {
print_error("Directory does not contain a git repository");
} else {
print_error(&format!("Opening repository failed: {}", error));
}
process::exit(1);
});
if args.pull {
repo.fetchall().unwrap_or_else(|error| {
print_error(&format!("Error fetching remotes: {}", error));
process::exit(1);
});
}
let config =
grm::repo::read_worktree_root_config(&cwd).unwrap_or_else(|error| {
print_error(&format!(
"Failed to read worktree configuration: {}",
error
));
process::exit(1);
});
let worktrees = repo.get_worktrees().unwrap_or_else(|error| {
print_error(&format!("Error getting worktrees: {}", error));
process::exit(1);
});
for worktree in &worktrees {
if args.pull {
if let Some(warning) = worktree
.forward_branch(args.rebase)
.unwrap_or_else(|error| {
print_error(&format!(
"Error updating worktree branch: {}",
error
));
process::exit(1);
})
{
print_warning(&format!("{}: {}", worktree.name(), warning));
}
}
}
for worktree in &worktrees {
if let Some(warning) =
worktree
.rebase_onto_default(&config)
.unwrap_or_else(|error| {
print_error(&format!(
"Error rebasing worktree branch: {}",
error
));
process::exit(1);
})
{
print_warning(&format!("{}: {}", worktree.name(), warning));
} else {
print_success(&format!("{}: Done", worktree.name()));
}
}
}
}
}
}

View File

@@ -219,8 +219,16 @@ impl Worktree {
.map_err(convert_libgit2_error)?;
let committer = rebased_commit.committer();
if rebase.commit(None, &committer, None).is_err() {
// This is effectively adding all files to the index explicitly.
// Normal files are already staged, but changed submodules are not.
let mut index = repo.0.index().map_err(convert_libgit2_error)?;
index
.add_all(["."].iter(), git2::IndexAddOption::CHECK_PATHSPEC, None)
.map_err(convert_libgit2_error)?;
if let Err(error) = rebase.commit(None, &committer, None) {
rebase.abort().map_err(convert_libgit2_error)?;
return Err(convert_libgit2_error(error));
}
}
@@ -251,6 +259,78 @@ impl Worktree {
};
Ok(None)
}
pub fn rebase_onto_default(
&self,
config: &Option<WorktreeRootConfig>,
) -> Result<Option<String>, String> {
let repo = Repo::open(Path::new(&self.name), false)
.map_err(|error| format!("Error opening worktree: {}", error))?;
let guess_default_branch = || {
repo.default_branch()
.map_err(|_| "Could not determine default branch")?
.name()
.map_err(|error| format!("Failed getting default branch name: {}", error))
};
let default_branch_name = match &config {
None => guess_default_branch()?,
Some(config) => match &config.persistent_branches {
None => guess_default_branch()?,
Some(persistent_branches) => {
if persistent_branches.is_empty() {
guess_default_branch()?
} else {
persistent_branches[0].clone()
}
}
},
};
let base_branch = repo.find_local_branch(&default_branch_name)?;
let base_annotated_commit = repo
.0
.find_annotated_commit(base_branch.commit()?.id().0)
.map_err(convert_libgit2_error)?;
let mut rebase = repo
.0
.rebase(
None, // use HEAD
Some(&base_annotated_commit),
None, // figure out the base yourself, libgit2!
Some(&mut git2::RebaseOptions::new()),
)
.map_err(convert_libgit2_error)?;
while let Some(operation) = rebase.next() {
let operation = operation.map_err(convert_libgit2_error)?;
// This is required to preserve the commiter of the rebased
// commits, which is the expected behaviour.
let rebased_commit = repo
.0
.find_commit(operation.id())
.map_err(convert_libgit2_error)?;
let committer = rebased_commit.committer();
// This is effectively adding all files to the index explicitly.
// Normal files are already staged, but changed submodules are not.
let mut index = repo.0.index().map_err(convert_libgit2_error)?;
index
.add_all(["."].iter(), git2::IndexAddOption::CHECK_PATHSPEC, None)
.map_err(convert_libgit2_error)?;
if let Err(error) = rebase.commit(None, &committer, None) {
rebase.abort().map_err(convert_libgit2_error)?;
return Err(convert_libgit2_error(error));
}
}
rebase.finish(None).map_err(convert_libgit2_error)?;
Ok(None)
}
}
impl RepoStatus {