Merge branch 'mg/branch-d-m-f'
authorJunio C Hamano <gitster@pobox.com>
Mon, 22 Dec 2014 20:27:36 +0000 (12:27 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 22 Dec 2014 20:27:36 +0000 (12:27 -0800)
"git branch -d" (delete) and "git branch -m" (move) learned to
honor "-f" (force) flag; unlike many other subcommands, the way to
force these have been with separate "-D/-M" options, which was
inconsistent.

* mg/branch-d-m-f:
branch: allow -f with -m and -d
t3200-branch: test -M

1  2 
builtin/branch.c
t/t3200-branch.sh
diff --combined builtin/branch.c
index 3b79c5087fbf1157d1bdfa2cccc99547f8abdb09,a2a03365c4a1c68b4118782c3b98bd6133eced52..dc6f0b266c9cf621486b9dba111c2e7b37d3bc1b
@@@ -62,40 -62,39 +62,40 @@@ static unsigned char merge_filter_ref[2
  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);
  }
@@@ -130,8 -129,7 +130,8 @@@ static int branch_merged(int kind, cons
                    branch->merge[0] &&
                    branch->merge[0]->dst &&
                    (reference_name = reference_name_to_free =
 -                   resolve_refdup(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
 +                   resolve_refdup(branch->merge[0]->dst, RESOLVE_REF_READING,
 +                                  sha1, NULL)) != NULL)
                        reference_rev = lookup_commit_reference(sha1);
        }
        if (!reference_rev)
@@@ -235,12 -233,9 +235,12 @@@ static int delete_branches(int argc, co
                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.")
                              : _("branch '%s' not found."), bname.buf);
                        continue;
                }
  
 -              if (!(flags & REF_ISSYMREF) &&
 +              if (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&
                    check_branch_commit(bname.buf, name, sha1, head_rev, kinds,
                                        force)) {
                        ret = 1;
                               ? _("Deleted remote 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);
@@@ -285,7 -280,6 +285,7 @@@ struct ref_item 
        char *dest;
        unsigned int kind, width;
        struct commit *commit;
 +      int ignore;
  };
  
  struct ref_list {
@@@ -302,7 -296,7 +302,7 @@@ static char *resolve_symref(const char 
        int flag;
        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)
@@@ -340,18 -334,20 +340,18 @@@ static int append_ref(const char *refna
        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;
        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)
@@@ -489,6 -484,17 +489,6 @@@ static void fill_tracking_info(struct s
        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)
  {
@@@ -516,9 -522,10 +516,9 @@@ static void print_ref_item(struct ref_i
  {
        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) {
@@@ -568,7 -575,7 +568,7 @@@ static int calc_maxwidth(struct ref_lis
  {
        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;
@@@ -611,7 -618,6 +611,7 @@@ static void show_detached(struct ref_li
                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, "");
@@@ -647,23 -653,7 +647,23 @@@ static int print_ref_list(int kinds, in
                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);
        }
@@@ -800,7 -790,7 +800,7 @@@ static int edit_branch_description(cons
  
  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;
                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"),
  
        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);
  
  
        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);
  
                colopts = 0;
        }
  
+       if (force) {
+               delete *= 2;
+               rename *= 2;
+       }
        if (delete) {
                if (!argc)
                        die(_("branch name required"));
  
                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
diff --combined t/t3200-branch.sh
index 432921b6b81684313c6b0041a288749c30fdee8e,e7bbb8f0ec4c90dc0e14e9da6528960287780c91..ddea49808d063f6c169cf8078f5698e34c2ba803
@@@ -97,6 -97,20 +97,20 @@@ test_expect_success 'git branch -m o/o 
        test_must_fail git branch -m o/o o
  '
  
+ test_expect_success 'git branch -m o/q o/p should fail when o/p exists' '
+       git branch o/q &&
+       test_must_fail git branch -m o/q o/p
+ '
+ test_expect_success 'git branch -M o/q o/p should work when o/p exists' '
+       git branch -M o/q o/p
+ '
+ test_expect_success 'git branch -m -f o/q o/p should work when o/p exists' '
+       git branch o/q &&
+       git branch -m -f o/q o/p
+ '
  test_expect_success 'git branch -m q r/q should fail when r exists' '
        git branch q &&
        git branch r &&
@@@ -285,15 -299,6 +299,15 @@@ test_expect_success 'deleting a danglin
        test_i18ncmp expect actual
  '
  
 +test_expect_success 'deleting a self-referential symref' '
 +      git symbolic-ref refs/heads/self-reference refs/heads/self-reference &&
 +      test_path_is_file .git/refs/heads/self-reference &&
 +      echo "Deleted branch self-reference (was refs/heads/self-reference)." >expect &&
 +      git branch -d self-reference >actual &&
 +      test_path_is_missing .git/refs/heads/self-reference &&
 +      test_i18ncmp expect actual
 +'
 +
  test_expect_success 'renaming a symref is not allowed' '
        git symbolic-ref refs/heads/master2 refs/heads/master &&
        test_must_fail git branch -m master2 master3 &&