Merge branch 'cn/branch-set-upstream-to'
authorJunio C Hamano <gitster@pobox.com>
Mon, 10 Sep 2012 22:43:07 +0000 (15:43 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 10 Sep 2012 22:43:07 +0000 (15:43 -0700)
"git branch --set-upstream origin/master" is a common mistake to
create a local branch 'origin/master' and set it to integrate with
the current branch. With a plan to deprecate this option, introduce
"git branch (-u|--set-upstream-to) origin/master" that sets the
current branch to integrate with 'origin/master' remote tracking
branch.

* cn/branch-set-upstream-to:
branch: deprecate --set-upstream and show help if we detect possible mistaken use
branch: add --unset-upstream option
branch: introduce --set-upstream-to

1  2 
builtin/branch.c
t/t3200-branch.sh
diff --combined builtin/branch.c
index 297be4ec4075e7a5877366217d780d6c29b3271b,5e95e3568a33c8d5f1cea02a882cae43892c2e17..e61b0ece2199f0d2b637cd2ce879105280c38d21
  #include "revision.h"
  #include "string-list.h"
  #include "column.h"
 +#include "utf8.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>",
 +      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>"),
        NULL
  };
  
@@@ -250,7 -249,7 +250,7 @@@ static int delete_branches(int argc, co
  struct ref_item {
        char *name;
        char *dest;
 -      unsigned int kind, len;
 +      unsigned int kind, width;
        struct commit *commit;
  };
  
@@@ -355,14 -354,14 +355,14 @@@ static int append_ref(const char *refna
        newitem->name = xstrdup(refname);
        newitem->kind = kind;
        newitem->commit = commit;
 -      newitem->len = strlen(refname);
 +      newitem->width = utf8_strwidth(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;
 +              newitem->width += 8;
 +      if (newitem->width > ref_list->maxwidth)
 +              ref_list->maxwidth = newitem->width;
  
        return 0;
  }
@@@ -491,12 -490,11 +491,12 @@@ static void print_ref_item(struct ref_i
        }
  
        strbuf_addf(&name, "%s%s", prefix, item->name);
 -      if (verbose)
 +      if (verbose) {
 +              int utf8_compensation = strlen(name.buf) - utf8_strwidth(name.buf);
                strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color),
 -                          maxwidth, name.buf,
 +                          maxwidth + utf8_compensation, name.buf,
                            branch_get_color(BRANCH_COLOR_RESET));
 -      else
 +      else
                strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color),
                            name.buf, branch_get_color(BRANCH_COLOR_RESET));
  
@@@ -521,8 -519,8 +521,8 @@@ static int calc_maxwidth(struct ref_lis
        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;
 +              if (refs->list[i].width > w)
 +                      w = refs->list[i].width;
        }
        return w;
  }
@@@ -535,12 -533,12 +535,12 @@@ static void show_detached(struct ref_li
        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.width = utf8_strwidth(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;
 +              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, "");
                free(item.name);
        }
@@@ -714,62 -712,65 +714,65 @@@ int cmd_branch(int argc, const char **a
        int delete = 0, rename = 0, force_create = 0, list = 0;
        int verbose = 0, abbrev = -1, detached = 0;
        int reflog = 0, edit_description = 0;
-       int quiet = 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 option options[] = {
 -              OPT_GROUP("Generic options"),
 +              OPT_GROUP(N_("Generic options")),
                OPT__VERBOSE(&verbose,
 -                      "show hash and subject, give twice for upstream branch"),
 -              OPT__QUIET(&quiet, "suppress informational messages"),
 -              OPT_SET_INT('t', "track",  &track, "set up tracking mode (see git-pull(1))",
 +                      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))"),
                        BRANCH_TRACK_EXPLICIT),
 -              OPT_SET_INT( 0, "set-upstream",  &track, "change upstream info",
 +              OPT_SET_INT( 0, "set-upstream",  &track, N_("change upstream info"),
                        BRANCH_TRACK_OVERRIDE),
 -              OPT__COLOR(&branch_use_color, "use colored output"),
 -              OPT_SET_INT('r', "remotes",     &kinds, "act on remote-tracking branches",
+               OPT_STRING('u', "set-upstream-to", &new_upstream, "upstream", "change the upstream info"),
+               OPT_BOOLEAN(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),
                {
 -                      OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
 -                      "print only branches that contain the commit",
 +                      OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
 +                      N_("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",
 +                      OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"),
 +                      N_("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', "all", &kinds, "list both remote-tracking and local branches",
 +              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_BIT('d', "delete", &delete, "delete fully merged branch", 1),
 -              OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2),
 -              OPT_BIT('m', "move", &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(0, "list", &list, "list branch names"),
 -              OPT_BOOLEAN('l', "create-reflog", &reflog, "create the branch's reflog"),
 +              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),
 +              OPT_BIT('M', NULL, &rename, N_("move/rename a branch, even if target exists"), 2),
 +              OPT_BOOLEAN(0, "list", &list, N_("list branch names")),
 +              OPT_BOOLEAN('l', "create-reflog", &reflog, N_("create the branch's reflog")),
                OPT_BOOLEAN(0, "edit-description", &edit_description,
 -                          "edit the description for the branch"),
 -              OPT__FORCE(&force_create, "force creation (when already exists)"),
 +                          N_("edit the description for the branch")),
 +              OPT__FORCE(&force_create, N_("force creation (when already exists)")),
                {
                        OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
 -                      "commit", "print only not merged branches",
 +                      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,
 -                      "commit", "print only merged branches",
 +                      N_("commit"), N_("print only merged branches"),
                        PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
                        opt_parse_merge_filter, (intptr_t) "HEAD",
                },
 -              OPT_COLUMN(0, "column", &colopts, "list branches in columns"),
 +              OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")),
                OPT_END(),
        };
  
        argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
                             0);
  
-       if (!delete && !rename && !edit_description && argc == 0)
+       if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0)
                list = 1;
  
-       if (!!delete + !!rename + !!force_create + !!list > 1)
+       if (!!delete + !!rename + !!force_create + !!list + !!new_upstream + !!unset_upstream > 1)
                usage_with_options(builtin_branch_usage, options);
  
        if (abbrev == -1)
                        rename_branch(argv[0], argv[1], rename > 1);
                else
                        usage_with_options(builtin_branch_usage, options);
+       } else if (new_upstream) {
+               struct branch *branch = branch_get(argv[0]);
+               if (!ref_exists(branch->refname))
+                       die(_("branch '%s' does not exist"), branch->name);
+               /*
+                * create_branch takes care of setting up the tracking
+                * info and making sure new_upstream is correct
+                */
+               create_branch(head, branch->name, new_upstream, 0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);
+       } else if (unset_upstream) {
+               struct branch *branch = branch_get(argv[0]);
+               struct strbuf buf = STRBUF_INIT;
+               if (!branch_has_merge_config(branch)) {
+                       die(_("Branch '%s' has no upstream information"), branch->name);
+               }
+               strbuf_addf(&buf, "branch.%s.remote", branch->name);
+               git_config_set_multivar(buf.buf, NULL, NULL, 1);
+               strbuf_reset(&buf);
+               strbuf_addf(&buf, "branch.%s.merge", branch->name);
+               git_config_set_multivar(buf.buf, NULL, NULL, 1);
+               strbuf_release(&buf);
        } else if (argc > 0 && argc <= 2) {
+               struct branch *branch = branch_get(argv[0]);
+               int branch_existed = 0, remote_tracking = 0;
+               struct strbuf buf = STRBUF_INIT;
                if (kinds != REF_LOCAL_BRANCH)
                        die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
+               if (track == BRANCH_TRACK_OVERRIDE)
+                       fprintf(stderr, _("The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to\n"));
+               strbuf_addf(&buf, "refs/remotes/%s", branch->name);
+               remote_tracking = ref_exists(buf.buf);
+               strbuf_release(&buf);
+               branch_existed = ref_exists(branch->refname);
                create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
                              force_create, reflog, 0, quiet, track);
+               /*
+                * We only show the instructions if the user gave us
+                * one branch which doesn't exist locally, but is the
+                * name of a remote-tracking branch.
+                */
+               if (argc == 1 && track == BRANCH_TRACK_OVERRIDE &&
+                   !branch_existed && remote_tracking) {
+                       fprintf(stderr, _("\nIf you wanted to make '%s' track '%s', do this:\n\n"), head, branch->name);
+                       fprintf(stderr, _("    git branch -d %s\n"), branch->name);
+                       fprintf(stderr, _("    git branch --set-upstream-to %s\n"), branch->name);
+               }
        } else
                usage_with_options(builtin_branch_usage, options);
  
diff --combined t/t3200-branch.sh
index 1ffce2173e4cc2d75d0fb33fc847c383cc5fa530,2a54a76abb359a8c91eb070486f604fd1c7a1811..79c8d0142ea72d25a2c403738312a01d0ace50cb
@@@ -31,7 -31,7 +31,7 @@@ test_expect_success 'branch -h in broke
                >.git/refs/heads/master &&
                test_expect_code 129 git branch -h >usage 2>&1
        ) &&
 -      grep "[Uu]sage" broken/usage
 +      test_i18ngrep "[Uu]sage" broken/usage
  '
  
  test_expect_success \
@@@ -74,7 -74,7 +74,7 @@@ test_expect_success 
  test_expect_success \
      'git branch -m dumps usage' \
         'test_expect_code 129 git branch -m 2>err &&
 -      grep "[Uu]sage: git branch" err'
 +      test_i18ngrep "[Uu]sage: git branch" err'
  
  test_expect_success \
      'git branch -m m m/m should work' \
@@@ -369,6 -369,76 +369,76 @@@ test_expect_success 
      'git tag foobar &&
       test_must_fail git branch --track my11 foobar'
  
+ test_expect_success 'use --set-upstream-to modify HEAD' \
+     'test_config branch.master.remote foo &&
+      test_config branch.master.merge foo &&
+      git branch my12
+      git branch --set-upstream-to my12 &&
+      test "$(git config branch.master.remote)" = "." &&
+      test "$(git config branch.master.merge)" = "refs/heads/my12"'
+ test_expect_success 'use --set-upstream-to modify a particular branch' \
+     'git branch my13
+      git branch --set-upstream-to master my13 &&
+      test "$(git config branch.my13.remote)" = "." &&
+      test "$(git config branch.my13.merge)" = "refs/heads/master"'
+ test_expect_success '--unset-upstream should fail if given a non-existent branch' \
+     'test_must_fail git branch --unset-upstream i-dont-exist'
+ test_expect_success 'test --unset-upstream on HEAD' \
+     'git branch my14
+      test_config branch.master.remote foo &&
+      test_config branch.master.merge foo &&
+      git branch --set-upstream-to my14 &&
+      git branch --unset-upstream &&
+      test_must_fail git config branch.master.remote &&
+      test_must_fail git config branch.master.merge &&
+      # fail for a branch without upstream set
+      test_must_fail git branch --unset-upstream
+ '
+ test_expect_success 'test --unset-upstream on a particular branch' \
+     'git branch my15
+      git branch --set-upstream-to master my14 &&
+      git branch --unset-upstream my14 &&
+      test_must_fail git config branch.my14.remote &&
+      test_must_fail git config branch.my14.merge'
+ test_expect_success '--set-upstream shows message when creating a new branch that exists as remote-tracking' \
+     'git update-ref refs/remotes/origin/master HEAD &&
+      git branch --set-upstream origin/master 2>actual &&
+      test_when_finished git update-ref -d refs/remotes/origin/master &&
+      test_when_finished git branch -d origin/master &&
+      cat >expected <<EOF &&
+ The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
+ If you wanted to make '"'master'"' track '"'origin/master'"', do this:
+     git branch -d origin/master
+     git branch --set-upstream-to origin/master
+ EOF
+      test_cmp expected actual
+ '
+ test_expect_success '--set-upstream with two args only shows the deprecation message' \
+     'git branch --set-upstream master my13 2>actual &&
+      test_when_finished git branch --unset-upstream master &&
+      cat >expected <<EOF &&
+ The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
+ EOF
+      test_cmp expected actual
+ '
+ test_expect_success '--set-upstream with one arg only shows the deprecation message if the branch existed' \
+     'git branch --set-upstream my13 2>actual &&
+      test_when_finished git branch --unset-upstream my13 &&
+      cat >expected <<EOF &&
+ The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
+ EOF
+      test_cmp expected actual
+ '
  # Keep this test last, as it changes the current branch
  cat >expect <<EOF
  $_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000       branch: Created from master