diff --git a/builtin/branch.c b/builtin/branch.c index ebc37423ab..bfbba2f8d1 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -19,6 +19,7 @@ #include "column.h" #include "utf8.h" #include "wt-status.h" +#include "ref-filter.h" static const char * const builtin_branch_usage[] = { N_("git branch [] [-r | -a] [--merged | --no-merged]"), @@ -28,10 +29,6 @@ static const char * const builtin_branch_usage[] = { NULL }; -#define REF_DETACHED_HEAD 0x01 -#define REF_LOCAL_BRANCH 0x02 -#define REF_REMOTE_BRANCH 0x04 - static const char *head; static unsigned char head_sha1[20]; @@ -53,13 +50,6 @@ enum color_branch { BRANCH_COLOR_UPSTREAM = 5 }; -static enum merge_filter { - NO_FILTER = 0, - SHOW_NOT_MERGED, - SHOW_MERGED -} merge_filter; -static unsigned char merge_filter_ref[20]; - static struct string_list output = STRING_LIST_INIT_DUP; static unsigned int colopts; @@ -122,7 +112,7 @@ static int branch_merged(int kind, const char *name, void *reference_name_to_free = NULL; int merged; - if (kind == REF_LOCAL_BRANCH) { + if (kind == FILTER_REFS_BRANCHES) { struct branch *branch = branch_get(name); const char *upstream = branch_get_upstream(branch, NULL); unsigned char sha1[20]; @@ -200,14 +190,14 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, struct strbuf bname = STRBUF_INIT; switch (kinds) { - case REF_REMOTE_BRANCH: + case FILTER_REFS_REMOTES: fmt = "refs/remotes/%s"; /* For subsequent UI messages */ remote_branch = 1; force = 1; break; - case REF_LOCAL_BRANCH: + case FILTER_REFS_BRANCHES: fmt = "refs/heads/%s"; break; default: @@ -224,7 +214,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, int flags = 0; strbuf_branchname(&bname, argv[i]); - if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) { + if (kinds == FILTER_REFS_BRANCHES && !strcmp(head, bname.buf)) { error(_("Cannot delete the branch '%s' " "which you are currently on."), bname.buf); ret = 1; @@ -280,22 +270,6 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, return(ret); } -struct ref_item { - char *name; - char *dest; - unsigned int kind; - struct commit *commit; - int ignore; -}; - -struct ref_list { - struct rev_info revs; - int index, alloc, verbose, abbrev; - struct ref_item *list; - struct commit_list *with_commit; - int kinds; -}; - static char *resolve_symref(const char *src, const char *prefix) { unsigned char sha1[20]; @@ -310,11 +284,6 @@ static char *resolve_symref(const char *src, const char *prefix) return xstrdup(dst); } -struct append_ref_cb { - struct ref_list *ref_list; - const char **pattern; -}; - static int match_patterns(const char **pattern, const char *refname) { if (!*pattern) @@ -327,11 +296,29 @@ static int match_patterns(const char **pattern, const char *refname) 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 append_ref_cb *cb = (struct append_ref_cb *)(cb_data); - struct ref_list *ref_list = cb->ref_list; - struct ref_item *newitem; + 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; @@ -340,8 +327,8 @@ static int append_ref(const char *refname, const struct object_id *oid, int flag int kind; const char *prefix; } ref_kind[] = { - { REF_LOCAL_BRANCH, "refs/heads/" }, - { REF_REMOTE_BRANCH, "refs/remotes/" }, + { FILTER_REFS_BRANCHES, "refs/heads/" }, + { FILTER_REFS_REMOTES, "refs/remotes/" }, }; /* Detect kind */ @@ -354,65 +341,52 @@ static int append_ref(const char *refname, const struct object_id *oid, int flag } if (ARRAY_SIZE(ref_kind) <= i) { if (!strcmp(refname, "HEAD")) - kind = REF_DETACHED_HEAD; + kind = FILTER_REFS_DETACHED_HEAD; else return 0; } /* Don't add types the caller doesn't want */ - if ((kind & ref_list->kinds) == 0) + if ((kind & filter->kind) == 0) return 0; - if (!match_patterns(cb->pattern, refname)) + if (!match_patterns(filter->name_patterns, refname)) return 0; commit = NULL; - if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) { + 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, ref_list->with_commit)) + if (!is_descendant_of(commit, filter->with_commit)) return 0; - if (merge_filter != NO_FILTER) - add_pending_object(&ref_list->revs, + if (filter->merge != REF_FILTER_MERGED_NONE) + add_pending_object(array->revs, (struct object *)commit, refname); } - ALLOC_GROW(ref_list->list, ref_list->index + 1, ref_list->alloc); + item = ref_array_append(array, refname); /* Record the new item */ - newitem = &(ref_list->list[ref_list->index++]); - newitem->name = xstrdup(refname); - newitem->kind = kind; - newitem->commit = commit; - newitem->dest = resolve_symref(orig_refname, prefix); - newitem->ignore = 0; + item->kind = kind; + item->commit = commit; + item->symref = resolve_symref(orig_refname, prefix); + item->ignore = 0; return 0; } -static void free_ref_list(struct ref_list *ref_list) -{ - int i; - - for (i = 0; i < ref_list->index; i++) { - free(ref_list->list[i].name); - free(ref_list->list[i].dest); - } - free(ref_list->list); -} - static int ref_cmp(const void *r1, const void *r2) { - struct ref_item *c1 = (struct ref_item *)(r1); - struct ref_item *c2 = (struct ref_item *)(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->name, c2->name); + return strcmp(c1->refname, c2->refname); } static void fill_tracking_info(struct strbuf *stat, const char *branch_name, @@ -477,8 +451,8 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, free(ref); } -static void add_verbose_info(struct strbuf *out, struct ref_item *item, - int verbose, int abbrev) +static void add_verbose_info(struct strbuf *out, struct ref_array_item *item, + struct ref_filter *filter) { struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; const char *sub = _(" **** invalid ref ****"); @@ -489,11 +463,11 @@ static void add_verbose_info(struct strbuf *out, struct ref_item *item, sub = subject.buf; } - if (item->kind == REF_LOCAL_BRANCH) - fill_tracking_info(&stat, item->name, verbose > 1); + if (item->kind == FILTER_REFS_BRANCHES) + fill_tracking_info(&stat, item->refname, filter->verbose > 1); strbuf_addf(out, " %s %s%s", - find_unique_abbrev(item->commit->object.sha1, abbrev), + find_unique_abbrev(item->commit->object.sha1, filter->abbrev), stat.buf, sub); strbuf_release(&stat); strbuf_release(&subject); @@ -530,32 +504,32 @@ static char *get_head_description(void) return strbuf_detach(&desc, NULL); } -static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, - int abbrev, int detached, const char *remote_prefix) +static void print_ref_item(struct ref_array_item *item, int maxwidth, + struct ref_filter *filter, const char *remote_prefix) { char c; int current = 0; int color; struct strbuf out = STRBUF_INIT, name = STRBUF_INIT; const char *prefix = ""; - const char *desc = item->name; + const char *desc = item->refname; char *to_free = NULL; if (item->ignore) return; switch (item->kind) { - case REF_LOCAL_BRANCH: - if (!detached && !strcmp(item->name, head)) + case FILTER_REFS_BRANCHES: + if (!filter->detached && !strcmp(item->refname, head)) current = 1; else color = BRANCH_COLOR_LOCAL; break; - case REF_REMOTE_BRANCH: + case FILTER_REFS_REMOTES: color = BRANCH_COLOR_REMOTE; prefix = remote_prefix; break; - case REF_DETACHED_HEAD: + case FILTER_REFS_DETACHED_HEAD: desc = to_free = get_head_description(); current = 1; break; @@ -571,7 +545,7 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, } strbuf_addf(&name, "%s%s", prefix, desc); - if (verbose) { + if (filter->verbose) { int utf8_compensation = strlen(name.buf) - utf8_strwidth(name.buf); strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color), maxwidth + utf8_compensation, name.buf, @@ -580,13 +554,13 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color), name.buf, branch_get_color(BRANCH_COLOR_RESET)); - if (item->dest) - strbuf_addf(&out, " -> %s", item->dest); - else if (verbose) + if (item->symref) + strbuf_addf(&out, " -> %s", item->symref); + else if (filter->verbose) /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */ - add_verbose_info(&out, item, verbose, abbrev); + add_verbose_info(&out, item, filter); if (column_active(colopts)) { - assert(!verbose && "--column and --verbose are incompatible"); + assert(!filter->verbose && "--column and --verbose are incompatible"); string_list_append(&output, out.buf); } else { printf("%s\n", out.buf); @@ -596,17 +570,17 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, free(to_free); } -static int calc_maxwidth(struct ref_list *refs, int remote_bonus) +static int calc_maxwidth(struct ref_array *refs, int remote_bonus) { int i, max = 0; - for (i = 0; i < refs->index; i++) { - struct ref_item *it = &refs->list[i]; + for (i = 0; i < refs->nr; i++) { + struct ref_array_item *it = refs->items[i]; int w; if (it->ignore) continue; - w = utf8_strwidth(it->name); - if (it->kind == REF_REMOTE_BRANCH) + w = utf8_strwidth(it->refname); + if (it->kind == FILTER_REFS_REMOTES) w += remote_bonus; if (w > max) max = w; @@ -614,81 +588,74 @@ static int calc_maxwidth(struct ref_list *refs, int remote_bonus) return max; } -static void print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern) +static void print_ref_list(struct ref_filter *filter) { int i; - struct append_ref_cb cb; - struct ref_list ref_list; + 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, * then remote branches will have a "remotes/" prefix. * We need to account for this in the width. */ - if (kinds != REF_REMOTE_BRANCH) + if (filter->kind != FILTER_REFS_REMOTES) remote_prefix = "remotes/"; - 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); - cb.ref_list = &ref_list; - cb.pattern = pattern; + 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, &cb); - if (detached) - head_ref(append_ref, &cb); + 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 (merge_filter != NO_FILTER) { - struct commit *filter; - filter = lookup_commit_reference_gently(merge_filter_ref, 0); - if (!filter) - die(_("object '%s' does not point to a commit"), - sha1_to_hex(merge_filter_ref)); + if (filter->merge != REF_FILTER_MERGED_NONE) { + filter->merge_commit->object.flags |= UNINTERESTING; + add_pending_object(&revs, &filter->merge_commit->object, ""); + revs.limited = 1; - filter->object.flags |= UNINTERESTING; - add_pending_object(&ref_list.revs, - (struct object *) filter, ""); - ref_list.revs.limited = 1; - - if (prepare_revision_walk(&ref_list.revs)) + if (prepare_revision_walk(&revs)) die(_("revision walk setup failed")); - for (i = 0; i < ref_list.index; i++) { - struct ref_item *item = &ref_list.list[i]; + 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 != (merge_filter == SHOW_MERGED); + item->ignore = is_merged != (filter->merge == REF_FILTER_MERGED_INCLUDE); } - for (i = 0; i < ref_list.index; i++) { - struct ref_item *item = &ref_list.list[i]; + 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, ALL_REV_FLAGS); + clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS); } - if (verbose) - maxwidth = calc_maxwidth(&ref_list, strlen(remote_prefix)); - qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); + if (filter->verbose) + maxwidth = calc_maxwidth(&array, strlen(remote_prefix)); - for (i = 0; i < ref_list.index; i++) - print_ref_item(&ref_list.list[i], maxwidth, verbose, - abbrev, detached, remote_prefix); + qsort(array.items, array.nr, sizeof(struct ref_array_item *), ref_cmp); - free_ref_list(&ref_list); + for (i = 0; i < array.nr; i++) + print_ref_item(array.items[i], maxwidth, filter, remote_prefix); + + ref_array_clear(&array); } static void rename_branch(const char *oldname, const char *newname, int force) @@ -744,24 +711,6 @@ static void rename_branch(const char *oldname, const char *newname, int force) strbuf_release(&newsection); } -/* - * This function is duplicated in ref-filter. It will eventually be removed - * when we port branch.c to use ref-filter APIs. - */ -static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset) -{ - merge_filter = ((opt->long_name[0] == 'n') - ? SHOW_NOT_MERGED - : SHOW_MERGED); - if (unset) - merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */ - if (!arg) - arg = "HEAD"; - if (get_sha1(arg, merge_filter_ref)) - die(_("malformed object name %s"), arg); - return 0; -} - static const char edit_description[] = "BRANCH_DESCRIPTION"; static int edit_branch_description(const char *branch_name) @@ -801,17 +750,15 @@ 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 = 0, list = 0; - int verbose = 0, abbrev = -1, detached = 0; int reflog = 0, edit_description = 0; int quiet = 0, unset_upstream = 0; const char *new_upstream = NULL; enum branch_track track; - int kinds = REF_LOCAL_BRANCH; - struct commit_list *with_commit = NULL; + struct ref_filter filter; struct option options[] = { OPT_GROUP(N_("Generic options")), - OPT__VERBOSE(&verbose, + OPT__VERBOSE(&filter.verbose, N_("show hash and subject, give twice for upstream branch")), OPT__QUIET(&quiet, N_("suppress informational messages")), OPT_SET_INT('t', "track", &track, N_("set up tracking mode (see git-pull(1))"), @@ -821,15 +768,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_STRING('u', "set-upstream-to", &new_upstream, "upstream", "change the upstream info"), OPT_BOOL(0, "unset-upstream", &unset_upstream, "Unset the upstream info"), OPT__COLOR(&branch_use_color, N_("use colored output")), - OPT_SET_INT('r', "remotes", &kinds, N_("act on remote-tracking branches"), - REF_REMOTE_BRANCH), - OPT_CONTAINS(&with_commit, N_("print only branches that contain the commit")), - OPT_WITH(&with_commit, N_("print only branches that contain the commit")), - OPT__ABBREV(&abbrev), + OPT_SET_INT('r', "remotes", &filter.kind, N_("act on remote-tracking branches"), + FILTER_REFS_REMOTES), + OPT_CONTAINS(&filter.with_commit, N_("print only branches that contain the commit")), + OPT_WITH(&filter.with_commit, N_("print only branches that contain the commit")), + OPT__ABBREV(&filter.abbrev), OPT_GROUP(N_("Specific git-branch actions:")), - OPT_SET_INT('a', "all", &kinds, N_("list both remote-tracking and local branches"), - REF_REMOTE_BRANCH | REF_LOCAL_BRANCH), + OPT_SET_INT('a', "all", &filter.kind, N_("list both remote-tracking and local branches"), + FILTER_REFS_REMOTES | FILTER_REFS_BRANCHES), OPT_BIT('d', "delete", &delete, N_("delete fully merged branch"), 1), OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2), OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1), @@ -839,22 +786,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "edit-description", &edit_description, N_("edit the description for the branch")), 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"), - PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, - opt_parse_merge_filter, (intptr_t) "HEAD", - }, - { - OPTION_CALLBACK, 0, "merged", &merge_filter_ref, - N_("commit"), N_("print only merged branches"), - PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, - opt_parse_merge_filter, (intptr_t) "HEAD", - }, + 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_END(), }; + memset(&filter, 0, sizeof(filter)); + filter.kind = FILTER_REFS_BRANCHES; + filter.abbrev = -1; + if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_branch_usage, options); @@ -866,11 +807,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!head) die(_("Failed to resolve HEAD as a valid ref.")); if (!strcmp(head, "HEAD")) - detached = 1; + filter.detached = 1; else if (!skip_prefix(head, "refs/heads/", &head)) die(_("HEAD not found below refs/heads!")); - hashcpy(merge_filter_ref, head_sha1); - argc = parse_options(argc, argv, prefix, options, builtin_branch_usage, 0); @@ -878,17 +817,17 @@ 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 (with_commit || merge_filter != NO_FILTER) + if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE) list = 1; if (!!delete + !!rename + !!new_upstream + list + unset_upstream > 1) usage_with_options(builtin_branch_usage, options); - if (abbrev == -1) - abbrev = DEFAULT_ABBREV; + if (filter.abbrev == -1) + filter.abbrev = DEFAULT_ABBREV; finalize_colopts(&colopts, -1); - if (verbose) { + if (filter.verbose) { if (explicitly_enable_column(colopts)) die(_("--column and --verbose are incompatible")); colopts = 0; @@ -902,13 +841,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (delete) { if (!argc) die(_("branch name required")); - return delete_branches(argc, argv, delete > 1, kinds, quiet); + return delete_branches(argc, argv, delete > 1, filter.kind, quiet); } else if (list) { /* git branch --local also shows HEAD when it is detached */ - if (kinds & REF_LOCAL_BRANCH) - kinds |= REF_DETACHED_HEAD; - print_ref_list(kinds, detached, verbose, abbrev, - with_commit, argv); + if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached) + filter.kind |= FILTER_REFS_DETACHED_HEAD; + filter.name_patterns = argv; + print_ref_list(&filter); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); return 0; @@ -918,7 +857,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) struct strbuf branch_ref = STRBUF_INIT; if (!argc) { - if (detached) + if (filter.detached) die(_("Cannot give description to detached HEAD")); branch_name = head; } else if (argc == 1) @@ -1006,7 +945,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!branch) die(_("no such branch '%s'"), argv[0]); - if (kinds != REF_LOCAL_BRANCH) + if (filter.kind != FILTER_REFS_BRANCHES) die(_("-a and -r options to 'git branch' do not make sense with a branch name")); if (track == BRANCH_TRACK_OVERRIDE) diff --git a/ref-filter.h b/ref-filter.h index a5cfa5e677..fadebc5da6 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -36,6 +36,7 @@ struct ref_array_item { unsigned char objectname[20]; int flag; unsigned int kind; + int ignore : 1; /* To be removed in the next patch */ const char *symref; struct commit *commit; struct atom_value *value; @@ -45,6 +46,7 @@ struct ref_array_item { struct ref_array { int nr, alloc; struct ref_array_item **items; + struct rev_info *revs; }; struct ref_filter { @@ -60,9 +62,12 @@ struct ref_filter { struct commit *merge_commit; unsigned int with_commit_tag_algo : 1, - match_as_path : 1; + match_as_path : 1, + detached : 1; unsigned int kind, lines; + int abbrev, + verbose; }; struct ref_filter_cbdata {