Skip to content

Commit 8a38c12

Browse files
committed
feat(branch): allow delete of current branch
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.
1 parent 3296387 commit 8a38c12

File tree

2 files changed

+84
-17
lines changed

2 files changed

+84
-17
lines changed

src/cmd/branch/delete.rs

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@ use crate::{
88
branchloc::BranchLocator,
99
ext::RepositoryExtended,
1010
stack::{InitializationPolicy, Stack, StackStateAccess},
11+
stupid::Stupid,
1112
};
1213

14+
use super::get_stgit_parent;
15+
1316
pub(super) fn command() -> clap::Command {
1417
clap::Command::new("--delete")
1518
.short_flag('D')
1619
.override_usage(super::super::make_usage(
1720
"stg branch --delete",
18-
&["[--force] <branch>"],
21+
&["[--force] [<branch>]"],
1922
))
2023
.about("Delete a branch")
2124
.long_about(
@@ -24,13 +27,16 @@ pub(super) fn command() -> clap::Command {
2427
The branch will not be deleted if there are any patches remaining unless \
2528
the '--force' option is provided.\n\
2629
\n\
30+
If the current branch is selected for deletion, its parent branch must be \
31+
configured and the worktree must be clean. The parent branch will be \
32+
checked-out after the current branch is deleted.\n\
33+
\n\
2734
A protected branch may not be deleted; it must be unprotected first.",
2835
)
2936
.arg(
3037
clap::Arg::new("branch-any")
3138
.help("Branch to delete")
3239
.value_name("branch")
33-
.required(true)
3440
.value_parser(clap::value_parser!(BranchLocator)),
3541
)
3642
.arg(
@@ -42,25 +48,49 @@ pub(super) fn command() -> clap::Command {
4248
}
4349

4450
pub(super) fn dispatch(repo: &gix::Repository, matches: &clap::ArgMatches) -> Result<()> {
45-
let target_branch = matches
46-
.get_one::<BranchLocator>("branch-any")
47-
.expect("required argument")
48-
.resolve(repo)?;
49-
let target_branchname = target_branch.get_branch_partial_name()?;
51+
let (target_branch, target_branchname) = if let Some(branch_loc) = matches.get_one::<BranchLocator>("branch-any") {
52+
let branch = branch_loc.resolve(repo)?;
53+
let branchname = branch.get_branch_partial_name()?;
54+
(branch, branchname)
55+
} else if let Ok(branch) = repo.get_current_branch() {
56+
let branchname = branch.get_branch_partial_name()?;
57+
(branch, branchname)
58+
} else {
59+
return Err(anyhow!("no target branch specified and no current branch"));
60+
};
61+
5062
let current_branch = repo.get_current_branch().ok();
5163
let current_branchname = current_branch
5264
.as_ref()
5365
.and_then(|branch| branch.get_branch_partial_name().ok());
54-
if Some(&target_branchname) == current_branchname.as_ref() {
55-
return Err(anyhow!("cannot delete the current branch"));
56-
}
66+
let config_snapshot = repo.config_snapshot();
67+
let stupid = repo.stupid();
68+
69+
let switch_to_branch = if Some(&target_branchname) == current_branchname.as_ref() {
70+
if let Some(parent_branch) = get_stgit_parent(&config_snapshot, &target_branchname) {
71+
let statuses = stupid.statuses(None)?;
72+
if let Err(e) = statuses
73+
.check_worktree_clean()
74+
.and_then(|_| statuses.check_conflicts())
75+
{
76+
return Err(anyhow!("cannot delete the current branch: {e}"));
77+
}
78+
Some(parent_branch)
79+
} else {
80+
return Err(anyhow!(
81+
"cannot delete the current branch without a known parent branch"
82+
));
83+
}
84+
} else {
85+
None
86+
};
5787

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

103+
if let Some(branch_name) = switch_to_branch {
104+
stupid
105+
.checkout(&branch_name)
106+
.context("switching to parent branch")?;
107+
}
108+
73109
target_branch.delete()?;
74110

75111
let mut local_config_file = repo.local_config_file()?;
76112
local_config_file.remove_section("branch", Some(target_branchname.as_ref().into()));
77-
repo.write_local_config(local_config_file).context("writing local config file")?;
113+
repo.write_local_config(local_config_file)
114+
.context("writing local config file")?;
78115

79116
Ok(())
80117
}

t/t1005-branch-delete.sh

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,44 @@ test_expect_success 'Make sure the branch files were deleted' '
4747
[ -z "$(find .git -type f | grep master | grep -v origin/master | tee /dev/stderr)" ]
4848
'
4949

50-
test_expect_success 'Attempt to delete current branch' '
51-
command_error stg branch --delete $(stg branch) 2>err &&
52-
grep -e "cannot delete the current branch" err
50+
test_expect_success 'Delete current branch' '
51+
stg branch -c baz &&
52+
stg branch --delete &&
53+
stg branch >out &&
54+
cat >expected <<-\EOF &&
55+
foo
56+
EOF
57+
test_cmp expected out
58+
'
59+
60+
test_expect_success 'Attempt delete current branch with patches' '
61+
stg branch -c baz2 &&
62+
stg new -m p0 &&
63+
command_error stg branch --delete 2>err &&
64+
grep "delete not permitted: the series still contains patches" err &&
65+
stg branch --delete --force &&
66+
stg branch >out &&
67+
cat >expected <<-\EOF &&
68+
foo
69+
EOF
70+
test_cmp expected out
71+
'
72+
73+
test_expect_success 'Attempt delete current branch with dirty worktree' '
74+
stg branch -c baz2 &&
75+
echo content >>p0.t &&
76+
command_error stg branch --delete 2>err &&
77+
grep "cannot delete the current branch: worktree not clean" err &&
78+
git checkout -- p0.t &&
79+
stg branch --delete &&
80+
stg branch >out &&
81+
cat >expected <<-\EOF &&
82+
foo
83+
EOF
84+
test_cmp expected out
5385
'
5486

5587
test_expect_success 'Invalid num args to delete' '
56-
general_error stg branch --delete 2>err &&
57-
grep -e "the following required arguments were not provided" err &&
5888
general_error stg branch --delete foo extra 2>err &&
5989
grep -e "unexpected argument .extra." err
6090
'

0 commit comments

Comments
 (0)