Skip to content

Commit

Permalink
branch -d: base the "already-merged" safety on the branch it merges with
Browse files Browse the repository at this point in the history
When a branch is marked to merge with another ref (e.g. local 'next' that
merges from and pushes back to origin's 'next', with 'branch.next.merge'
set to 'refs/heads/next'), it makes little sense to base the "branch -d"
safety, whose purpose is not to lose commits that are not merged to other
branches, on the current branch.  It is much more sensible to check if it
is merged with the other branch it merges with.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
Junio C Hamano committed Dec 30, 2009
1 parent c2ff10c commit 99c419c
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 10 deletions.
64 changes: 54 additions & 10 deletions builtin-branch.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,60 @@ static const char *branch_get_color(enum color_branch ix)
return "";
}

static int branch_merged(int kind, const char *name,
struct commit *rev, struct commit *head_rev)
{
/*
* This checks whether the merge bases of branch and HEAD (or
* the other branch this branch builds upon) contains the
* branch, which means that the branch has already been merged
* safely to HEAD (or the other branch).
*/
struct commit *reference_rev = NULL;
const char *reference_name = NULL;
int merged;

if (kind == REF_LOCAL_BRANCH) {
struct branch *branch = branch_get(name);
unsigned char sha1[20];

if (branch &&
branch->merge &&
branch->merge[0] &&
branch->merge[0]->dst &&
(reference_name =
resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
reference_rev = lookup_commit_reference(sha1);
}
if (!reference_rev)
reference_rev = head_rev;

merged = in_merge_bases(rev, &reference_rev, 1);

/*
* After the safety valve is fully redefined to "check with
* upstream, if any, otherwise with HEAD", we should just
* return the result of the in_merge_bases() above without
* any of the following code, but during the transition period,
* a gentle reminder is in order.
*/
if ((head_rev != reference_rev) &&
in_merge_bases(rev, &head_rev, 1) != merged) {
if (merged)
warning("deleting branch '%s' that has been merged to\n"
" '%s', but it is not yet merged to HEAD.",
name, reference_name);
else
warning("not deleting branch '%s' that is not yet merged to\n"
" '%s', even though it is merged to HEAD.",
name, reference_name);
}
return merged;
}

static int delete_branches(int argc, const char **argv, int force, int kinds)
{
struct commit *rev, *head_rev = head_rev;
struct commit *rev, *head_rev = NULL;
unsigned char sha1[20];
char *name = NULL;
const char *fmt, *remote;
Expand Down Expand Up @@ -148,15 +199,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
continue;
}

/* This checks whether the merge bases of branch and
* HEAD contains branch -- which means that the HEAD
* contains everything in both.
*/

if (!force &&
!in_merge_bases(rev, &head_rev, 1)) {
error("The branch '%s' is not an ancestor of "
"your current HEAD.\n"
if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) {
error("The branch '%s' is not fully merged.\n"
"If you are sure you want to delete it, "
"run 'git branch -D %s'.", bname.buf, bname.buf);
ret = 1;
Expand Down
26 changes: 26 additions & 0 deletions t/t3200-branch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -468,4 +468,30 @@ test_expect_success 'detect misconfigured autosetuprebase (no value)' '
git config --unset branch.autosetuprebase
'

test_expect_success 'attempt to delete a branch without base and unmerged to HEAD' '
git checkout my9 &&
git config --unset branch.my8.merge &&
test_must_fail git branch -d my8
'

test_expect_success 'attempt to delete a branch merged to its base' '
# we are on my9 which is the initial commit; traditionally
# we would not have allowed deleting my8 that is not merged
# to my9, but it is set to track master that already has my8
git config branch.my8.merge refs/heads/master &&
git branch -d my8
'

test_expect_success 'attempt to delete a branch merged to its base' '
git checkout master &&
echo Third >>A &&
git commit -m "Third commit" A &&
git branch -t my10 my9 &&
git branch -f my10 HEAD^ &&
# we are on master which is at the third commit, and my10
# is behind us, so traditionally we would have allowed deleting
# it; but my10 is set to track my9 that is further behind.
test_must_fail git branch -d my10
'

test_done

0 comments on commit 99c419c

Please sign in to comment.