reset: add test cases for "--keep" option
[gitweb.git] / builtin-branch.c
index 5687d6042ced29e60aa1999dc9d73eb6c38b8e77..a28a13986d11ebfecd11a206d1b7a1fb626865db 100644 (file)
@@ -65,7 +65,7 @@ static int parse_branch_color_slot(const char *var, int ofs)
                return BRANCH_COLOR_LOCAL;
        if (!strcasecmp(var+ofs, "current"))
                return BRANCH_COLOR_CURRENT;
-       die("bad config variable '%s'", var);
+       return -1;
 }
 
 static int git_branch_config(const char *var, const char *value, void *cb)
@@ -76,6 +76,8 @@ static int git_branch_config(const char *var, const char *value, void *cb)
        }
        if (!prefixcmp(var, "color.branch.")) {
                int slot = parse_branch_color_slot(var, 13);
+               if (slot < 0)
+                       return 0;
                if (!value)
                        return config_error_nonbool(var);
                color_parse(value, var, branch_colors[slot]);
@@ -91,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;
@@ -146,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;
@@ -191,7 +237,7 @@ struct ref_item {
 
 struct ref_list {
        struct rev_info revs;
-       int index, alloc, maxwidth;
+       int index, alloc, maxwidth, verbose, abbrev;
        struct ref_item *list;
        struct commit_list *with_commit;
        int kinds;
@@ -240,21 +286,24 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
        if (ARRAY_SIZE(ref_kind) <= i)
                return 0;
 
-       commit = lookup_commit_reference_gently(sha1, 1);
-       if (!commit)
-               return error("branch '%s' does not point at a commit", refname);
-
-       /* Filter with with_commit if specified */
-       if (!is_descendant_of(commit, ref_list->with_commit))
-               return 0;
-
        /* Don't add types the caller doesn't want */
        if ((kind & ref_list->kinds) == 0)
                return 0;
 
-       if (merge_filter != NO_FILTER)
-               add_pending_object(&ref_list->revs,
-                                  (struct object *)commit, refname);
+       commit = NULL;
+       if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
+               commit = lookup_commit_reference_gently(sha1, 1);
+               if (!commit)
+                       return error("branch '%s' does not point at a commit", refname);
+
+               /* Filter with with_commit if specified */
+               if (!is_descendant_of(commit, ref_list->with_commit))
+                       return 0;
+
+               if (merge_filter != NO_FILTER)
+                       add_pending_object(&ref_list->revs,
+                                          (struct object *)commit, refname);
+       }
 
        /* Resize buffer */
        if (ref_list->index >= ref_list->alloc) {
@@ -384,8 +433,9 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
 
                commit = item->commit;
                if (commit && !parse_commit(commit)) {
+                       struct pretty_print_context ctx = {0};
                        pretty_print_commit(CMIT_FMT_ONELINE, commit,
-                                           &subject, 0, NULL, NULL, 0, 0);
+                                           &subject, &ctx);
                        sub = subject.buf;
                }
 
@@ -415,18 +465,38 @@ static int calc_maxwidth(struct ref_list *refs)
        return w;
 }
 
+
+static void show_detached(struct ref_list *ref_list)
+{
+       struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1);
+
+       if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) {
+               struct ref_item item;
+               item.name = xstrdup("(no branch)");
+               item.len = strlen(item.name);
+               item.kind = REF_LOCAL_BRANCH;
+               item.dest = NULL;
+               item.commit = head_commit;
+               if (item.len > ref_list->maxwidth)
+                       ref_list->maxwidth = item.len;
+               print_ref_item(&item, ref_list->maxwidth, ref_list->verbose, ref_list->abbrev, 1, "");
+               free(item.name);
+       }
+}
+
 static void print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit)
 {
        int i;
        struct ref_list ref_list;
-       struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1);
 
        memset(&ref_list, 0, sizeof(ref_list));
        ref_list.kinds = kinds;
+       ref_list.verbose = verbose;
+       ref_list.abbrev = abbrev;
        ref_list.with_commit = with_commit;
        if (merge_filter != NO_FILTER)
                init_revisions(&ref_list.revs, NULL);
-       for_each_ref(append_ref, &ref_list);
+       for_each_rawref(append_ref, &ref_list);
        if (merge_filter != NO_FILTER) {
                struct commit *filter;
                filter = lookup_commit_reference_gently(merge_filter_ref, 0);
@@ -442,19 +512,8 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str
        qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
 
        detached = (detached && (kinds & REF_LOCAL_BRANCH));
-       if (detached && head_commit &&
-           is_descendant_of(head_commit, with_commit)) {
-               struct ref_item item;
-               item.name = xstrdup("(no branch)");
-               item.len = strlen(item.name);
-               item.kind = REF_LOCAL_BRANCH;
-               item.dest = NULL;
-               item.commit = head_commit;
-               if (item.len > ref_list.maxwidth)
-                       ref_list.maxwidth = item.len;
-               print_ref_item(&item, ref_list.maxwidth, verbose, abbrev, 1, "");
-               free(item.name);
-       }
+       if (detached)
+               show_detached(&ref_list);
 
        for (i = 0; i < ref_list.index; i++) {
                int current = !detached &&
@@ -549,6 +608,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT__VERBOSE(&verbose),
                OPT_SET_INT('t', "track",  &track, "set up tracking mode (see git-pull(1))",
                        BRANCH_TRACK_EXPLICIT),
+               OPT_SET_INT( 0, "set-upstream",  &track, "change upstream info",
+                       BRANCH_TRACK_OVERRIDE),
                OPT_BOOLEAN( 0 , "color",  &branch_use_color, "use colored output"),
                OPT_SET_INT('r', NULL,     &kinds, "act on remote-tracking branches",
                        REF_REMOTE_BRANCH),
@@ -574,7 +635,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1),
                OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2),
                OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"),
-               OPT_BOOLEAN('f', NULL, &force_create, "force creation (when already exists)"),
+               OPT_BOOLEAN('f', "force", &force_create, "force creation (when already exists)"),
                {
                        OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
                        "commit", "print only not merged branches",
@@ -623,10 +684,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                rename_branch(head, argv[0], rename > 1);
        else if (rename && (argc == 2))
                rename_branch(argv[0], argv[1], rename > 1);
-       else if (argc <= 2)
+       else if (argc <= 2) {
+               if (kinds != REF_LOCAL_BRANCH)
+                       die("-a and -r options to 'git branch' do not make sense with a branch name");
                create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
                              force_create, reflog, track);
-       else
+       else
                usage_with_options(builtin_branch_usage, options);
 
        return 0;