Skip to content

Commit

Permalink
feat(branch): allow delete of current branch
Browse files Browse the repository at this point in the history
Previously, any attempt to delete the current branch was forbidden. With
this change, deleting the current branch is allowed, but with some
additional constraints versus deleting other, non-current branches. Namely:

- The current branch must have a parent branch configured
  (`branch.<branch>.stgit.parentbranch`).
- The worktree must be clean.

The parent branch of the current branch will be checked-out upon
deleting the current branch.
  • Loading branch information
jpgrayson committed Feb 5, 2024
1 parent 3296387 commit 8a38c12
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 17 deletions.
61 changes: 49 additions & 12 deletions src/cmd/branch/delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ use crate::{
branchloc::BranchLocator,
ext::RepositoryExtended,
stack::{InitializationPolicy, Stack, StackStateAccess},
stupid::Stupid,
};

use super::get_stgit_parent;

pub(super) fn command() -> clap::Command {
clap::Command::new("--delete")
.short_flag('D')
.override_usage(super::super::make_usage(
"stg branch --delete",
&["[--force] <branch>"],
&["[--force] [<branch>]"],
))
.about("Delete a branch")
.long_about(
Expand All @@ -24,13 +27,16 @@ pub(super) fn command() -> clap::Command {
The branch will not be deleted if there are any patches remaining unless \
the '--force' option is provided.\n\
\n\
If the current branch is selected for deletion, its parent branch must be \
configured and the worktree must be clean. The parent branch will be \
checked-out after the current branch is deleted.\n\
\n\
A protected branch may not be deleted; it must be unprotected first.",
)
.arg(
clap::Arg::new("branch-any")
.help("Branch to delete")
.value_name("branch")
.required(true)
.value_parser(clap::value_parser!(BranchLocator)),
)
.arg(
Expand All @@ -42,25 +48,49 @@ pub(super) fn command() -> clap::Command {
}

pub(super) fn dispatch(repo: &gix::Repository, matches: &clap::ArgMatches) -> Result<()> {
let target_branch = matches
.get_one::<BranchLocator>("branch-any")
.expect("required argument")
.resolve(repo)?;
let target_branchname = target_branch.get_branch_partial_name()?;
let (target_branch, target_branchname) = if let Some(branch_loc) = matches.get_one::<BranchLocator>("branch-any") {
let branch = branch_loc.resolve(repo)?;
let branchname = branch.get_branch_partial_name()?;
(branch, branchname)
} else if let Ok(branch) = repo.get_current_branch() {
let branchname = branch.get_branch_partial_name()?;
(branch, branchname)
} else {
return Err(anyhow!("no target branch specified and no current branch"));
};

let current_branch = repo.get_current_branch().ok();
let current_branchname = current_branch
.as_ref()
.and_then(|branch| branch.get_branch_partial_name().ok());
if Some(&target_branchname) == current_branchname.as_ref() {
return Err(anyhow!("cannot delete the current branch"));
}
let config_snapshot = repo.config_snapshot();
let stupid = repo.stupid();

let switch_to_branch = if Some(&target_branchname) == current_branchname.as_ref() {
if let Some(parent_branch) = get_stgit_parent(&config_snapshot, &target_branchname) {
let statuses = stupid.statuses(None)?;
if let Err(e) = statuses
.check_worktree_clean()
.and_then(|_| statuses.check_conflicts())
{
return Err(anyhow!("cannot delete the current branch: {e}"));
}
Some(parent_branch)
} else {
return Err(anyhow!(
"cannot delete the current branch without a known parent branch"
));
}
} else {
None
};

if let Ok(stack) = Stack::from_branch(
repo,
target_branch.clone(),
InitializationPolicy::RequireInitialized,
) {
if stack.is_protected(&repo.config_snapshot()) {
if stack.is_protected(&config_snapshot) {
return Err(anyhow!("delete not permitted: this branch is protected"));
} else if !matches.get_flag("force") && stack.all_patches().count() > 0 {
return Err(anyhow!(
Expand All @@ -70,11 +100,18 @@ pub(super) fn dispatch(repo: &gix::Repository, matches: &clap::ArgMatches) -> Re
stack.deinitialize()?;
}

if let Some(branch_name) = switch_to_branch {
stupid
.checkout(&branch_name)
.context("switching to parent branch")?;
}

target_branch.delete()?;

let mut local_config_file = repo.local_config_file()?;
local_config_file.remove_section("branch", Some(target_branchname.as_ref().into()));
repo.write_local_config(local_config_file).context("writing local config file")?;
repo.write_local_config(local_config_file)
.context("writing local config file")?;

Ok(())
}
40 changes: 35 additions & 5 deletions t/t1005-branch-delete.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,44 @@ test_expect_success 'Make sure the branch files were deleted' '
[ -z "$(find .git -type f | grep master | grep -v origin/master | tee /dev/stderr)" ]
'

test_expect_success 'Attempt to delete current branch' '
command_error stg branch --delete $(stg branch) 2>err &&
grep -e "cannot delete the current branch" err
test_expect_success 'Delete current branch' '
stg branch -c baz &&
stg branch --delete &&
stg branch >out &&
cat >expected <<-\EOF &&
foo
EOF
test_cmp expected out
'

test_expect_success 'Attempt delete current branch with patches' '
stg branch -c baz2 &&
stg new -m p0 &&
command_error stg branch --delete 2>err &&
grep "delete not permitted: the series still contains patches" err &&
stg branch --delete --force &&
stg branch >out &&
cat >expected <<-\EOF &&
foo
EOF
test_cmp expected out
'

test_expect_success 'Attempt delete current branch with dirty worktree' '
stg branch -c baz2 &&
echo content >>p0.t &&
command_error stg branch --delete 2>err &&
grep "cannot delete the current branch: worktree not clean" err &&
git checkout -- p0.t &&
stg branch --delete &&
stg branch >out &&
cat >expected <<-\EOF &&
foo
EOF
test_cmp expected out
'

test_expect_success 'Invalid num args to delete' '
general_error stg branch --delete 2>err &&
grep -e "the following required arguments were not provided" err &&
general_error stg branch --delete foo extra 2>err &&
grep -e "unexpected argument .extra." err
'
Expand Down

0 comments on commit 8a38c12

Please sign in to comment.