Merge branch 'md/exclude-promisor-objects-fix'
[gitweb.git] / builtin / checkout.c
index 2e1d2376d24043a75adb9bbc69724591bcc2d5c3..acdafc6e4c4d104aadcbe7492a8045e16e83ba50 100644 (file)
@@ -4,6 +4,7 @@
 #include "lockfile.h"
 #include "parse-options.h"
 #include "refs.h"
+#include "object-store.h"
 #include "commit.h"
 #include "tree.h"
 #include "tree-walk.h"
@@ -22,6 +23,9 @@
 #include "resolve-undo.h"
 #include "submodule-config.h"
 #include "submodule.h"
+#include "advice.h"
+
+static int checkout_optimize_new_branch;
 
 static const char * const checkout_usage[] = {
        N_("git checkout [<options>] <branch>"),
@@ -40,6 +44,10 @@ struct checkout_opts {
        int ignore_skipworktree;
        int ignore_other_worktrees;
        int show_progress;
+       /*
+        * If new checkout options are added, skip_merge_working_tree
+        * should be updated accordingly.
+        */
 
        const char *new_branch;
        const char *new_branch_force;
@@ -77,7 +85,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base,
                return READ_TREE_RECURSIVE;
 
        len = base->len + strlen(pathname);
-       ce = xcalloc(1, cache_entry_size(len));
+       ce = make_empty_cache_entry(&the_index, len);
        oidcpy(&ce->oid, oid);
        memcpy(ce->name, base->buf, base->len);
        memcpy(ce->name + base->len, pathname, len - base->len);
@@ -94,9 +102,9 @@ static int update_some(const struct object_id *oid, struct strbuf *base,
        if (pos >= 0) {
                struct cache_entry *old = active_cache[pos];
                if (ce->ce_mode == old->ce_mode &&
-                   !oidcmp(&ce->oid, &old->oid)) {
+                   oideq(&ce->oid, &old->oid)) {
                        old->ce_flags |= CE_UPDATE;
-                       free(ce);
+                       discard_cache_entry(ce);
                        return 0;
                }
        }
@@ -206,7 +214,8 @@ static int checkout_merged(int pos, const struct checkout *state)
         * merge.renormalize set, too
         */
        status = ll_merge(&result_buf, path, &ancestor, "base",
-                         &ours, "ours", &theirs, "theirs", NULL);
+                         &ours, "ours", &theirs, "theirs",
+                         state->istate, NULL);
        free(ancestor.ptr);
        free(ours.ptr);
        free(theirs.ptr);
@@ -230,11 +239,11 @@ static int checkout_merged(int pos, const struct checkout *state)
        if (write_object_file(result_buf.ptr, result_buf.size, blob_type, &oid))
                die(_("Unable to add merge result for '%s'"), path);
        free(result_buf.ptr);
-       ce = make_cache_entry(mode, oid.hash, path, 2, 0);
+       ce = make_transient_cache_entry(mode, &oid, path, 2);
        if (!ce)
                die(_("make_cache_entry failed for path '%s'"), path);
        status = checkout_entry(ce, state, NULL);
-       free(ce);
+       discard_cache_entry(ce);
        return status;
 }
 
@@ -316,7 +325,7 @@ static int checkout_paths(const struct checkout_opts *opts,
                 * match_pathspec() for _all_ entries when
                 * opts->source_tree != NULL.
                 */
-               if (ce_path_match(ce, &opts->pathspec, ps_matched))
+               if (ce_path_match(&the_index, ce, &opts->pathspec, ps_matched))
                        ce->ce_flags |= CE_MATCHED;
        }
 
@@ -378,7 +387,7 @@ static int checkout_paths(const struct checkout_opts *opts,
                die(_("unable to write new index file"));
 
        read_ref_full("HEAD", 0, &rev, NULL);
-       head = lookup_commit_reference_gently(&rev, 1);
+       head = lookup_commit_reference_gently(the_repository, &rev, 1);
 
        errs |= post_checkout_hook(head, head, 0);
        return errs;
@@ -389,7 +398,7 @@ static void show_local_changes(struct object *head,
 {
        struct rev_info rev;
        /* I think we want full paths, even if we're in a subdirectory. */
-       init_revisions(&rev, NULL);
+       repo_init_revisions(the_repository, &rev, NULL);
        rev.diffopt.flags = opts->flags;
        rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
        diff_setup_done(&rev.diffopt);
@@ -470,6 +479,99 @@ static void setup_branch_path(struct branch_info *branch)
        branch->path = strbuf_detach(&buf, NULL);
 }
 
+/*
+ * Skip merging the trees, updating the index and working directory if and
+ * only if we are creating a new branch via "git checkout -b <new_branch>."
+ */
+static int skip_merge_working_tree(const struct checkout_opts *opts,
+       const struct branch_info *old_branch_info,
+       const struct branch_info *new_branch_info)
+{
+       /*
+        * Do the merge if sparse checkout is on and the user has not opted in
+        * to the optimized behavior
+        */
+       if (core_apply_sparse_checkout && !checkout_optimize_new_branch)
+               return 0;
+
+       /*
+        * We must do the merge if we are actually moving to a new commit.
+        */
+       if (!old_branch_info->commit || !new_branch_info->commit ||
+               !oideq(&old_branch_info->commit->object.oid,
+                      &new_branch_info->commit->object.oid))
+               return 0;
+
+       /*
+        * opts->patch_mode cannot be used with switching branches so is
+        * not tested here
+        */
+
+       /*
+        * opts->quiet only impacts output so doesn't require a merge
+        */
+
+       /*
+        * Honor the explicit request for a three-way merge or to throw away
+        * local changes
+        */
+       if (opts->merge || opts->force)
+               return 0;
+
+       /*
+        * --detach is documented as "updating the index and the files in the
+        * working tree" but this optimization skips those steps so fall through
+        * to the regular code path.
+        */
+       if (opts->force_detach)
+               return 0;
+
+       /*
+        * opts->writeout_stage cannot be used with switching branches so is
+        * not tested here
+        */
+
+       /*
+        * Honor the explicit ignore requests
+        */
+       if (!opts->overwrite_ignore || opts->ignore_skipworktree ||
+               opts->ignore_other_worktrees)
+               return 0;
+
+       /*
+        * opts->show_progress only impacts output so doesn't require a merge
+        */
+
+       /*
+        * If we aren't creating a new branch any changes or updates will
+        * happen in the existing branch.  Since that could only be updating
+        * the index and working directory, we don't want to skip those steps
+        * or we've defeated any purpose in running the command.
+        */
+       if (!opts->new_branch)
+               return 0;
+
+       /*
+        * new_branch_force is defined to "create/reset and checkout a branch"
+        * so needs to go through the merge to do the reset
+        */
+       if (opts->new_branch_force)
+               return 0;
+
+       /*
+        * A new orphaned branch requrires the index and the working tree to be
+        * adjusted to <start_point>
+        */
+       if (opts->new_orphan_branch)
+               return 0;
+
+       /*
+        * Remaining variables are not checkout options but used to track state
+        */
+
+       return 1;
+}
+
 static int merge_working_tree(const struct checkout_opts *opts,
                              struct branch_info *old_branch_info,
                              struct branch_info *new_branch_info,
@@ -798,7 +900,7 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne
        struct rev_info revs;
        struct object *object = &old_commit->object;
 
-       init_revisions(&revs, NULL);
+       repo_init_revisions(the_repository, &revs, NULL);
        setup_revisions(0, NULL, &revs, NULL);
 
        object->flags &= ~UNINTERESTING;
@@ -829,7 +931,7 @@ static int switch_branches(const struct checkout_opts *opts,
        memset(&old_branch_info, 0, sizeof(old_branch_info));
        old_branch_info.path = path_to_free = resolve_refdup("HEAD", 0, &rev, &flag);
        if (old_branch_info.path)
-               old_branch_info.commit = lookup_commit_reference_gently(&rev, 1);
+               old_branch_info.commit = lookup_commit_reference_gently(the_repository, &rev, 1);
        if (!(flag & REF_ISSYMREF))
                old_branch_info.path = NULL;
 
@@ -844,10 +946,19 @@ static int switch_branches(const struct checkout_opts *opts,
                parse_commit_or_die(new_branch_info->commit);
        }
 
-       ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
-       if (ret) {
-               free(path_to_free);
-               return ret;
+       /* optimize the "checkout -b <new_branch> path */
+       if (skip_merge_working_tree(opts, &old_branch_info, new_branch_info)) {
+               if (!checkout_optimize_new_branch && !opts->quiet) {
+                       if (read_cache_preload(NULL) < 0)
+                               return error(_("index file corrupt"));
+                       show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
+               }
+       } else {
+               ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
+               if (ret) {
+                       free(path_to_free);
+                       return ret;
+               }
        }
 
        if (!opts->quiet && !old_branch_info.path && old_branch_info.commit && new_branch_info->commit != old_branch_info.commit)
@@ -862,6 +973,11 @@ static int switch_branches(const struct checkout_opts *opts,
 
 static int git_checkout_config(const char *var, const char *value, void *cb)
 {
+       if (!strcmp(var, "checkout.optimizenewbranch")) {
+               checkout_optimize_new_branch = git_config_bool(var, value);
+               return 0;
+       }
+
        if (!strcmp(var, "diff.ignoresubmodules")) {
                struct checkout_opts *opts = cb;
                handle_ignore_submodules_arg(&opts->diff_options, value);
@@ -878,7 +994,8 @@ static int parse_branchname_arg(int argc, const char **argv,
                                int dwim_new_local_branch_ok,
                                struct branch_info *new_branch_info,
                                struct checkout_opts *opts,
-                               struct object_id *rev)
+                               struct object_id *rev,
+                               int *dwim_remotes_matched)
 {
        struct tree **source_tree = &opts->source_tree;
        const char **new_branch = &opts->new_branch;
@@ -910,8 +1027,10 @@ static int parse_branchname_arg(int argc, const char **argv,
         *   (b) If <something> is _not_ a commit, either "--" is present
         *       or <something> is not a path, no -t or -b was given, and
         *       and there is a tracking branch whose name is <something>
-        *       in one and only one remote, then this is a short-hand to
-        *       fork local <something> from that remote-tracking branch.
+        *       in one and only one remote (or if the branch exists on the
+        *       remote named in checkout.defaultRemote), then this is a
+        *       short-hand to fork local <something> from that
+        *       remote-tracking branch.
         *
         *   (c) Otherwise, if "--" is present, treat it like case (1).
         *
@@ -972,7 +1091,8 @@ static int parse_branchname_arg(int argc, const char **argv,
                        recover_with_dwim = 0;
 
                if (recover_with_dwim) {
-                       const char *remote = unique_tracking_name(arg, rev);
+                       const char *remote = unique_tracking_name(arg, rev,
+                                                                 dwim_remotes_matched);
                        if (remote) {
                                *new_branch = arg;
                                arg = remote;
@@ -1003,7 +1123,7 @@ static int parse_branchname_arg(int argc, const char **argv,
        else
                new_branch_info->path = NULL; /* not an existing branch */
 
-       new_branch_info->commit = lookup_commit_reference_gently(rev, 1);
+       new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
        if (!new_branch_info->commit) {
                /* not a commit */
                *source_tree = parse_tree_indirect(rev);
@@ -1109,6 +1229,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        struct branch_info new_branch_info;
        char *conflict_style = NULL;
        int dwim_new_local_branch = 1;
+       int dwim_remotes_matched = 0;
        struct option options[] = {
                OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
                OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
@@ -1120,10 +1241,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                OPT_SET_INT('t', "track",  &opts.track, N_("set upstream info for new branch"),
                        BRANCH_TRACK_EXPLICIT),
                OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
-               OPT_SET_INT('2', "ours", &opts.writeout_stage, N_("checkout our version for unmerged files"),
-                           2),
-               OPT_SET_INT('3', "theirs", &opts.writeout_stage, N_("checkout their version for unmerged files"),
-                           3),
+               OPT_SET_INT_F('2', "ours", &opts.writeout_stage,
+                             N_("checkout our version for unmerged files"),
+                             2, PARSE_OPT_NONEG),
+               OPT_SET_INT_F('3', "theirs", &opts.writeout_stage,
+                             N_("checkout their version for unmerged files"),
+                             3, PARSE_OPT_NONEG),
                OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)"),
                           PARSE_OPT_NOCOMPLETE),
                OPT_BOOL('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")),
@@ -1189,12 +1312,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        if (opts.track != BRANCH_TRACK_UNSPECIFIED && !opts.new_branch) {
                const char *argv0 = argv[0];
                if (!argc || !strcmp(argv0, "--"))
-                       die (_("--track needs a branch name"));
+                       die(_("--track needs a branch name"));
                skip_prefix(argv0, "refs/", &argv0);
                skip_prefix(argv0, "remotes/", &argv0);
                argv0 = strchr(argv0, '/');
                if (!argv0 || !argv0[1])
-                       die (_("Missing branch name; try -b"));
+                       die(_("missing branch name; try -b"));
                opts.new_branch = argv0 + 1;
        }
 
@@ -1219,7 +1342,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                        opts.track == BRANCH_TRACK_UNSPECIFIED &&
                        !opts.new_branch;
                int n = parse_branchname_arg(argc, argv, dwim_ok,
-                                            &new_branch_info, &opts, &rev);
+                                            &new_branch_info, &opts, &rev,
+                                            &dwim_remotes_matched);
                argv += n;
                argc -= n;
        }
@@ -1261,8 +1385,26 @@ int cmd_checkout(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);
-       else
+       if (opts.patch_mode || opts.pathspec.nr) {
+               int ret = checkout_paths(&opts, new_branch_info.name);
+               if (ret && dwim_remotes_matched > 1 &&
+                   advice_checkout_ambiguous_remote_branch_name)
+                       advise(_("'%s' matched more than one remote tracking branch.\n"
+                                "We found %d remotes with a reference that matched. So we fell back\n"
+                                "on trying to resolve the argument as a path, but failed there too!\n"
+                                "\n"
+                                "If you meant to check out a remote tracking branch on, e.g. 'origin',\n"
+                                "you can do so by fully qualifying the name with the --track option:\n"
+                                "\n"
+                                "    git checkout --track origin/<name>\n"
+                                "\n"
+                                "If you'd like to always have checkouts of an ambiguous <name> prefer\n"
+                                "one remote, e.g. the 'origin' remote, consider setting\n"
+                                "checkout.defaultRemote=origin in your config."),
+                              argv[0],
+                              dwim_remotes_matched);
+               return ret;
+       } else {
                return checkout_branch(&opts, &new_branch_info);
+       }
 }