branch -d: base the "already-merged" safety on the branch it merges with
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>
Junio C Hamano committed
Dec 29, 2009 at 22:43 UTC
99c419c91554e9f60940228006b7d39d42704da7
2 files changed
+80
-10
builtin-branch.c
+54
-10
@@ -93,9 +93,60 @@ static const char *branch_get_color(enum color_branch ix)
93
return "";
94
}
95
96
+static int branch_merged(int kind, const char *name,
97
+ struct commit *rev, struct commit *head_rev)
98
+{
99
+ /*
100
+ * This checks whether the merge bases of branch and HEAD (or
101
+ * the other branch this branch builds upon) contains the
102
+ * branch, which means that the branch has already been merged
103
+ * safely to HEAD (or the other branch).
104
+ */
105
+ struct commit *reference_rev = NULL;
106
+ const char *reference_name = NULL;
107
+ int merged;
108
+
109
+ if (kind == REF_LOCAL_BRANCH) {
110
+ struct branch *branch = branch_get(name);
111
+ unsigned char sha1[20];
112
+
113
+ if (branch &&
114
+ branch->merge &&
115
+ branch->merge[0] &&
116
+ branch->merge[0]->dst &&
117
+ (reference_name =
118
+ resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
119
+ reference_rev = lookup_commit_reference(sha1);
120
+ }
121
+ if (!reference_rev)
122
+ reference_rev = head_rev;
123
+
124
+ merged = in_merge_bases(rev, &reference_rev, 1);
125
+
126
+ /*
127
+ * After the safety valve is fully redefined to "check with
128
+ * upstream, if any, otherwise with HEAD", we should just
129
+ * return the result of the in_merge_bases() above without
130
+ * any of the following code, but during the transition period,
131
+ * a gentle reminder is in order.
132
+ */
133
+ if ((head_rev != reference_rev) &&
134
+ in_merge_bases(rev, &head_rev, 1) != merged) {
135
+ if (merged)
136
+ warning("deleting branch '%s' that has been merged to\n"
137
+ " '%s', but it is not yet merged to HEAD.",
138
+ name, reference_name);
139
+ else
140
+ warning("not deleting branch '%s' that is not yet merged to\n"
141
+ " '%s', even though it is merged to HEAD.",
142
+ name, reference_name);
143
+ }
144
+ return merged;
145
+}
146
+
147
static int delete_branches(int argc, const char **argv, int force, int kinds)
148
{
98
- struct commit *rev, *head_rev = head_rev;
149
+ struct commit *rev, *head_rev = NULL;
150
unsigned char sha1[20];
151
char *name = NULL;
152
const char *fmt, *remote;
@@ -148,15 +199,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
199
continue;
200
}
201
151
- /* This checks whether the merge bases of branch and
152
- * HEAD contains branch -- which means that the HEAD
153
- * contains everything in both.
154
- */
155
-
156
- if (!force &&
157
- !in_merge_bases(rev, &head_rev, 1)) {
158
- error("The branch '%s' is not an ancestor of "
159
- "your current HEAD.\n"
202
+ if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) {
203
+ error("The branch '%s' is not fully merged.\n"
204
"If you are sure you want to delete it, "
205
"run 'git branch -D %s'.", bname.buf, bname.buf);
206
ret = 1;
t/t3200-branch.sh
+26
@@ -468,4 +468,30 @@ test_expect_success 'detect misconfigured autosetuprebase (no value)' '
468
git config --unset branch.autosetuprebase
469
'
470
471
+test_expect_success 'attempt to delete a branch without base and unmerged to HEAD' '
472
+ git checkout my9 &&
473
+ git config --unset branch.my8.merge &&
474
+ test_must_fail git branch -d my8
475
+'
476
+
477
+test_expect_success 'attempt to delete a branch merged to its base' '
478
+ # we are on my9 which is the initial commit; traditionally
479
+ # we would not have allowed deleting my8 that is not merged
480
+ # to my9, but it is set to track master that already has my8
481
+ git config branch.my8.merge refs/heads/master &&
482
+ git branch -d my8
483
+'
484
+
485
+test_expect_success 'attempt to delete a branch merged to its base' '
486
+ git checkout master &&
487
+ echo Third >>A &&
488
+ git commit -m "Third commit" A &&
489
+ git branch -t my10 my9 &&
490
+ git branch -f my10 HEAD^ &&
491
+ # we are on master which is at the third commit, and my10
492
+ # is behind us, so traditionally we would have allowed deleting
493
+ # it; but my10 is set to track my9 that is further behind.
494
+ test_must_fail git branch -d my10
495
+'
496
+
497
test_done