summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--builtin-branch.c64
-rwxr-xr-xt/t3200-branch.sh26
2 files changed, 80 insertions, 10 deletions
diff --git a/builtin-branch.c b/builtin-branch.c
index c87e63b..d2a35fe 100644
--- a/builtin-branch.c
+++ b/builtin-branch.c
@@ -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;
@@ -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;
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index d59a9b4..e0b7605 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -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