Imported Upstream version 2.28.0
[platform/upstream/git.git] / builtin / checkout.c
index d677381..af849c6 100644 (file)
@@ -88,6 +88,19 @@ struct checkout_opts {
        struct tree *source_tree;
 };
 
+struct branch_info {
+       const char *name; /* The short name used */
+       const char *path; /* The full name of a real branch */
+       struct commit *commit; /* The named commit */
+       char *refname; /* The full name of the ref being checked out. */
+       struct object_id oid; /* The object ID of the commit being checked out. */
+       /*
+        * if not null the branch is detached because it's already
+        * checked out in this checkout
+        */
+       char *checkout;
+};
+
 static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
                              int changed)
 {
@@ -337,7 +350,8 @@ static void mark_ce_for_checkout_no_overlay(struct cache_entry *ce,
        }
 }
 
-static int checkout_worktree(const struct checkout_opts *opts)
+static int checkout_worktree(const struct checkout_opts *opts,
+                            const struct branch_info *info)
 {
        struct checkout state = CHECKOUT_INIT;
        int nr_checkouts = 0, nr_unmerged = 0;
@@ -348,6 +362,10 @@ static int checkout_worktree(const struct checkout_opts *opts)
        state.refresh_cache = 1;
        state.istate = &the_index;
 
+       init_checkout_metadata(&state.meta, info->refname,
+                              info->commit ? &info->commit->object.oid : &info->oid,
+                              NULL);
+
        enable_delayed_checkout(&state);
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
@@ -396,7 +414,7 @@ static int checkout_worktree(const struct checkout_opts *opts)
 }
 
 static int checkout_paths(const struct checkout_opts *opts,
-                         const char *revision)
+                         const struct branch_info *new_branch_info)
 {
        int pos;
        static char *ps_matched;
@@ -462,7 +480,7 @@ static int checkout_paths(const struct checkout_opts *opts,
                else
                        BUG("either flag must have been set, worktree=%d, index=%d",
                            opts->checkout_worktree, opts->checkout_index);
-               return run_add_interactive(revision, patch_mode, &opts->pathspec);
+               return run_add_interactive(new_branch_info->name, patch_mode, &opts->pathspec);
        }
 
        repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
@@ -523,7 +541,7 @@ static int checkout_paths(const struct checkout_opts *opts,
 
        /* Now we are committed to check them out */
        if (opts->checkout_worktree)
-               errs |= checkout_worktree(opts);
+               errs |= checkout_worktree(opts, new_branch_info);
        else
                remove_marked_cache_entries(&the_index, 1);
 
@@ -586,7 +604,8 @@ static void describe_detached_head(const char *msg, struct commit *commit)
 }
 
 static int reset_tree(struct tree *tree, const struct checkout_opts *o,
-                     int worktree, int *writeout_error)
+                     int worktree, int *writeout_error,
+                     struct branch_info *info)
 {
        struct unpack_trees_options opts;
        struct tree_desc tree_desc;
@@ -601,6 +620,9 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
        opts.verbose_update = o->show_progress;
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
+       init_checkout_metadata(&opts.meta, info->refname,
+                              info->commit ? &info->commit->object.oid : &null_oid,
+                              NULL);
        parse_tree(tree);
        init_tree_desc(&tree_desc, tree->buffer, tree->size);
        switch (unpack_trees(1, &tree_desc, &opts)) {
@@ -620,21 +642,17 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
        }
 }
 
-struct branch_info {
-       const char *name; /* The short name used */
-       const char *path; /* The full name of a real branch */
-       struct commit *commit; /* The named commit */
-       /*
-        * if not null the branch is detached because it's already
-        * checked out in this checkout
-        */
-       char *checkout;
-};
-
 static void setup_branch_path(struct branch_info *branch)
 {
        struct strbuf buf = STRBUF_INIT;
 
+       /*
+        * If this is a ref, resolve it; otherwise, look up the OID for our
+        * expression.  Failure here is okay.
+        */
+       if (!dwim_ref(branch->name, strlen(branch->name), &branch->oid, &branch->refname))
+               repo_get_oid_committish(the_repository, branch->name, &branch->oid);
+
        strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);
        if (strcmp(buf.buf, branch->name))
                branch->name = xstrdup(buf.buf);
@@ -663,7 +681,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
        } else
                new_tree = get_commit_tree(new_branch_info->commit);
        if (opts->discard_changes) {
-               ret = reset_tree(new_tree, opts, 1, writeout_error);
+               ret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);
                if (ret)
                        return ret;
        } else {
@@ -692,6 +710,10 @@ static int merge_working_tree(const struct checkout_opts *opts,
                topts.quiet = opts->merge && old_branch_info->commit;
                topts.verbose_update = opts->show_progress;
                topts.fn = twoway_merge;
+               init_checkout_metadata(&topts.meta, new_branch_info->refname,
+                                      new_branch_info->commit ?
+                                      &new_branch_info->commit->object.oid :
+                                      &new_branch_info->oid, NULL);
                if (opts->overwrite_ignore) {
                        topts.dir = xcalloc(1, sizeof(*topts.dir));
                        topts.dir->flags |= DIR_SHOW_IGNORED;
@@ -762,7 +784,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
 
                        ret = reset_tree(new_tree,
                                         opts, 1,
-                                        writeout_error);
+                                        writeout_error, new_branch_info);
                        if (ret)
                                return ret;
                        o.ancestor = old_branch_info->name;
@@ -782,7 +804,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
                                exit(128);
                        ret = reset_tree(new_tree,
                                         opts, 0,
-                                        writeout_error);
+                                        writeout_error, new_branch_info);
                        strbuf_release(&o.obuf);
                        strbuf_release(&old_commit_shortname);
                        if (ret)
@@ -1462,9 +1484,9 @@ static struct option *add_common_options(struct checkout_opts *opts,
 {
        struct option options[] = {
                OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
-               { OPTION_CALLBACK, 0, "recurse-submodules", NULL,
+               OPT_CALLBACK_F(0, "recurse-submodules", NULL,
                            "checkout", "control recursive updating of submodules",
-                           PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
+                           PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater),
                OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
                OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
                OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
@@ -1520,6 +1542,9 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,
        return newopts;
 }
 
+/* create-branch option (either b or c) */
+static char cb_option = 'b';
+
 static int checkout_main(int argc, const char **argv, const char *prefix,
                         struct checkout_opts *opts, struct option *options,
                         const char * const usagestr[])
@@ -1562,7 +1587,8 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
        }
 
        if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
-               die(_("-b, -B and --orphan are mutually exclusive"));
+               die(_("-%c, -%c and --orphan are mutually exclusive"),
+                               cb_option, toupper(cb_option));
 
        if (opts->overlay_mode == 1 && opts->patch_mode)
                die(_("-p and --overlay are mutually exclusive"));
@@ -1581,16 +1607,16 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
        if (opts->checkout_index < 0 || opts->checkout_worktree < 0)
                BUG("these flags should be non-negative by now");
        /*
-        * convenient shortcut: "git restore --staged" equals
-        * "git restore --staged --source HEAD"
+        * convenient shortcut: "git restore --staged [--worktree]" equals
+        * "git restore --staged [--worktree] --source HEAD"
         */
-       if (!opts->from_treeish && opts->checkout_index && !opts->checkout_worktree)
+       if (!opts->from_treeish && opts->checkout_index)
                opts->from_treeish = "HEAD";
 
        /*
         * From here on, new_branch will contain the branch to be checked out,
         * and new_branch_force and new_orphan_branch will tell us which one of
-        * -b/-B/--orphan is being used.
+        * -b/-B/-c/-C/--orphan is being used.
         */
        if (opts->new_branch_force)
                opts->new_branch = opts->new_branch_force;
@@ -1598,7 +1624,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
        if (opts->new_orphan_branch)
                opts->new_branch = opts->new_orphan_branch;
 
-       /* --track without -b/-B/--orphan should DWIM */
+       /* --track without -c/-C/-b/-B/--orphan should DWIM */
        if (opts->track != BRANCH_TRACK_UNSPECIFIED && !opts->new_branch) {
                const char *argv0 = argv[0];
                if (!argc || !strcmp(argv0, "--"))
@@ -1607,7 +1633,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
                skip_prefix(argv0, "remotes/", &argv0);
                argv0 = strchr(argv0, '/');
                if (!argv0 || !argv0[1])
-                       die(_("missing branch name; try -b"));
+                       die(_("missing branch name; try -%c"), cb_option);
                opts->new_branch = argv0 + 1;
        }
 
@@ -1661,7 +1687,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
                 * Try to give more helpful suggestion.
                 * new_branch && argc > 1 will be caught later.
                 */
-               if (opts->new_branch && argc == 1)
+               if (opts->new_branch && argc == 1 && !new_branch_info.commit)
                        die(_("'%s' is not a commit and a branch '%s' cannot be created from it"),
                                argv[0], opts->new_branch);
 
@@ -1710,7 +1736,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 
        UNLEAK(opts);
        if (opts->patch_mode || opts->pathspec.nr)
-               return checkout_paths(opts, new_branch_info.name);
+               return checkout_paths(opts, &new_branch_info);
        else
                return checkout_branch(opts, &new_branch_info);
 }
@@ -1798,6 +1824,8 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
        options = add_common_options(&opts, options);
        options = add_common_switch_branch_options(&opts, options);
 
+       cb_option = 'c';
+
        ret = checkout_main(argc, argv, prefix, &opts,
                            options, switch_branch_usage);
        FREE_AND_NULL(options);