Imported Upstream version 2.5.0
[platform/upstream/git.git] / builtin / branch.c
index 652b1d2..b42e5b6 100644 (file)
 #include "wt-status.h"
 
 static const char * const builtin_branch_usage[] = {
-       N_("git branch [options] [-r | -a] [--merged | --no-merged]"),
-       N_("git branch [options] [-l] [-f] <branchname> [<start-point>]"),
-       N_("git branch [options] [-r] (-d | -D) <branchname>..."),
-       N_("git branch [options] (-m | -M) [<oldbranch>] <newbranch>"),
+       N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"),
+       N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
+       N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
+       N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
        NULL
 };
 
@@ -62,39 +62,40 @@ static unsigned char merge_filter_ref[20];
 static struct string_list output = STRING_LIST_INIT_DUP;
 static unsigned int colopts;
 
-static int parse_branch_color_slot(const char *var, int ofs)
+static int parse_branch_color_slot(const char *slot)
 {
-       if (!strcasecmp(var+ofs, "plain"))
+       if (!strcasecmp(slot, "plain"))
                return BRANCH_COLOR_PLAIN;
-       if (!strcasecmp(var+ofs, "reset"))
+       if (!strcasecmp(slot, "reset"))
                return BRANCH_COLOR_RESET;
-       if (!strcasecmp(var+ofs, "remote"))
+       if (!strcasecmp(slot, "remote"))
                return BRANCH_COLOR_REMOTE;
-       if (!strcasecmp(var+ofs, "local"))
+       if (!strcasecmp(slot, "local"))
                return BRANCH_COLOR_LOCAL;
-       if (!strcasecmp(var+ofs, "current"))
+       if (!strcasecmp(slot, "current"))
                return BRANCH_COLOR_CURRENT;
-       if (!strcasecmp(var+ofs, "upstream"))
+       if (!strcasecmp(slot, "upstream"))
                return BRANCH_COLOR_UPSTREAM;
        return -1;
 }
 
 static int git_branch_config(const char *var, const char *value, void *cb)
 {
+       const char *slot_name;
+
        if (starts_with(var, "column."))
                return git_column_config(var, value, "branch", &colopts);
        if (!strcmp(var, "color.branch")) {
                branch_use_color = git_config_colorbool(var, value);
                return 0;
        }
-       if (starts_with(var, "color.branch.")) {
-               int slot = parse_branch_color_slot(var, 13);
+       if (skip_prefix(var, "color.branch.", &slot_name)) {
+               int slot = parse_branch_color_slot(slot_name);
                if (slot < 0)
                        return 0;
                if (!value)
                        return config_error_nonbool(var);
-               color_parse(value, var, branch_colors[slot]);
-               return 0;
+               return color_parse(value, branch_colors[slot]);
        }
        return git_color_default_config(var, value, cb);
 }
@@ -122,14 +123,13 @@ static int branch_merged(int kind, const char *name,
 
        if (kind == REF_LOCAL_BRANCH) {
                struct branch *branch = branch_get(name);
+               const char *upstream = branch_get_upstream(branch, NULL);
                unsigned char sha1[20];
 
-               if (branch &&
-                   branch->merge &&
-                   branch->merge[0] &&
-                   branch->merge[0]->dst &&
+               if (upstream &&
                    (reference_name = reference_name_to_free =
-                    resolve_refdup(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
+                    resolve_refdup(upstream, RESOLVE_REF_READING,
+                                   sha1, NULL)) != NULL)
                        reference_rev = lookup_commit_reference(sha1);
        }
        if (!reference_rev)
@@ -233,17 +233,20 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                free(name);
 
                name = mkpathdup(fmt, bname.buf);
-               target = resolve_ref_unsafe(name, sha1, 0, &flags);
-               if (!target ||
-                   (!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) {
+               target = resolve_ref_unsafe(name,
+                                           RESOLVE_REF_READING
+                                           | RESOLVE_REF_NO_RECURSE
+                                           | RESOLVE_REF_ALLOW_BAD_NAME,
+                                           sha1, &flags);
+               if (!target) {
                        error(remote_branch
-                             ? _("remote branch '%s' not found.")
+                             ? _("remote-tracking branch '%s' not found.")
                              : _("branch '%s' not found."), bname.buf);
                        ret = 1;
                        continue;
                }
 
-               if (!(flags & REF_ISSYMREF) &&
+               if (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&
                    check_branch_commit(bname.buf, name, sha1, head_rev, kinds,
                                        force)) {
                        ret = 1;
@@ -252,7 +255,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
 
                if (delete_ref(name, sha1, REF_NODEREF)) {
                        error(remote_branch
-                             ? _("Error deleting remote branch '%s'")
+                             ? _("Error deleting remote-tracking branch '%s'")
                              : _("Error deleting branch '%s'"),
                              bname.buf);
                        ret = 1;
@@ -260,11 +263,11 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                }
                if (!quiet) {
                        printf(remote_branch
-                              ? _("Deleted remote branch %s (was %s).\n")
+                              ? _("Deleted remote-tracking branch %s (was %s).\n")
                               : _("Deleted branch %s (was %s).\n"),
                               bname.buf,
-                              (flags & REF_ISSYMREF)
-                              ? target
+                              (flags & REF_ISBROKEN) ? "broken"
+                              : (flags & REF_ISSYMREF) ? target
                               : find_unique_abbrev(sha1, DEFAULT_ABBREV));
                }
                delete_branch_config(bname.buf);
@@ -280,6 +283,7 @@ struct ref_item {
        char *dest;
        unsigned int kind, width;
        struct commit *commit;
+       int ignore;
 };
 
 struct ref_list {
@@ -294,13 +298,13 @@ static char *resolve_symref(const char *src, const char *prefix)
 {
        unsigned char sha1[20];
        int flag;
-       const char *dst, *cp;
+       const char *dst;
 
-       dst = resolve_ref_unsafe(src, sha1, 0, &flag);
+       dst = resolve_ref_unsafe(src, 0, sha1, &flag);
        if (!(dst && (flag & REF_ISSYMREF)))
                return NULL;
-       if (prefix && (cp = skip_prefix(dst, prefix)))
-               dst = cp;
+       if (prefix)
+               skip_prefix(dst, prefix, &dst);
        return xstrdup(dst);
 }
 
@@ -322,7 +326,7 @@ static int match_patterns(const char **pattern, const char *refname)
        return 0;
 }
 
-static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+static int append_ref(const char *refname, const struct object_id *oid, int flags, void *cb_data)
 {
        struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data);
        struct ref_list *ref_list = cb->ref_list;
@@ -334,20 +338,18 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
        static struct {
                int kind;
                const char *prefix;
-               int pfxlen;
        } ref_kind[] = {
-               { REF_LOCAL_BRANCH, "refs/heads/", 11 },
-               { REF_REMOTE_BRANCH, "refs/remotes/", 13 },
+               { REF_LOCAL_BRANCH, "refs/heads/" },
+               { REF_REMOTE_BRANCH, "refs/remotes/" },
        };
 
        /* Detect kind */
        for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
                prefix = ref_kind[i].prefix;
-               if (strncmp(refname, prefix, ref_kind[i].pfxlen))
-                       continue;
-               kind = ref_kind[i].kind;
-               refname += ref_kind[i].pfxlen;
-               break;
+               if (skip_prefix(refname, prefix, &refname)) {
+                       kind = ref_kind[i].kind;
+                       break;
+               }
        }
        if (ARRAY_SIZE(ref_kind) <= i)
                return 0;
@@ -361,7 +363,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
 
        commit = NULL;
        if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
-               commit = lookup_commit_reference_gently(sha1, 1);
+               commit = lookup_commit_reference_gently(oid->hash, 1);
                if (!commit) {
                        cb->ret = error(_("branch '%s' does not point at a commit"), refname);
                        return 0;
@@ -385,6 +387,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
        newitem->commit = commit;
        newitem->width = utf8_strwidth(refname);
        newitem->dest = resolve_symref(orig_refname, prefix);
+       newitem->ignore = 0;
        /* adjust for "remotes/" */
        if (newitem->kind == REF_REMOTE_BRANCH &&
            ref_list->kinds != REF_REMOTE_BRANCH)
@@ -422,25 +425,19 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
        int ours, theirs;
        char *ref = NULL;
        struct branch *branch = branch_get(branch_name);
+       const char *upstream;
        struct strbuf fancy = STRBUF_INIT;
        int upstream_is_gone = 0;
        int added_decoration = 1;
 
-       switch (stat_tracking_info(branch, &ours, &theirs)) {
-       case 0:
-               /* no base */
-               return;
-       case -1:
-               /* with "gone" base */
+       if (stat_tracking_info(branch, &ours, &theirs, &upstream) < 0) {
+               if (!upstream)
+                       return;
                upstream_is_gone = 1;
-               break;
-       default:
-               /* with base */
-               break;
        }
 
        if (show_upstream_ref) {
-               ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0);
+               ref = shorten_unambiguous_ref(upstream, 0);
                if (want_color(branch_use_color))
                        strbuf_addf(&fancy, "%s%s%s",
                                        branch_get_color(BRANCH_COLOR_UPSTREAM),
@@ -484,17 +481,6 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
        free(ref);
 }
 
-static int matches_merge_filter(struct commit *commit)
-{
-       int is_merged;
-
-       if (merge_filter == NO_FILTER)
-               return 1;
-
-       is_merged = !!(commit->object.flags & UNINTERESTING);
-       return (is_merged == (merge_filter == SHOW_MERGED));
-}
-
 static void add_verbose_info(struct strbuf *out, struct ref_item *item,
                             int verbose, int abbrev)
 {
@@ -522,10 +508,9 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
 {
        char c;
        int color;
-       struct commit *commit = item->commit;
        struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
 
-       if (!matches_merge_filter(commit))
+       if (item->ignore)
                return;
 
        switch (item->kind) {
@@ -575,7 +560,7 @@ static int calc_maxwidth(struct ref_list *refs)
 {
        int i, w = 0;
        for (i = 0; i < refs->index; i++) {
-               if (!matches_merge_filter(refs->list[i].commit))
+               if (refs->list[i].ignore)
                        continue;
                if (refs->list[i].width > w)
                        w = refs->list[i].width;
@@ -596,9 +581,16 @@ static char *get_head_description(void)
        else if (state.bisect_in_progress)
                strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
                            state.branch);
-       else if (state.detached_from)
-               strbuf_addf(&desc, _("(detached from %s)"),
-                           state.detached_from);
+       else if (state.detached_from) {
+               /* TRANSLATORS: make sure these match _("HEAD detached at ")
+                  and _("HEAD detached from ") in wt-status.c */
+               if (state.detached_at)
+                       strbuf_addf(&desc, _("(HEAD detached at %s)"),
+                               state.detached_from);
+               else
+                       strbuf_addf(&desc, _("(HEAD detached from %s)"),
+                               state.detached_from);
+       }
        else
                strbuf_addstr(&desc, _("(no branch)"));
        free(state.branch);
@@ -618,6 +610,7 @@ static void show_detached(struct ref_list *ref_list)
                item.kind = REF_LOCAL_BRANCH;
                item.dest = NULL;
                item.commit = head_commit;
+               item.ignore = 0;
                if (item.width > ref_list->maxwidth)
                        ref_list->maxwidth = item.width;
                print_ref_item(&item, ref_list->maxwidth, ref_list->verbose, ref_list->abbrev, 1, "");
@@ -653,7 +646,23 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru
                add_pending_object(&ref_list.revs,
                                   (struct object *) filter, "");
                ref_list.revs.limited = 1;
-               prepare_revision_walk(&ref_list.revs);
+
+               if (prepare_revision_walk(&ref_list.revs))
+                       die(_("revision walk setup failed"));
+
+               for (i = 0; i < ref_list.index; i++) {
+                       struct ref_item *item = &ref_list.list[i];
+                       struct commit *commit = item->commit;
+                       int is_merged = !!(commit->object.flags & UNINTERESTING);
+                       item->ignore = is_merged != (merge_filter == SHOW_MERGED);
+               }
+
+               for (i = 0; i < ref_list.index; i++) {
+                       struct ref_item *item = &ref_list.list[i];
+                       clear_commit_marks(item->commit, ALL_REV_FLAGS);
+               }
+               clear_commit_marks(filter, ALL_REV_FLAGS);
+
                if (verbose)
                        ref_list.maxwidth = calc_maxwidth(&ref_list);
        }
@@ -754,7 +763,6 @@ static const char edit_description[] = "BRANCH_DESCRIPTION";
 
 static int edit_branch_description(const char *branch_name)
 {
-       FILE *fp;
        int status;
        struct strbuf buf = STRBUF_INIT;
        struct strbuf name = STRBUF_INIT;
@@ -767,8 +775,7 @@ static int edit_branch_description(const char *branch_name)
                    "  %s\n"
                    "Lines starting with '%c' will be stripped.\n",
                    branch_name, comment_line_char);
-       fp = fopen(git_path(edit_description), "w");
-       if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {
+       if (write_file(git_path(edit_description), 0, "%s", buf.buf)) {
                strbuf_release(&buf);
                return error(_("could not write branch description template: %s"),
                             strerror(errno));
@@ -790,7 +797,7 @@ static int edit_branch_description(const char *branch_name)
 
 int cmd_branch(int argc, const char **argv, const char *prefix)
 {
-       int delete = 0, rename = 0, force_create = 0, list = 0;
+       int delete = 0, rename = 0, force = 0, list = 0;
        int verbose = 0, abbrev = -1, detached = 0;
        int reflog = 0, edit_description = 0;
        int quiet = 0, unset_upstream = 0;
@@ -838,7 +845,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_BOOL('l', "create-reflog", &reflog, N_("create the branch's reflog")),
                OPT_BOOL(0, "edit-description", &edit_description,
                         N_("edit the description for the branch")),
-               OPT__FORCE(&force_create, N_("force creation (when already exists)")),
+               OPT__FORCE(&force, N_("force creation, move/rename, deletion")),
                {
                        OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
                        N_("commit"), N_("print only not merged branches"),
@@ -862,16 +869,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 
        track = git_branch_track;
 
-       head = resolve_refdup("HEAD", head_sha1, 0, NULL);
+       head = resolve_refdup("HEAD", 0, head_sha1, NULL);
        if (!head)
                die(_("Failed to resolve HEAD as a valid ref."));
-       if (!strcmp(head, "HEAD")) {
+       if (!strcmp(head, "HEAD"))
                detached = 1;
-       } else {
-               if (!starts_with(head, "refs/heads/"))
-                       die(_("HEAD not found below refs/heads!"));
-               head += 11;
-       }
+       else if (!skip_prefix(head, "refs/heads/", &head))
+               die(_("HEAD not found below refs/heads!"));
        hashcpy(merge_filter_ref, head_sha1);
 
 
@@ -884,7 +888,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        if (with_commit || merge_filter != NO_FILTER)
                list = 1;
 
-       if (!!delete + !!rename + !!force_create + !!new_upstream +
+       if (!!delete + !!rename + !!new_upstream +
            list + unset_upstream > 1)
                usage_with_options(builtin_branch_usage, options);
 
@@ -897,6 +901,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                colopts = 0;
        }
 
+       if (force) {
+               delete *= 2;
+               rename *= 2;
+       }
+
        if (delete) {
                if (!argc)
                        die(_("branch name required"));
@@ -1013,7 +1022,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 
                branch_existed = ref_exists(branch->refname);
                create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
-                             force_create, reflog, 0, quiet, track);
+                             force, reflog, 0, quiet, track);
 
                /*
                 * We only show the instructions if the user gave us