Merge branch 'nd/edit-branch-desc-while-detached'
authorJunio C Hamano <gitster@pobox.com>
Fri, 1 Feb 2013 20:40:52 +0000 (12:40 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 1 Feb 2013 20:40:52 +0000 (12:40 -0800)
Attempt to "branch --edit-description" an existing branch, while
being on a detached HEAD, errored out.

* nd/edit-branch-desc-while-detached:
branch: no detached HEAD check when editing another branch's description

1  2 
builtin/branch.c
diff --combined builtin/branch.c
index 873f624d1cc70e03da4c67b30f8c839b4afe099d,ea10bce3ba02dbc1afd804c2d91c7fc5a8445c10..ea6498b08a3b290126f64d498ed388f4ce067e9e
  #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
  };
  
@@@ -130,7 -129,7 +130,7 @@@ static int branch_merged(int kind, cons
        if (!reference_rev)
                reference_rev = head_rev;
  
 -      merged = in_merge_bases(rev, &reference_rev, 1);
 +      merged = in_merge_bases(rev, reference_rev);
  
        /*
         * After the safety valve is fully redefined to "check with
         * a gentle reminder is in order.
         */
        if ((head_rev != reference_rev) &&
 -          in_merge_bases(rev, &head_rev, 1) != merged) {
 +          in_merge_bases(rev, head_rev) != merged) {
                if (merged)
                        warning(_("deleting branch '%s' that has been merged to\n"
                                "         '%s', but not yet merged to HEAD."),
        return merged;
  }
  
 +static int check_branch_commit(const char *branchname, const char *refname,
 +                             unsigned char *sha1, struct commit *head_rev,
 +                             int kinds, int force)
 +{
 +      struct commit *rev = lookup_commit_reference(sha1);
 +      if (!rev) {
 +              error(_("Couldn't look up commit object for '%s'"), refname);
 +              return -1;
 +      }
 +      if (!force && !branch_merged(kinds, branchname, rev, head_rev)) {
 +              error(_("The branch '%s' is not fully merged.\n"
 +                    "If you are sure you want to delete it, "
 +                    "run 'git branch -D %s'."), branchname, branchname);
 +              return -1;
 +      }
 +      return 0;
 +}
 +
 +static void delete_branch_config(const char *branchname)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +      strbuf_addf(&buf, "branch.%s", branchname);
 +      if (git_config_rename_section(buf.buf, NULL) < 0)
 +              warning(_("Update of config-file failed"));
 +      strbuf_release(&buf);
 +}
 +
  static int delete_branches(int argc, const char **argv, int force, int kinds,
                           int quiet)
  {
 -      struct commit *rev, *head_rev = NULL;
 +      struct commit *head_rev = NULL;
        unsigned char sha1[20];
        char *name = NULL;
        const char *fmt;
                        die(_("Couldn't look up commit object for HEAD"));
        }
        for (i = 0; i < argc; i++, strbuf_release(&bname)) {
 +              const char *target;
 +              int flags = 0;
 +
                strbuf_branchname(&bname, argv[i]);
                if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) {
                        error(_("Cannot delete the branch '%s' "
  
                free(name);
  
 -              name = xstrdup(mkpath(fmt, bname.buf));
 -              if (read_ref(name, sha1)) {
 +              name = mkpathdup(fmt, bname.buf);
 +              target = resolve_ref_unsafe(name, sha1, 0, &flags);
 +              if (!target ||
 +                  (!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) {
                        error(remote_branch
                              ? _("remote branch '%s' not found.")
                              : _("branch '%s' not found."), bname.buf);
                        continue;
                }
  
 -              rev = lookup_commit_reference(sha1);
 -              if (!rev) {
 -                      error(_("Couldn't look up commit object for '%s'"), name);
 -                      ret = 1;
 -                      continue;
 -              }
 -
 -              if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) {
 -                      error(_("The branch '%s' is not fully merged.\n"
 -                            "If you are sure you want to delete it, "
 -                            "run 'git branch -D %s'."), bname.buf, bname.buf);
 +              if (!(flags & REF_ISSYMREF) &&
 +                  check_branch_commit(bname.buf, name, sha1, head_rev, kinds,
 +                                      force)) {
                        ret = 1;
                        continue;
                }
  
 -              if (delete_ref(name, sha1, 0)) {
 +              if (delete_ref(name, sha1, REF_NODEREF)) {
                        error(remote_branch
                              ? _("Error deleting remote branch '%s'")
                              : _("Error deleting branch '%s'"),
                              bname.buf);
                        ret = 1;
 -              } else {
 -                      struct strbuf buf = STRBUF_INIT;
 -                      if (!quiet)
 -                              printf(remote_branch
 -                                     ? _("Deleted remote branch %s (was %s).\n")
 -                                     : _("Deleted branch %s (was %s).\n"),
 -                                     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);
 +                      continue;
 +              }
 +              if (!quiet) {
 +                      printf(remote_branch
 +                             ? _("Deleted remote branch %s (was %s).\n")
 +                             : _("Deleted branch %s (was %s).\n"),
 +                             bname.buf,
 +                             (flags & REF_ISSYMREF)
 +                             ? target
 +                             : find_unique_abbrev(sha1, DEFAULT_ABBREV));
                }
 +              delete_branch_config(bname.buf);
        }
  
        free(name);
  struct ref_item {
        char *name;
        char *dest;
 -      unsigned int kind, len;
 +      unsigned int kind, width;
        struct commit *commit;
  };
  
@@@ -378,14 -354,14 +378,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;
  }
@@@ -514,12 -490,11 +514,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));
  
@@@ -544,8 -519,8 +544,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;
  }
@@@ -558,12 -533,12 +558,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);
        }
@@@ -725,7 -700,7 +725,7 @@@ static int edit_branch_description(cons
        stripspace(&buf, 1);
  
        strbuf_addf(&name, "branch.%s.description", branch_name);
 -      status = git_config_set(name.buf, buf.buf);
 +      status = git_config_set(name.buf, buf.len ? buf.buf : NULL);
        strbuf_release(&name);
        strbuf_release(&buf);
  
@@@ -737,65 -712,62 +737,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)
                const char *branch_name;
                struct strbuf branch_ref = STRBUF_INIT;
  
-               if (detached)
-                       die("Cannot give description to detached HEAD");
-               if (!argc)
+               if (!argc) {
+                       if (detached)
+                               die("Cannot give description to detached HEAD");
                        branch_name = head;
-               else if (argc == 1)
+               else if (argc == 1)
                        branch_name = argv[0];
                else
                        usage_with_options(builtin_branch_usage, options);
                        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);