commit: express tree entry constants in terms of the_hash_algo
[gitweb.git] / builtin / branch.c
index 8f779b02b5f09907e0984dfb27024778026f0b4f..1876ca9e7969019e1db1c97aedcf1064903b80a2 100644 (file)
 #include "wt-status.h"
 #include "ref-filter.h"
 #include "worktree.h"
+#include "help.h"
 
 static const char * const builtin_branch_usage[] = {
        N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"),
        N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
        N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
        N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
+       N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"),
        N_("git branch [<options>] [-r | -a] [--points-at]"),
        N_("git branch [<options>] [-r | -a] [--format]"),
        NULL
@@ -54,25 +56,19 @@ enum color_branch {
        BRANCH_COLOR_UPSTREAM = 5
 };
 
+static const char *color_branch_slots[] = {
+       [BRANCH_COLOR_RESET]    = "reset",
+       [BRANCH_COLOR_PLAIN]    = "plain",
+       [BRANCH_COLOR_REMOTE]   = "remote",
+       [BRANCH_COLOR_LOCAL]    = "local",
+       [BRANCH_COLOR_CURRENT]  = "current",
+       [BRANCH_COLOR_UPSTREAM] = "upstream",
+};
+
 static struct string_list output = STRING_LIST_INIT_DUP;
 static unsigned int colopts;
 
-static int parse_branch_color_slot(const char *slot)
-{
-       if (!strcasecmp(slot, "plain"))
-               return BRANCH_COLOR_PLAIN;
-       if (!strcasecmp(slot, "reset"))
-               return BRANCH_COLOR_RESET;
-       if (!strcasecmp(slot, "remote"))
-               return BRANCH_COLOR_REMOTE;
-       if (!strcasecmp(slot, "local"))
-               return BRANCH_COLOR_LOCAL;
-       if (!strcasecmp(slot, "current"))
-               return BRANCH_COLOR_CURRENT;
-       if (!strcasecmp(slot, "upstream"))
-               return BRANCH_COLOR_UPSTREAM;
-       return -1;
-}
+define_list_config_array(color_branch_slots);
 
 static int git_branch_config(const char *var, const char *value, void *cb)
 {
@@ -85,7 +81,7 @@ static int git_branch_config(const char *var, const char *value, void *cb)
                return 0;
        }
        if (skip_prefix(var, "color.branch.", &slot_name)) {
-               int slot = parse_branch_color_slot(slot_name);
+               int slot = LOOKUP_CONFIG(color_branch_slots, slot_name);
                if (slot < 0)
                        return 0;
                if (!value)
@@ -124,7 +120,7 @@ static int branch_merged(int kind, const char *name,
                if (upstream &&
                    (reference_name = reference_name_to_free =
                     resolve_refdup(upstream, RESOLVE_REF_READING,
-                                   oid.hash, NULL)) != NULL)
+                                   &oid, NULL)) != NULL)
                        reference_rev = lookup_commit_reference(&oid);
        }
        if (!reference_rev)
@@ -240,7 +236,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                                        RESOLVE_REF_READING
                                        | RESOLVE_REF_NO_RECURSE
                                        | RESOLVE_REF_ALLOW_BAD_NAME,
-                                       oid.hash, &flags);
+                                       &oid, &flags);
                if (!target) {
                        error(remote_branch
                              ? _("remote-tracking branch '%s' not found.")
@@ -256,8 +252,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                        goto next;
                }
 
-               if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : oid.hash,
-                              REF_NODEREF)) {
+               if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : &oid,
+                              REF_NO_DEREF)) {
                        error(remote_branch
                              ? _("Error deleting remote-tracking branch '%s'")
                              : _("Error deleting branch '%s'"),
@@ -272,7 +268,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                               bname.buf,
                               (flags & REF_ISBROKEN) ? "broken"
                               : (flags & REF_ISSYMREF) ? target
-                              : find_unique_abbrev(oid.hash, DEFAULT_ABBREV));
+                              : find_unique_abbrev(&oid, DEFAULT_ABBREV));
                }
                delete_branch_config(bname.buf);
 
@@ -353,7 +349,7 @@ static char *build_format(struct ref_filter *filter, int maxwidth, const char *r
                        strbuf_addf(&obname, "%%(objectname:short=%d)", filter->abbrev);
 
                strbuf_addf(&local, "%%(align:%d,left)%%(refname:lstrip=2)%%(end)", maxwidth);
-               strbuf_addf(&local, "%s", branch_get_color(BRANCH_COLOR_RESET));
+               strbuf_addstr(&local, branch_get_color(BRANCH_COLOR_RESET));
                strbuf_addf(&local, " %s ", obname.buf);
 
                if (filter->verbose > 1)
@@ -390,7 +386,6 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
        struct ref_array array;
        int maxwidth = 0;
        const char *remote_prefix = "";
-       struct strbuf out = STRBUF_INIT;
        char *to_free = NULL;
 
        /*
@@ -418,7 +413,10 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
        ref_array_sort(sorting, &array);
 
        for (i = 0; i < array.nr; i++) {
-               format_ref_array_item(array.items[i], format, &out);
+               struct strbuf out = STRBUF_INIT;
+               struct strbuf err = STRBUF_INIT;
+               if (format_ref_array_item(array.items[i], format, &out, &err))
+                       die("%s", err.buf);
                if (column_active(colopts)) {
                        assert(!filter->verbose && "--column and --verbose are incompatible");
                         /* format to a string_list to let print_columns() do its job */
@@ -427,6 +425,7 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
                        fwrite(out.buf, 1, out.len, stdout);
                        putchar('\n');
                }
+               strbuf_release(&err);
                strbuf_release(&out);
        }
 
@@ -457,15 +456,20 @@ static void reject_rebase_or_bisect_branch(const char *target)
        free_worktrees(worktrees);
 }
 
-static void rename_branch(const char *oldname, const char *newname, int force)
+static void copy_or_rename_branch(const char *oldname, const char *newname, int copy, int force)
 {
        struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
        struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
+       const char *interpreted_oldname = NULL;
+       const char *interpreted_newname = NULL;
        int recovery = 0;
-       int clobber_head_ok;
 
-       if (!oldname)
-               die(_("cannot rename the current branch while not on any."));
+       if (!oldname) {
+               if (copy)
+                       die(_("cannot copy the current branch while not on any."));
+               else
+                       die(_("cannot rename the current branch while not on any."));
+       }
 
        if (strbuf_check_branch_ref(&oldref, oldname)) {
                /*
@@ -482,32 +486,53 @@ static void rename_branch(const char *oldname, const char *newname, int force)
         * A command like "git branch -M currentbranch currentbranch" cannot
         * cause the worktree to become inconsistent with HEAD, so allow it.
         */
-       clobber_head_ok = !strcmp(oldname, newname);
-
-       validate_new_branchname(newname, &newref, force, clobber_head_ok);
+       if (!strcmp(oldname, newname))
+               validate_branchname(newname, &newref);
+       else
+               validate_new_branchname(newname, &newref, force);
 
        reject_rebase_or_bisect_branch(oldref.buf);
 
-       strbuf_addf(&logmsg, "Branch: renamed %s to %s",
-                oldref.buf, newref.buf);
+       if (!skip_prefix(oldref.buf, "refs/heads/", &interpreted_oldname) ||
+           !skip_prefix(newref.buf, "refs/heads/", &interpreted_newname)) {
+               BUG("expected prefix missing for refs");
+       }
+
+       if (copy)
+               strbuf_addf(&logmsg, "Branch: copied %s to %s",
+                           oldref.buf, newref.buf);
+       else
+               strbuf_addf(&logmsg, "Branch: renamed %s to %s",
+                           oldref.buf, newref.buf);
 
-       if (rename_ref(oldref.buf, newref.buf, logmsg.buf))
+       if (!copy && rename_ref(oldref.buf, newref.buf, logmsg.buf))
                die(_("Branch rename failed"));
+       if (copy && copy_existing_ref(oldref.buf, newref.buf, logmsg.buf))
+               die(_("Branch copy failed"));
 
-       if (recovery)
-               warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11);
+       if (recovery) {
+               if (copy)
+                       warning(_("Created a copy of a misnamed branch '%s'"),
+                               interpreted_oldname);
+               else
+                       warning(_("Renamed a misnamed branch '%s' away"),
+                               interpreted_oldname);
+       }
 
-       if (replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf))
+       if (!copy &&
+           replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf))
                die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
 
        strbuf_release(&logmsg);
 
-       strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11);
+       strbuf_addf(&oldsection, "branch.%s", interpreted_oldname);
        strbuf_release(&oldref);
-       strbuf_addf(&newsection, "branch.%s", newref.buf + 11);
+       strbuf_addf(&newsection, "branch.%s", interpreted_newname);
        strbuf_release(&newref);
-       if (git_config_rename_section(oldsection.buf, newsection.buf) < 0)
+       if (!copy && git_config_rename_section(oldsection.buf, newsection.buf) < 0)
                die(_("Branch is renamed, but update of config-file failed"));
+       if (copy && strcmp(oldname, newname) && git_config_copy_section(oldsection.buf, newsection.buf) < 0)
+               die(_("Branch is copied, but update of config-file failed"));
        strbuf_release(&oldsection);
        strbuf_release(&newsection);
 }
@@ -545,7 +570,7 @@ 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 delete = 0, rename = 0, copy = 0, force = 0, list = 0;
        int reflog = 0, edit_description = 0;
        int quiet = 0, unset_upstream = 0;
        const char *new_upstream = NULL;
@@ -562,8 +587,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                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, N_("change upstream info"),
-                       BRANCH_TRACK_OVERRIDE),
+               OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
+                       BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
                OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
                OPT_BOOL(0, "unset-upstream", &unset_upstream, N_("Unset the upstream info")),
                OPT__COLOR(&branch_use_color, N_("use colored output")),
@@ -582,11 +607,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                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_BIT('c', "copy", &copy, N_("copy a branch and its reflog"), 1),
+               OPT_BIT('C', NULL, &copy, N_("copy a branch, even if target exists"), 2),
                OPT_BOOL(0, "list", &list, N_("list branch names")),
                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, N_("force creation, move/rename, deletion")),
+               OPT__FORCE(&force, N_("force creation, move/rename, deletion"), PARSE_OPT_NOCOMPLETE),
                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")),
@@ -614,7 +641,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 
        track = git_branch_track;
 
-       head = resolve_refdup("HEAD", 0, head_oid.hash, NULL);
+       head = resolve_refdup("HEAD", 0, &head_oid, NULL);
        if (!head)
                die(_("Failed to resolve HEAD as a valid ref."));
        if (!strcmp(head, "HEAD"))
@@ -625,14 +652,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
                             0);
 
-       if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0)
+       if (!delete && !rename && !copy && !edit_description && !new_upstream && !unset_upstream && argc == 0)
                list = 1;
 
        if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr ||
            filter.no_commit)
                list = 1;
 
-       if (!!delete + !!rename + !!new_upstream +
+       if (!!delete + !!rename + !!copy + !!new_upstream +
            list + unset_upstream > 1)
                usage_with_options(builtin_branch_usage, options);
 
@@ -650,8 +677,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        if (force) {
                delete *= 2;
                rename *= 2;
+               copy *= 2;
        }
 
+       if (list)
+               setup_auto_pager("branch", 1);
+
        if (delete) {
                if (!argc)
                        die(_("branch name required"));
@@ -665,7 +696,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                 * If no sorting parameter is given then we default to sorting
                 * by 'refname'. This would give us an alphabetically sorted
                 * array with the 'HEAD' ref at the beginning followed by
-                * local branches 'refs/heads/...' and finally remote-tacking
+                * local branches 'refs/heads/...' and finally remote-tracking
                 * branches 'refs/remotes/...'.
                 */
                if (!sorting)
@@ -704,20 +735,29 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 
                if (edit_branch_description(branch_name))
                        return 1;
+       } else if (copy) {
+               if (!argc)
+                       die(_("branch name required"));
+               else if (argc == 1)
+                       copy_or_rename_branch(head, argv[0], 1, copy > 1);
+               else if (argc == 2)
+                       copy_or_rename_branch(argv[0], argv[1], 1, copy > 1);
+               else
+                       die(_("too many branches for a copy operation"));
        } else if (rename) {
                if (!argc)
                        die(_("branch name required"));
                else if (argc == 1)
-                       rename_branch(head, argv[0], rename > 1);
+                       copy_or_rename_branch(head, argv[0], 0, rename > 1);
                else if (argc == 2)
-                       rename_branch(argv[0], argv[1], rename > 1);
+                       copy_or_rename_branch(argv[0], argv[1], 0, rename > 1);
                else
-                       die(_("too many branches for a rename operation"));
+                       die(_("too many arguments for a rename operation"));
        } else if (new_upstream) {
                struct branch *branch = branch_get(argv[0]);
 
                if (argc > 1)
-                       die(_("too many branches to set new upstream"));
+                       die(_("too many arguments to set new upstream"));
 
                if (!branch) {
                        if (!argc || !strcmp(argv[0], "HEAD"))
@@ -740,7 +780,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                struct strbuf buf = STRBUF_INIT;
 
                if (argc > 1)
-                       die(_("too many branches to unset upstream"));
+                       die(_("too many arguments to unset upstream"));
 
                if (!branch) {
                        if (!argc || !strcmp(argv[0], "HEAD"))
@@ -760,11 +800,6 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                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 (!strcmp(argv[0], "HEAD"))
-                       die(_("it does not make sense to create 'HEAD' manually"));
 
                if (!branch)
                        die(_("no such branch '%s'"), argv[0]);
@@ -773,27 +808,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                        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"));
+                       die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead."));
 
-               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(argv[0], (argc == 2) ? argv[1] : head,
-                             force, 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);
-               }
+                             force, 0, reflog, quiet, track);
 
        } else
                usage_with_options(builtin_branch_usage, options);