diff options
author | Junio C Hamano <gitster@pobox.com> | 2010-01-30 16:03:10 -0800 |
---|---|---|
committer | Junio C Hamano <gitster@pobox.com> | 2010-01-30 16:03:10 -0800 |
commit | 00d3278c8534a8244ae3447189401111e017fd5d (patch) | |
tree | f1c19903bc10ffe4816642040080fb6cfd5da376 /builtin-branch.c | |
parent | b9b727ddb3c9e005bc4e9af0b990b6ef06d7f621 (diff) | |
parent | b319ef70a94731a5c6f18d07a49d5dda3f06f5d3 (diff) | |
download | git-00d3278c8534a8244ae3447189401111e017fd5d.tar.gz git-00d3278c8534a8244ae3447189401111e017fd5d.tar.xz |
Merge commit 'b319ef7' into jc/maint-fix-test-perm
* commit 'b319ef7': (8132 commits)
Add a small patch-mode testing library
git-apply--interactive: Refactor patch mode code
t8005: Nobody writes Russian in shift_jis
Fix severe breakage in "git-apply --whitespace=fix"
Update release notes for 1.6.4
After renaming a section, print any trailing variable definitions
Make section_name_match start on '[', and return the length on success
send-email: detect cycles in alias expansion
Show the presence of untracked files in the bash prompt.
SunOS grep does not understand -C<n> nor -e
Fix export_marks() error handling.
git repack: keep commits hidden by a graft
Add a test showing that 'git repack' throws away grafted-away parents
git branch: clean up detached branch handling
git branch: avoid unnecessary object lookups
git branch: fix performance problem
git svn: fix shallow clone when upstream revision is too new
do_one_ref(): null_sha1 check is not about broken ref
configure.ac: properly unset NEEDS_SSL_WITH_CRYPTO when sha1 func is missing
janitor: useless checks before free
...
Diffstat (limited to 'builtin-branch.c')
-rw-r--r-- | builtin-branch.c | 798 |
1 files changed, 395 insertions, 403 deletions
diff --git a/builtin-branch.c b/builtin-branch.c index 740828505..1a03d5f35 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -1,7 +1,7 @@ /* * Builtin "git branch" * - * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com> + * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com> * Based on git-branch.sh by Junio C Hamano. */ @@ -10,71 +10,83 @@ #include "refs.h" #include "commit.h" #include "builtin.h" +#include "remote.h" +#include "parse-options.h" +#include "branch.h" +#include "diff.h" +#include "revision.h" + +static const char * const builtin_branch_usage[] = { + "git branch [options] [-r | -a] [--merged | --no-merged]", + "git branch [options] [-l] [-f] <branchname> [<start-point>]", + "git branch [options] [-r] (-d | -D) <branchname>", + "git branch [options] (-m | -M) [<oldbranch>] <newbranch>", + NULL +}; -static const char builtin_branch_usage[] = - "git-branch [-r] (-d | -D) <branchname> | [--track | --no-track] [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [--color | --no-color] [-r | -a] [-v [--abbrev=<length> | --no-abbrev]]"; - -#define REF_UNKNOWN_TYPE 0x00 #define REF_LOCAL_BRANCH 0x01 #define REF_REMOTE_BRANCH 0x02 -#define REF_TAG 0x04 static const char *head; static unsigned char head_sha1[20]; -static int branch_track_remotes; - -static int branch_use_color; +static int branch_use_color = -1; static char branch_colors[][COLOR_MAXLEN] = { - "\033[m", /* reset */ - "", /* PLAIN (normal) */ - "\033[31m", /* REMOTE (red) */ - "", /* LOCAL (normal) */ - "\033[32m", /* CURRENT (green) */ + GIT_COLOR_RESET, + GIT_COLOR_NORMAL, /* PLAIN */ + GIT_COLOR_RED, /* REMOTE */ + GIT_COLOR_NORMAL, /* LOCAL */ + GIT_COLOR_GREEN, /* CURRENT */ }; enum color_branch { - COLOR_BRANCH_RESET = 0, - COLOR_BRANCH_PLAIN = 1, - COLOR_BRANCH_REMOTE = 2, - COLOR_BRANCH_LOCAL = 3, - COLOR_BRANCH_CURRENT = 4, + BRANCH_COLOR_RESET = 0, + BRANCH_COLOR_PLAIN = 1, + BRANCH_COLOR_REMOTE = 2, + BRANCH_COLOR_LOCAL = 3, + BRANCH_COLOR_CURRENT = 4, }; +static enum merge_filter { + NO_FILTER = 0, + SHOW_NOT_MERGED, + SHOW_MERGED, +} merge_filter; +static unsigned char merge_filter_ref[20]; + static int parse_branch_color_slot(const char *var, int ofs) { if (!strcasecmp(var+ofs, "plain")) - return COLOR_BRANCH_PLAIN; + return BRANCH_COLOR_PLAIN; if (!strcasecmp(var+ofs, "reset")) - return COLOR_BRANCH_RESET; + return BRANCH_COLOR_RESET; if (!strcasecmp(var+ofs, "remote")) - return COLOR_BRANCH_REMOTE; + return BRANCH_COLOR_REMOTE; if (!strcasecmp(var+ofs, "local")) - return COLOR_BRANCH_LOCAL; + return BRANCH_COLOR_LOCAL; if (!strcasecmp(var+ofs, "current")) - return COLOR_BRANCH_CURRENT; + return BRANCH_COLOR_CURRENT; die("bad config variable '%s'", var); } -int git_branch_config(const char *var, const char *value) +static int git_branch_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "color.branch")) { - branch_use_color = git_config_colorbool(var, value); + branch_use_color = git_config_colorbool(var, value, -1); return 0; } if (!prefixcmp(var, "color.branch.")) { int slot = parse_branch_color_slot(var, 13); + if (!value) + return config_error_nonbool(var); color_parse(value, var, branch_colors[slot]); return 0; } - if (!strcmp(var, "branch.autosetupmerge")) - branch_track_remotes = git_config_bool(var, value); - - return git_default_config(var, value); + return git_color_default_config(var, value, cb); } -const char *branch_get_color(enum color_branch ix) +static const char *branch_get_color(enum color_branch ix) { - if (branch_use_color) + if (branch_use_color > 0) return branch_colors[ix]; return ""; } @@ -87,6 +99,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) const char *fmt, *remote; int i; int ret = 0; + struct strbuf bname = STRBUF_INIT; switch (kinds) { case REF_REMOTE_BRANCH: @@ -107,21 +120,21 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) if (!head_rev) die("Couldn't look up commit object for HEAD"); } - for (i = 0; i < argc; i++) { - if (kinds == REF_LOCAL_BRANCH && !strcmp(head, argv[i])) { + for (i = 0; i < argc; i++, strbuf_release(&bname)) { + strbuf_branchname(&bname, argv[i]); + if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) { error("Cannot delete the branch '%s' " - "which you are currently on.", argv[i]); + "which you are currently on.", bname.buf); ret = 1; continue; } - if (name) - free(name); + free(name); - name = xstrdup(mkpath(fmt, argv[i])); + name = xstrdup(mkpath(fmt, bname.buf)); if (!resolve_ref(name, sha1, 1, NULL)) { error("%sbranch '%s' not found.", - remote, argv[i]); + remote, bname.buf); ret = 1; continue; } @@ -140,64 +153,112 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) if (!force && !in_merge_bases(rev, &head_rev, 1)) { - error("The branch '%s' is not a strict subset of " - "your current HEAD.\n" - "If you are sure you want to delete it, " - "run 'git branch -D %s'.", argv[i], argv[i]); + error("The branch '%s' is not an ancestor of " + "your current HEAD.\n" + "If you are sure you want to delete it, " + "run 'git branch -D %s'.", bname.buf, bname.buf); ret = 1; continue; } - if (delete_ref(name, sha1)) { + if (delete_ref(name, sha1, 0)) { error("Error deleting %sbranch '%s'", remote, - argv[i]); + bname.buf); ret = 1; - } else - printf("Deleted %sbranch %s.\n", remote, argv[i]); - + } else { + struct strbuf buf = STRBUF_INIT; + printf("Deleted %sbranch %s (was %s).\n", remote, + bname.buf, + find_unique_abbrev(sha1, DEFAULT_ABBREV)); + strbuf_addf(&buf, "branch.%s", bname.buf); + if (git_config_rename_section(buf.buf, NULL) < 0) + warning("Update of config-file failed"); + strbuf_release(&buf); + } } - if (name) - free(name); + free(name); return(ret); } struct ref_item { char *name; - unsigned int kind; - unsigned char sha1[20]; + char *dest; + unsigned int kind, len; + struct commit *commit; }; struct ref_list { - int index, alloc, maxwidth; + struct rev_info revs; + int index, alloc, maxwidth, 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]; + int flag; + const char *dst, *cp; + + dst = resolve_ref(src, sha1, 0, &flag); + if (!(dst && (flag & REF_ISSYMREF))) + return NULL; + if (prefix && (cp = skip_prefix(dst, prefix))) + dst = cp; + return xstrdup(dst); +} + static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { struct ref_list *ref_list = (struct ref_list*)(cb_data); struct ref_item *newitem; - int kind = REF_UNKNOWN_TYPE; - int len; + struct commit *commit; + int kind, i; + const char *prefix, *orig_refname = refname; + + static struct { + int kind; + const char *prefix; + int pfxlen; + } ref_kind[] = { + { REF_LOCAL_BRANCH, "refs/heads/", 11 }, + { REF_REMOTE_BRANCH, "refs/remotes/", 13 }, + }; /* Detect kind */ - if (!prefixcmp(refname, "refs/heads/")) { - kind = REF_LOCAL_BRANCH; - refname += 11; - } else if (!prefixcmp(refname, "refs/remotes/")) { - kind = REF_REMOTE_BRANCH; - refname += 13; - } else if (!prefixcmp(refname, "refs/tags/")) { - kind = REF_TAG; - refname += 10; + 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 (ARRAY_SIZE(ref_kind) <= i) + return 0; /* Don't add types the caller doesn't want */ if ((kind & ref_list->kinds) == 0) return 0; + 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) { ref_list->alloc = alloc_nr(ref_list->alloc); @@ -209,10 +270,15 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, newitem = &(ref_list->list[ref_list->index++]); newitem->name = xstrdup(refname); newitem->kind = kind; - hashcpy(newitem->sha1, sha1); - len = strlen(newitem->name); - if (len > ref_list->maxwidth) - ref_list->maxwidth = len; + newitem->commit = commit; + newitem->len = strlen(refname); + newitem->dest = resolve_symref(orig_refname, prefix); + /* adjust for "remotes/" */ + if (newitem->kind == REF_REMOTE_BRANCH && + ref_list->kinds != REF_REMOTE_BRANCH) + newitem->len += 8; + if (newitem->len > ref_list->maxwidth) + ref_list->maxwidth = newitem->len; return 0; } @@ -221,8 +287,10 @@ static void free_ref_list(struct ref_list *ref_list) { int i; - for (i = 0; i < ref_list->index; 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); } @@ -236,418 +304,342 @@ static int ref_cmp(const void *r1, const void *r2) return strcmp(c1->name, c2->name); } +static void fill_tracking_info(struct strbuf *stat, const char *branch_name, + int show_upstream_ref) +{ + int ours, theirs; + struct branch *branch = branch_get(branch_name); + + if (!stat_tracking_info(branch, &ours, &theirs)) { + if (branch && branch->merge && branch->merge[0]->dst && + show_upstream_ref) + strbuf_addf(stat, "[%s] ", + shorten_unambiguous_ref(branch->merge[0]->dst, 0)); + return; + } + + strbuf_addch(stat, '['); + if (show_upstream_ref) + strbuf_addf(stat, "%s: ", + shorten_unambiguous_ref(branch->merge[0]->dst, 0)); + if (!ours) + strbuf_addf(stat, "behind %d] ", theirs); + else if (!theirs) + strbuf_addf(stat, "ahead %d] ", ours); + else + strbuf_addf(stat, "ahead %d, behind %d] ", ours, theirs); +} + +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 print_ref_item(struct ref_item *item, int maxwidth, int verbose, - int abbrev, int current) + int abbrev, int current, char *prefix) { char c; int color; - struct commit *commit; - char subject[256]; + struct commit *commit = item->commit; + struct strbuf out = STRBUF_INIT, name = STRBUF_INIT; + + if (!matches_merge_filter(commit)) + return; switch (item->kind) { case REF_LOCAL_BRANCH: - color = COLOR_BRANCH_LOCAL; + color = BRANCH_COLOR_LOCAL; break; case REF_REMOTE_BRANCH: - color = COLOR_BRANCH_REMOTE; + color = BRANCH_COLOR_REMOTE; break; default: - color = COLOR_BRANCH_PLAIN; + color = BRANCH_COLOR_PLAIN; break; } c = ' '; if (current) { c = '*'; - color = COLOR_BRANCH_CURRENT; + color = BRANCH_COLOR_CURRENT; } - if (verbose) { - commit = lookup_commit(item->sha1); - if (commit && !parse_commit(commit)) - pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, - subject, sizeof(subject), 0, - NULL, NULL, 0); - else - strcpy(subject, " **** invalid ref ****"); - printf("%c %s%-*s%s %s %s\n", c, branch_get_color(color), - maxwidth, item->name, - branch_get_color(COLOR_BRANCH_RESET), - find_unique_abbrev(item->sha1, abbrev), subject); - } else { - printf("%c %s%s%s\n", c, branch_get_color(color), item->name, - branch_get_color(COLOR_BRANCH_RESET)); - } -} - -static void print_ref_list(int kinds, int detached, int verbose, int abbrev) -{ - int i; - struct ref_list ref_list; - - memset(&ref_list, 0, sizeof(ref_list)); - ref_list.kinds = kinds; - for_each_ref(append_ref, &ref_list); - - qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); + strbuf_addf(&name, "%s%s", prefix, item->name); + if (verbose) + strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color), + maxwidth, name.buf, + branch_get_color(BRANCH_COLOR_RESET)); + else + 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) { + struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; + const char *sub = " **** invalid ref ****"; + + commit = item->commit; + if (commit && !parse_commit(commit)) { + pretty_print_commit(CMIT_FMT_ONELINE, commit, + &subject, 0, NULL, NULL, 0, 0); + sub = subject.buf; + } - detached = (detached && (kinds & REF_LOCAL_BRANCH)); - if (detached) { - struct ref_item item; - item.name = xstrdup("(no branch)"); - item.kind = REF_LOCAL_BRANCH; - hashcpy(item.sha1, head_sha1); - if (strlen(item.name) > ref_list.maxwidth) - ref_list.maxwidth = strlen(item.name); - print_ref_item(&item, ref_list.maxwidth, verbose, abbrev, 1); - free(item.name); - } + if (item->kind == REF_LOCAL_BRANCH) + fill_tracking_info(&stat, item->name, verbose > 1); - for (i = 0; i < ref_list.index; i++) { - int current = !detached && - (ref_list.list[i].kind == REF_LOCAL_BRANCH) && - !strcmp(ref_list.list[i].name, head); - print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose, - abbrev, current); + strbuf_addf(&out, " %s %s%s", + find_unique_abbrev(item->commit->object.sha1, abbrev), + stat.buf, sub); + strbuf_release(&stat); + strbuf_release(&subject); } - - free_ref_list(&ref_list); + printf("%s\n", out.buf); + strbuf_release(&name); + strbuf_release(&out); } -static char *config_repo; -static char *config_remote; -static const char *start_ref; -static int start_len; -static int base_len; - -static int get_remote_branch_name(const char *value) +static int calc_maxwidth(struct ref_list *refs) { - const char *colon; - const char *end; - - if (*value == '+') - value++; - - colon = strchr(value, ':'); - if (!colon) - return 0; - - end = value + strlen(value); - - /* Try an exact match first. */ - if (!strcmp(colon + 1, start_ref)) { - /* Truncate the value before the colon. */ - nfasprintf(&config_repo, "%.*s", colon - value, value); - return 1; - } - - /* Try with a wildcard match now. */ - if (end - value > 2 && end[-2] == '/' && end[-1] == '*' && - colon - value > 2 && colon[-2] == '/' && colon[-1] == '*' && - (end - 2) - (colon + 1) == base_len && - !strncmp(colon + 1, start_ref, base_len)) { - /* Replace the star with the remote branch name. */ - nfasprintf(&config_repo, "%.*s%s", - (colon - 2) - value, value, - start_ref + base_len); - return 1; + int i, w = 0; + for (i = 0; i < refs->index; i++) { + if (!matches_merge_filter(refs->list[i].commit)) + continue; + if (refs->list[i].len > w) + w = refs->list[i].len; } - - return 0; + return w; } -static int get_remote_config(const char *key, const char *value) -{ - const char *var; - if (prefixcmp(key, "remote.")) - return 0; - - var = strrchr(key, '.'); - if (var == key + 6) - return 0; - - if (!strcmp(var, ".fetch") && get_remote_branch_name(value)) - nfasprintf(&config_remote, "%.*s", var - (key + 7), key + 7); - - return 0; -} -static void set_branch_merge(const char *name, const char *config_remote, - const char *config_repo) +static void show_detached(struct ref_list *ref_list) { - char key[1024]; - if (sizeof(key) <= - snprintf(key, sizeof(key), "branch.%s.remote", name)) - die("what a long branch name you have!"); - git_config_set(key, config_remote); - - /* - * We do not have to check if we have enough space for - * the 'merge' key, since it's shorter than the - * previous 'remote' key, which we already checked. - */ - snprintf(key, sizeof(key), "branch.%s.merge", name); - git_config_set(key, config_repo); -} - -static void set_branch_defaults(const char *name, const char *real_ref) -{ - const char *slash = strrchr(real_ref, '/'); + struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1); - if (!slash) - return; - - start_ref = real_ref; - start_len = strlen(real_ref); - base_len = slash - real_ref; - git_config(get_remote_config); - if (!config_repo && !config_remote && - !prefixcmp(real_ref, "refs/heads/")) { - set_branch_merge(name, ".", real_ref); - printf("Branch %s set up to track local branch %s.\n", - name, real_ref); - } - - if (config_repo && config_remote) { - set_branch_merge(name, config_remote, config_repo); - printf("Branch %s set up to track remote branch %s.\n", - name, real_ref); + 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); } - - if (config_repo) - free(config_repo); - if (config_remote) - free(config_remote); } -static void create_branch(const char *name, const char *start_name, - int force, int reflog, int track) +static void print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit) { - struct ref_lock *lock; - struct commit *commit; - unsigned char sha1[20]; - char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20]; - int forcing = 0; - - snprintf(ref, sizeof ref, "refs/heads/%s", name); - if (check_ref_format(ref)) - die("'%s' is not a valid branch name.", name); - - if (resolve_ref(ref, sha1, 1, NULL)) { - if (!force) - die("A branch named '%s' already exists.", name); - else if (!is_bare_repository() && !strcmp(head, name)) - die("Cannot force update the current branch."); - forcing = 1; - } - - real_ref = NULL; - if (get_sha1(start_name, sha1)) - die("Not a valid object name: '%s'.", start_name); + int i; + struct ref_list ref_list; - switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) { - case 0: - /* Not branching from any existing branch */ - real_ref = NULL; - break; - case 1: - /* Unique completion -- good */ - break; - default: - die("Ambiguous object name: '%s'.", start_name); - break; + 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_rawref(append_ref, &ref_list); + if (merge_filter != NO_FILTER) { + struct commit *filter; + filter = lookup_commit_reference_gently(merge_filter_ref, 0); + filter->object.flags |= UNINTERESTING; + add_pending_object(&ref_list.revs, + (struct object *) filter, ""); + ref_list.revs.limited = 1; + prepare_revision_walk(&ref_list.revs); + if (verbose) + ref_list.maxwidth = calc_maxwidth(&ref_list); } - if ((commit = lookup_commit_reference(sha1)) == NULL) - die("Not a valid branch point: '%s'.", start_name); - hashcpy(sha1, commit->object.sha1); - - lock = lock_any_ref_for_update(ref, NULL); - if (!lock) - die("Failed to lock ref for update: %s.", strerror(errno)); - - if (reflog) - log_all_ref_updates = 1; - - if (forcing) - snprintf(msg, sizeof msg, "branch: Reset from %s", - start_name); - else - snprintf(msg, sizeof msg, "branch: Created from %s", - start_name); + qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); - /* When branching off a remote branch, set up so that git-pull - automatically merges from there. So far, this is only done for - remotes registered via .git/config. */ - if (real_ref && track) - set_branch_defaults(name, real_ref); + detached = (detached && (kinds & REF_LOCAL_BRANCH)); + if (detached) + show_detached(&ref_list); - if (write_ref_sha1(lock, sha1, msg) < 0) - die("Failed to write ref: %s.", strerror(errno)); + for (i = 0; i < ref_list.index; i++) { + int current = !detached && + (ref_list.list[i].kind == REF_LOCAL_BRANCH) && + !strcmp(ref_list.list[i].name, head); + char *prefix = (kinds != REF_REMOTE_BRANCH && + ref_list.list[i].kind == REF_REMOTE_BRANCH) + ? "remotes/" : ""; + print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose, + abbrev, current, prefix); + } - if (real_ref) - free(real_ref); + free_ref_list(&ref_list); } static void rename_branch(const char *oldname, const char *newname, int force) { - char oldref[PATH_MAX], newref[PATH_MAX], logmsg[PATH_MAX*2 + 100]; + struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT; unsigned char sha1[20]; - char oldsection[PATH_MAX], newsection[PATH_MAX]; + struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT; + int recovery = 0; if (!oldname) die("cannot rename the current branch while not on any."); - if (snprintf(oldref, sizeof(oldref), "refs/heads/%s", oldname) > sizeof(oldref)) - die("Old branchname too long"); - - if (check_ref_format(oldref)) - die("Invalid branch name: %s", oldref); - - if (snprintf(newref, sizeof(newref), "refs/heads/%s", newname) > sizeof(newref)) - die("New branchname too long"); + if (strbuf_check_branch_ref(&oldref, oldname)) { + /* + * Bad name --- this could be an attempt to rename a + * ref that we used to allow to be created by accident. + */ + if (resolve_ref(oldref.buf, sha1, 1, NULL)) + recovery = 1; + else + die("Invalid branch name: '%s'", oldname); + } - if (check_ref_format(newref)) - die("Invalid branch name: %s", newref); + if (strbuf_check_branch_ref(&newref, newname)) + die("Invalid branch name: '%s'", newname); - if (resolve_ref(newref, sha1, 1, NULL) && !force) - die("A branch named '%s' already exists.", newname); + if (resolve_ref(newref.buf, sha1, 1, NULL) && !force) + die("A branch named '%s' already exists.", newref.buf + 11); - snprintf(logmsg, sizeof(logmsg), "Branch: renamed %s to %s", - oldref, newref); + strbuf_addf(&logmsg, "Branch: renamed %s to %s", + oldref.buf, newref.buf); - if (rename_ref(oldref, newref, logmsg)) + if (rename_ref(oldref.buf, newref.buf, logmsg.buf)) die("Branch rename failed"); + strbuf_release(&logmsg); + + if (recovery) + warning("Renamed a misnamed branch '%s' away", oldref.buf + 11); /* no need to pass logmsg here as HEAD didn't really move */ - if (!strcmp(oldname, head) && create_symref("HEAD", newref, NULL)) + if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL)) die("Branch renamed to %s, but HEAD is not updated!", newname); - snprintf(oldsection, sizeof(oldsection), "branch.%s", oldref + 11); - snprintf(newsection, sizeof(newsection), "branch.%s", newref + 11); - if (git_config_rename_section(oldsection, newsection) < 0) + strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11); + strbuf_release(&oldref); + strbuf_addf(&newsection, "branch.%s", newref.buf + 11); + strbuf_release(&newref); + if (git_config_rename_section(oldsection.buf, newsection.buf) < 0) die("Branch is renamed, but update of config-file failed"); + strbuf_release(&oldsection); + strbuf_release(&newsection); +} + +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; } int cmd_branch(int argc, const char **argv, const char *prefix) { - int delete = 0, force_delete = 0, force_create = 0; - int rename = 0, force_rename = 0; + int delete = 0, rename = 0, force_create = 0; int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0; - int reflog = 0, track; + int reflog = 0; + enum branch_track track; int kinds = REF_LOCAL_BRANCH; - int i; - - git_config(git_branch_config); - track = branch_track_remotes; - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "--track")) { - track = 1; - continue; - } - if (!strcmp(arg, "--no-track")) { - track = 0; - continue; - } - if (!strcmp(arg, "-d")) { - delete = 1; - continue; - } - if (!strcmp(arg, "-D")) { - delete = 1; - force_delete = 1; - continue; - } - if (!strcmp(arg, "-f")) { - force_create = 1; - continue; - } - if (!strcmp(arg, "-m")) { - rename = 1; - continue; - } - if (!strcmp(arg, "-M")) { - rename = 1; - force_rename = 1; - continue; - } - if (!strcmp(arg, "-r")) { - kinds = REF_REMOTE_BRANCH; - continue; - } - if (!strcmp(arg, "-a")) { - kinds = REF_REMOTE_BRANCH | REF_LOCAL_BRANCH; - continue; - } - if (!strcmp(arg, "-l")) { - reflog = 1; - continue; - } - if (!prefixcmp(arg, "--no-abbrev")) { - abbrev = 0; - continue; - } - if (!prefixcmp(arg, "--abbrev=")) { - abbrev = strtoul(arg + 9, NULL, 10); - if (abbrev < MINIMUM_ABBREV) - abbrev = MINIMUM_ABBREV; - else if (abbrev > 40) - abbrev = 40; - continue; - } - if (!strcmp(arg, "-v")) { - verbose = 1; - continue; - } - if (!strcmp(arg, "--color")) { - branch_use_color = 1; - continue; - } - if (!strcmp(arg, "--no-color")) { - branch_use_color = 0; - continue; - } - usage(builtin_branch_usage); - } - - if ((delete && rename) || (delete && force_create) || - (rename && force_create)) - usage(builtin_branch_usage); - - head = xstrdup(resolve_ref("HEAD", head_sha1, 0, NULL)); + struct commit_list *with_commit = NULL; + + struct option options[] = { + OPT_GROUP("Generic options"), + OPT__VERBOSE(&verbose), + OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))", + BRANCH_TRACK_EXPLICIT), + OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"), + OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches", + REF_REMOTE_BRANCH), + { + OPTION_CALLBACK, 0, "contains", &with_commit, "commit", + "print only branches that contain the commit", + PARSE_OPT_LASTARG_DEFAULT, + parse_opt_with_commit, (intptr_t)"HEAD", + }, + { + OPTION_CALLBACK, 0, "with", &with_commit, "commit", + "print only branches that contain the commit", + PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT, + parse_opt_with_commit, (intptr_t) "HEAD", + }, + OPT__ABBREV(&abbrev), + + OPT_GROUP("Specific git-branch actions:"), + OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches", + REF_REMOTE_BRANCH | REF_LOCAL_BRANCH), + OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1), + OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2), + 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)"), + { + OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref, + "commit", "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, + "commit", "print only merged branches", + PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, + opt_parse_merge_filter, (intptr_t) "HEAD", + }, + OPT_END(), + }; + + git_config(git_branch_config, NULL); + + if (branch_use_color == -1) + branch_use_color = git_use_color_default; + + track = git_branch_track; + + head = resolve_ref("HEAD", head_sha1, 0, NULL); if (!head) die("Failed to resolve HEAD as a valid ref."); + head = xstrdup(head); if (!strcmp(head, "HEAD")) { detached = 1; - } - else { + } else { if (prefixcmp(head, "refs/heads/")) die("HEAD not found below refs/heads!"); head += 11; } + hashcpy(merge_filter_ref, head_sha1); + + argc = parse_options(argc, argv, prefix, options, builtin_branch_usage, + 0); + if (!!delete + !!rename + !!force_create > 1) + usage_with_options(builtin_branch_usage, options); if (delete) - return delete_branches(argc - i, argv + i, force_delete, kinds); - else if (i == argc) - print_ref_list(kinds, detached, verbose, abbrev); - else if (rename && (i == argc - 1)) - rename_branch(head, argv[i], force_rename); - else if (rename && (i == argc - 2)) - rename_branch(argv[i], argv[i + 1], force_rename); - else if (i == argc - 1 || i == argc - 2) - create_branch(argv[i], (i == argc - 2) ? argv[i+1] : head, + return delete_branches(argc, argv, delete > 1, kinds); + else if (argc == 0) + print_ref_list(kinds, detached, verbose, abbrev, with_commit); + else if (rename && (argc == 1)) + rename_branch(head, argv[0], rename > 1); + else if (rename && (argc == 2)) + rename_branch(argv[0], argv[1], rename > 1); + else if (argc <= 2) + create_branch(head, argv[0], (argc == 2) ? argv[1] : head, force_create, reflog, track); else - usage(builtin_branch_usage); + usage_with_options(builtin_branch_usage, options); return 0; } |