Sync with 2.7.1
[gitweb.git] / builtin / branch.c
index bfbba2f8d10501348e1ddf04a3928ab11cfb93f2..3f6c825db1caf9c7cdd2ca7c1280e5ad157f9a27 100644 (file)
@@ -26,6 +26,7 @@ static const char * const builtin_branch_usage[] = {
        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>"),
+       N_("git branch [<options>] [-r | -a] [--points-at]"),
        NULL
 };
 
@@ -270,125 +271,6 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
        return(ret);
 }
 
-static char *resolve_symref(const char *src, const char *prefix)
-{
-       unsigned char sha1[20];
-       int flag;
-       const char *dst;
-
-       dst = resolve_ref_unsafe(src, 0, sha1, &flag);
-       if (!(dst && (flag & REF_ISSYMREF)))
-               return NULL;
-       if (prefix)
-               skip_prefix(dst, prefix, &dst);
-       return xstrdup(dst);
-}
-
-static int match_patterns(const char **pattern, const char *refname)
-{
-       if (!*pattern)
-               return 1; /* no pattern always matches */
-       while (*pattern) {
-               if (!wildmatch(*pattern, refname, 0, NULL))
-                       return 1;
-               pattern++;
-       }
-       return 0;
-}
-
-/*
- * Allocate memory for a new ref_array_item and insert that into the
- * given ref_array. Doesn't take the objectname unlike
- * new_ref_array_item(). This is a temporary function which will be
- * removed when we port branch.c to use ref-filter APIs.
- */
-static struct ref_array_item *ref_array_append(struct ref_array *array, const char *refname)
-{
-       size_t len = strlen(refname);
-       struct ref_array_item *ref = xcalloc(1, sizeof(struct ref_array_item) + len + 1);
-       memcpy(ref->refname, refname, len);
-       ref->refname[len] = '\0';
-       REALLOC_ARRAY(array->items, array->nr + 1);
-       array->items[array->nr++] = ref;
-       return ref;
-}
-
-static int append_ref(const char *refname, const struct object_id *oid, int flags, void *cb_data)
-{
-       struct ref_filter_cbdata *cb = (struct ref_filter_cbdata *)(cb_data);
-       struct ref_filter *filter = cb->filter;
-       struct ref_array *array = cb->array;
-       struct ref_array_item *item;
-       struct commit *commit;
-       int kind, i;
-       const char *prefix, *orig_refname = refname;
-
-       static struct {
-               int kind;
-               const char *prefix;
-       } ref_kind[] = {
-               { FILTER_REFS_BRANCHES, "refs/heads/" },
-               { FILTER_REFS_REMOTES, "refs/remotes/" },
-       };
-
-       /* Detect kind */
-       for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
-               prefix = ref_kind[i].prefix;
-               if (skip_prefix(refname, prefix, &refname)) {
-                       kind = ref_kind[i].kind;
-                       break;
-               }
-       }
-       if (ARRAY_SIZE(ref_kind) <= i) {
-               if (!strcmp(refname, "HEAD"))
-                       kind = FILTER_REFS_DETACHED_HEAD;
-               else
-                       return 0;
-       }
-
-       /* Don't add types the caller doesn't want */
-       if ((kind & filter->kind) == 0)
-               return 0;
-
-       if (!match_patterns(filter->name_patterns, refname))
-               return 0;
-
-       commit = NULL;
-       if (filter->verbose || filter->with_commit || filter->merge != REF_FILTER_MERGED_NONE) {
-               commit = lookup_commit_reference_gently(oid->hash, 1);
-               if (!commit)
-                       return 0;
-
-               /* Filter with with_commit if specified */
-               if (!is_descendant_of(commit, filter->with_commit))
-                       return 0;
-
-               if (filter->merge != REF_FILTER_MERGED_NONE)
-                       add_pending_object(array->revs,
-                                          (struct object *)commit, refname);
-       }
-
-       item = ref_array_append(array, refname);
-
-       /* Record the new item */
-       item->kind = kind;
-       item->commit = commit;
-       item->symref = resolve_symref(orig_refname, prefix);
-       item->ignore = 0;
-
-       return 0;
-}
-
-static int ref_cmp(const void *r1, const void *r2)
-{
-       struct ref_array_item *c1 = *((struct ref_array_item **)r1);
-       struct ref_array_item *c2 = *((struct ref_array_item **)r2);
-
-       if (c1->kind != c2->kind)
-               return c1->kind - c2->kind;
-       return strcmp(c1->refname, c2->refname);
-}
-
 static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
                int show_upstream_ref)
 {
@@ -452,7 +334,7 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
 }
 
 static void add_verbose_info(struct strbuf *out, struct ref_array_item *item,
-                            struct ref_filter *filter)
+                            struct ref_filter *filter, const char *refname)
 {
        struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
        const char *sub = _(" **** invalid ref ****");
@@ -464,10 +346,10 @@ static void add_verbose_info(struct strbuf *out, struct ref_array_item *item,
        }
 
        if (item->kind == FILTER_REFS_BRANCHES)
-               fill_tracking_info(&stat, item->refname, filter->verbose > 1);
+               fill_tracking_info(&stat, refname, filter->verbose > 1);
 
        strbuf_addf(out, " %s %s%s",
-               find_unique_abbrev(item->commit->object.sha1, filter->abbrev),
+               find_unique_abbrev(item->commit->object.oid.hash, filter->abbrev),
                stat.buf, sub);
        strbuf_release(&stat);
        strbuf_release(&subject);
@@ -504,8 +386,8 @@ static char *get_head_description(void)
        return strbuf_detach(&desc, NULL);
 }
 
-static void print_ref_item(struct ref_array_item *item, int maxwidth,
-                          struct ref_filter *filter, const char *remote_prefix)
+static void format_and_print_ref_item(struct ref_array_item *item, int maxwidth,
+                                     struct ref_filter *filter, const char *remote_prefix)
 {
        char c;
        int current = 0;
@@ -515,17 +397,16 @@ static void print_ref_item(struct ref_array_item *item, int maxwidth,
        const char *desc = item->refname;
        char *to_free = NULL;
 
-       if (item->ignore)
-               return;
-
        switch (item->kind) {
        case FILTER_REFS_BRANCHES:
-               if (!filter->detached && !strcmp(item->refname, head))
+               skip_prefix(desc, "refs/heads/", &desc);
+               if (!filter->detached && !strcmp(desc, head))
                        current = 1;
                else
                        color = BRANCH_COLOR_LOCAL;
                break;
        case FILTER_REFS_REMOTES:
+               skip_prefix(desc, "refs/remotes/", &desc);
                color = BRANCH_COLOR_REMOTE;
                prefix = remote_prefix;
                break;
@@ -554,11 +435,13 @@ static void print_ref_item(struct ref_array_item *item, int maxwidth,
                strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color),
                            name.buf, branch_get_color(BRANCH_COLOR_RESET));
 
-       if (item->symref)
-               strbuf_addf(&out, " -> %s", item->symref);
+       if (item->symref) {
+               skip_prefix(item->symref, "refs/remotes/", &desc);
+               strbuf_addf(&out, " -> %s", desc);
+       }
        else if (filter->verbose)
                /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
-               add_verbose_info(&out, item, filter);
+               add_verbose_info(&out, item, filter, desc);
        if (column_active(colopts)) {
                assert(!filter->verbose && "--column and --verbose are incompatible");
                string_list_append(&output, out.buf);
@@ -575,11 +458,13 @@ static int calc_maxwidth(struct ref_array *refs, int remote_bonus)
        int i, max = 0;
        for (i = 0; i < refs->nr; i++) {
                struct ref_array_item *it = refs->items[i];
+               const char *desc = it->refname;
                int w;
 
-               if (it->ignore)
-                       continue;
-               w = utf8_strwidth(it->refname);
+               skip_prefix(it->refname, "refs/heads/", &desc);
+               skip_prefix(it->refname, "refs/remotes/", &desc);
+               w = utf8_strwidth(desc);
+
                if (it->kind == FILTER_REFS_REMOTES)
                        w += remote_bonus;
                if (w > max)
@@ -588,14 +473,12 @@ static int calc_maxwidth(struct ref_array *refs, int remote_bonus)
        return max;
 }
 
-static void print_ref_list(struct ref_filter *filter)
+static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting)
 {
        int i;
        struct ref_array array;
-       struct ref_filter_cbdata data;
        int maxwidth = 0;
        const char *remote_prefix = "";
-       struct rev_info revs;
 
        /*
         * If we are listing more than just remote branches,
@@ -606,54 +489,26 @@ static void print_ref_list(struct ref_filter *filter)
                remote_prefix = "remotes/";
 
        memset(&array, 0, sizeof(array));
-       if (filter->merge != REF_FILTER_MERGED_NONE)
-               init_revisions(&revs, NULL);
-
-       data.array = &array;
-       data.filter = filter;
-       array.revs = &revs;
-
-       /*
-        * First we obtain all regular branch refs and if the HEAD is
-        * detached then we insert that ref to the end of the ref_fist
-        * so that it can be printed and removed first.
-        */
-       for_each_rawref(append_ref, &data);
-       if (filter->detached)
-               head_ref(append_ref, &data);
-       /*
-        * The following implementation is currently duplicated in ref-filter. It
-        * will eventually be removed when we port branch.c to use ref-filter APIs.
-        */
-       if (filter->merge != REF_FILTER_MERGED_NONE) {
-               filter->merge_commit->object.flags |= UNINTERESTING;
-               add_pending_object(&revs, &filter->merge_commit->object, "");
-               revs.limited = 1;
-
-               if (prepare_revision_walk(&revs))
-                       die(_("revision walk setup failed"));
-
-               for (i = 0; i < array.nr; i++) {
-                       struct ref_array_item *item = array.items[i];
-                       struct commit *commit = item->commit;
-                       int is_merged = !!(commit->object.flags & UNINTERESTING);
-                       item->ignore = is_merged != (filter->merge == REF_FILTER_MERGED_INCLUDE);
-               }
 
-               for (i = 0; i < array.nr; i++) {
-                       struct ref_array_item *item = array.items[i];
-                       clear_commit_marks(item->commit, ALL_REV_FLAGS);
-               }
-               clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS);
-       }
+       verify_ref_format("%(refname)%(symref)");
+       filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN);
 
        if (filter->verbose)
                maxwidth = calc_maxwidth(&array, strlen(remote_prefix));
 
-       qsort(array.items, array.nr, sizeof(struct ref_array_item *), ref_cmp);
+       /*
+        * If no sorting parameter is given then we default to sorting
+        * by 'refname'. This would give us an alphabetically sorted
+        * array with the 'HEAD' ref at the beginning followed by
+        * local branches 'refs/heads/...' and finally remote-tacking
+        * branches 'refs/remotes/...'.
+        */
+       if (!sorting)
+               sorting = ref_default_sorting();
+       ref_array_sort(sorting, &array);
 
        for (i = 0; i < array.nr; i++)
-               print_ref_item(array.items[i], maxwidth, filter, remote_prefix);
+               format_and_print_ref_item(array.items[i], maxwidth, filter, remote_prefix);
 
        ref_array_clear(&array);
 }
@@ -727,7 +582,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);
-       if (write_file(git_path(edit_description), 0, "%s", buf.buf)) {
+       if (write_file_gently(git_path(edit_description), "%s", buf.buf)) {
                strbuf_release(&buf);
                return error(_("could not write branch description template: %s"),
                             strerror(errno));
@@ -737,7 +592,7 @@ static int edit_branch_description(const char *branch_name)
                strbuf_release(&buf);
                return -1;
        }
-       stripspace(&buf, 1);
+       strbuf_stripspace(&buf, 1);
 
        strbuf_addf(&name, "branch.%s.description", branch_name);
        status = git_config_set(name.buf, buf.len ? buf.buf : NULL);
@@ -755,6 +610,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        const char *new_upstream = NULL;
        enum branch_track track;
        struct ref_filter filter;
+       static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
 
        struct option options[] = {
                OPT_GROUP(N_("Generic options")),
@@ -789,6 +645,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_MERGED(&filter, N_("print only branches that are merged")),
                OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
                OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")),
+               OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
+                            N_("field name to sort on"), &parse_opt_ref_sorting),
+               {
+                       OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
+                       N_("print only branches of the object"), 0, parse_opt_object_name
+               },
                OPT_END(),
        };
 
@@ -817,7 +679,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0)
                list = 1;
 
-       if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE)
+       if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr)
                list = 1;
 
        if (!!delete + !!rename + !!new_upstream +
@@ -847,7 +709,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached)
                        filter.kind |= FILTER_REFS_DETACHED_HEAD;
                filter.name_patterns = argv;
-               print_ref_list(&filter);
+               print_ref_list(&filter, sorting);
                print_columns(&output, colopts, NULL);
                string_list_clear(&output, 0);
                return 0;