]> git.ipfire.org Git - thirdparty/git.git/blobdiff - builtin/checkout.c
Merge branch 'jk/bundle-use-dash-for-stdfiles'
[thirdparty/git.git] / builtin / checkout.c
index b23bc149d17b83686709c843e540f834e54620bc..734d730980c18e7fd8e774ac2b720e1e823dc47e 100644 (file)
@@ -1,4 +1,4 @@
-#define USE_THE_INDEX_COMPATIBILITY_MACROS
+#define USE_THE_INDEX_VARIABLE
 #include "builtin.h"
 #include "advice.h"
 #include "blob.h"
@@ -9,6 +9,8 @@
 #include "config.h"
 #include "diff.h"
 #include "dir.h"
+#include "hex.h"
+#include "hook.h"
 #include "ll-merge.h"
 #include "lockfile.h"
 #include "merge-recursive.h"
@@ -28,6 +30,7 @@
 #include "xdiff-interface.h"
 #include "entry.h"
 #include "parallel-checkout.h"
+#include "add-interactive.h"
 
 static const char * const checkout_usage[] = {
        N_("git checkout [<options>] <branch>"),
@@ -73,7 +76,7 @@ struct checkout_opts {
        const char *ignore_unmerged_opt;
        int ignore_unmerged;
        int pathspec_file_nul;
-       const char *pathspec_from_file;
+       char *pathspec_from_file;
 
        const char *new_branch;
        const char *new_branch_force;
@@ -91,8 +94,8 @@ struct checkout_opts {
 };
 
 struct branch_info {
-       const char *name; /* The short name used */
-       const char *path; /* The full name of a real branch */
+       char *name; /* The short name used */
+       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. */
@@ -103,10 +106,18 @@ struct branch_info {
        char *checkout;
 };
 
+static void branch_info_release(struct branch_info *info)
+{
+       free(info->name);
+       free(info->path);
+       free(info->refname);
+       free(info->checkout);
+}
+
 static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
                              int changed)
 {
-       return run_hook_le(NULL, "post-checkout",
+       return run_hooks_l("post-checkout",
                           oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()),
                           oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()),
                           changed ? "1" : "0", NULL);
@@ -116,7 +127,7 @@ static int post_checkout_hook(struct commit *old_commit, struct commit *new_comm
 }
 
 static int update_some(const struct object_id *oid, struct strbuf *base,
-               const char *pathname, unsigned mode, void *context)
+                      const char *pathname, unsigned mode, void *context UNUSED)
 {
        int len;
        struct cache_entry *ce;
@@ -139,9 +150,9 @@ static int update_some(const struct object_id *oid, struct strbuf *base,
         * entry in place. Whether it is UPTODATE or not, checkout_entry will
         * do the right thing.
         */
-       pos = cache_name_pos(ce->name, ce->ce_namelen);
+       pos = index_name_pos(&the_index, ce->name, ce->ce_namelen);
        if (pos >= 0) {
-               struct cache_entry *old = active_cache[pos];
+               struct cache_entry *old = the_index.cache[pos];
                if (ce->ce_mode == old->ce_mode &&
                    !ce_intent_to_add(old) &&
                    oideq(&ce->oid, &old->oid)) {
@@ -151,7 +162,8 @@ static int update_some(const struct object_id *oid, struct strbuf *base,
                }
        }
 
-       add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
+       add_index_entry(&the_index, ce,
+                       ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
        return 0;
 }
 
@@ -169,8 +181,8 @@ static int read_tree_some(struct tree *tree, const struct pathspec *pathspec)
 
 static int skip_same_name(const struct cache_entry *ce, int pos)
 {
-       while (++pos < active_nr &&
-              !strcmp(active_cache[pos]->name, ce->name))
+       while (++pos < the_index.cache_nr &&
+              !strcmp(the_index.cache[pos]->name, ce->name))
                ; /* skip */
        return pos;
 }
@@ -178,9 +190,9 @@ static int skip_same_name(const struct cache_entry *ce, int pos)
 static int check_stage(int stage, const struct cache_entry *ce, int pos,
                       int overlay_mode)
 {
-       while (pos < active_nr &&
-              !strcmp(active_cache[pos]->name, ce->name)) {
-               if (ce_stage(active_cache[pos]) == stage)
+       while (pos < the_index.cache_nr &&
+              !strcmp(the_index.cache[pos]->name, ce->name)) {
+               if (ce_stage(the_index.cache[pos]) == stage)
                        return 0;
                pos++;
        }
@@ -197,8 +209,8 @@ static int check_stages(unsigned stages, const struct cache_entry *ce, int pos)
        unsigned seen = 0;
        const char *name = ce->name;
 
-       while (pos < active_nr) {
-               ce = active_cache[pos];
+       while (pos < the_index.cache_nr) {
+               ce = the_index.cache[pos];
                if (strcmp(name, ce->name))
                        break;
                seen |= (1 << ce_stage(ce));
@@ -214,15 +226,15 @@ static int checkout_stage(int stage, const struct cache_entry *ce, int pos,
                          const struct checkout *state, int *nr_checkouts,
                          int overlay_mode)
 {
-       while (pos < active_nr &&
-              !strcmp(active_cache[pos]->name, ce->name)) {
-               if (ce_stage(active_cache[pos]) == stage)
-                       return checkout_entry(active_cache[pos], state,
+       while (pos < the_index.cache_nr &&
+              !strcmp(the_index.cache[pos]->name, ce->name)) {
+               if (ce_stage(the_index.cache[pos]) == stage)
+                       return checkout_entry(the_index.cache[pos], state,
                                              NULL, nr_checkouts);
                pos++;
        }
        if (!overlay_mode) {
-               unlink_entry(ce);
+               unlink_entry(ce, NULL);
                return 0;
        }
        if (stage == 2)
@@ -234,9 +246,10 @@ static int checkout_stage(int stage, const struct cache_entry *ce, int pos,
 static int checkout_merged(int pos, const struct checkout *state,
                           int *nr_checkouts, struct mem_pool *ce_mem_pool)
 {
-       struct cache_entry *ce = active_cache[pos];
+       struct cache_entry *ce = the_index.cache[pos];
        const char *path = ce->name;
        mmfile_t ancestor, ours, theirs;
+       enum ll_merge_result merge_status;
        int status;
        struct object_id oid;
        mmbuffer_t result_buf;
@@ -246,7 +259,7 @@ static int checkout_merged(int pos, const struct checkout *state,
        int renormalize = 0;
 
        memset(threeway, 0, sizeof(threeway));
-       while (pos < active_nr) {
+       while (pos < the_index.cache_nr) {
                int stage;
                stage = ce_stage(ce);
                if (!stage || strcmp(path, ce->name))
@@ -255,7 +268,7 @@ static int checkout_merged(int pos, const struct checkout *state,
                if (stage == 2)
                        mode = create_ce_mode(ce->ce_mode);
                pos++;
-               ce = active_cache[pos];
+               ce = the_index.cache[pos];
        }
        if (is_null_oid(&threeway[1]) || is_null_oid(&threeway[2]))
                return error(_("path '%s' does not have necessary versions"), path);
@@ -267,13 +280,16 @@ static int checkout_merged(int pos, const struct checkout *state,
        memset(&ll_opts, 0, sizeof(ll_opts));
        git_config_get_bool("merge.renormalize", &renormalize);
        ll_opts.renormalize = renormalize;
-       status = ll_merge(&result_buf, path, &ancestor, "base",
-                         &ours, "ours", &theirs, "theirs",
-                         state->istate, &ll_opts);
+       merge_status = ll_merge(&result_buf, path, &ancestor, "base",
+                               &ours, "ours", &theirs, "theirs",
+                               state->istate, &ll_opts);
        free(ancestor.ptr);
        free(ours.ptr);
        free(theirs.ptr);
-       if (status < 0 || !result_buf.ptr) {
+       if (merge_status == LL_MERGE_BINARY_CONFLICT)
+               warning("Cannot merge binary files: %s (%s vs. %s)",
+                       path, "ours", "theirs");
+       if (merge_status < 0 || !result_buf.ptr) {
                free(result_buf.ptr);
                return error(_("path '%s': cannot merge"), path);
        }
@@ -290,7 +306,7 @@ static int checkout_merged(int pos, const struct checkout *state,
         * (it also writes the merge result to the object database even
         * when it may contain conflicts).
         */
-       if (write_object_file(result_buf.ptr, result_buf.size, blob_type, &oid))
+       if (write_object_file(result_buf.ptr, result_buf.size, OBJ_BLOB, &oid))
                die(_("Unable to add merge result for '%s'"), path);
        free(result_buf.ptr);
        ce = make_transient_cache_entry(mode, &oid, path, 2, ce_mem_pool);
@@ -378,8 +394,8 @@ static int checkout_worktree(const struct checkout_opts *opts,
        if (pc_workers > 1)
                init_parallel_checkout();
 
-       for (pos = 0; pos < active_nr; pos++) {
-               struct cache_entry *ce = active_cache[pos];
+       for (pos = 0; pos < the_index.cache_nr; pos++) {
+               struct cache_entry *ce = the_index.cache[pos];
                if (ce->ce_flags & CE_MATCHED) {
                        if (!ce_stage(ce)) {
                                errs |= checkout_entry(ce, &state,
@@ -404,7 +420,7 @@ static int checkout_worktree(const struct checkout_opts *opts,
        mem_pool_discard(&ce_mem_pool, should_validate_cache_entries());
        remove_marked_cache_entries(&the_index, 1);
        remove_scheduled_dirs();
-       errs |= finish_delayed_checkout(&state, &nr_checkouts, opts->show_progress);
+       errs |= finish_delayed_checkout(&state, opts->show_progress);
 
        if (opts->count_checkout_paths) {
                if (nr_unmerged)
@@ -456,10 +472,10 @@ static int checkout_paths(const struct checkout_opts *opts,
                die(_("'%s' cannot be used with updating paths"), "--detach");
 
        if (opts->merge && opts->patch_mode)
-               die(_("'%s' cannot be used with %s"), "--merge", "--patch");
+               die(_("options '%s' and '%s' cannot be used together"), "--merge", "--patch");
 
        if (opts->ignore_unmerged && opts->merge)
-               die(_("'%s' cannot be used with %s"),
+               die(_("options '%s' and '%s' cannot be used together"),
                    opts->ignore_unmerged_opt, "-m");
 
        if (opts->new_branch)
@@ -474,18 +490,31 @@ static int checkout_paths(const struct checkout_opts *opts,
                die(_("'%s' must be used when '%s' is not specified"),
                    "--worktree", "--source");
 
-       if (opts->checkout_index && !opts->checkout_worktree &&
-           opts->writeout_stage)
-               die(_("'%s' or '%s' cannot be used with %s"),
-                   "--ours", "--theirs", "--staged");
-
-       if (opts->checkout_index && !opts->checkout_worktree &&
-           opts->merge)
-               die(_("'%s' or '%s' cannot be used with %s"),
-                   "--merge", "--conflict", "--staged");
+       /*
+        * Reject --staged option to the restore command when combined with
+        * merge-related options. Use the accept_ref flag to distinguish it
+        * from the checkout command, which does not accept --staged anyway.
+        *
+        * `restore --ours|--theirs --worktree --staged` could mean resolving
+        * conflicted paths to one side in both the worktree and the index,
+        * but does not currently.
+        *
+        * `restore --merge|--conflict=<style>` already recreates conflicts
+        * in both the worktree and the index, so adding --staged would be
+        * meaningless.
+        */
+       if (!opts->accept_ref && opts->checkout_index) {
+               if (opts->writeout_stage)
+                       die(_("'%s' or '%s' cannot be used with %s"),
+                           "--ours", "--theirs", "--staged");
+
+               if (opts->merge)
+                       die(_("'%s' or '%s' cannot be used with %s"),
+                           "--merge", "--conflict", "--staged");
+       }
 
        if (opts->patch_mode) {
-               const char *patch_mode;
+               enum add_p_mode patch_mode;
                const char *rev = new_branch_info->name;
                char rev_oid[GIT_MAX_HEXSZ + 1];
 
@@ -503,19 +532,20 @@ static int checkout_paths(const struct checkout_opts *opts,
                        rev = oid_to_hex_r(rev_oid, &new_branch_info->commit->object.oid);
 
                if (opts->checkout_index && opts->checkout_worktree)
-                       patch_mode = "--patch=checkout";
+                       patch_mode = ADD_P_CHECKOUT;
                else if (opts->checkout_index && !opts->checkout_worktree)
-                       patch_mode = "--patch=reset";
+                       patch_mode = ADD_P_RESET;
                else if (!opts->checkout_index && opts->checkout_worktree)
-                       patch_mode = "--patch=worktree";
+                       patch_mode = ADD_P_WORKTREE;
                else
                        BUG("either flag must have been set, worktree=%d, index=%d",
                            opts->checkout_worktree, opts->checkout_index);
-               return run_add_interactive(rev, patch_mode, &opts->pathspec);
+               return !!run_add_p(the_repository, patch_mode, rev,
+                                  &opts->pathspec);
        }
 
        repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
-       if (read_cache_preload(&opts->pathspec) < 0)
+       if (repo_read_index_preload(the_repository, &opts->pathspec, 0) < 0)
                return error(_("index file corrupt"));
 
        if (opts->source_tree)
@@ -527,13 +557,13 @@ static int checkout_paths(const struct checkout_opts *opts,
         * Make sure all pathspecs participated in locating the paths
         * to be checked out.
         */
-       for (pos = 0; pos < active_nr; pos++)
+       for (pos = 0; pos < the_index.cache_nr; pos++)
                if (opts->overlay_mode)
-                       mark_ce_for_checkout_overlay(active_cache[pos],
+                       mark_ce_for_checkout_overlay(the_index.cache[pos],
                                                     ps_matched,
                                                     opts);
                else
-                       mark_ce_for_checkout_no_overlay(active_cache[pos],
+                       mark_ce_for_checkout_no_overlay(the_index.cache[pos],
                                                        ps_matched,
                                                        opts);
 
@@ -548,8 +578,8 @@ static int checkout_paths(const struct checkout_opts *opts,
                unmerge_marked_index(&the_index);
 
        /* Any unmerged paths? */
-       for (pos = 0; pos < active_nr; pos++) {
-               const struct cache_entry *ce = active_cache[pos];
+       for (pos = 0; pos < the_index.cache_nr; pos++) {
+               const struct cache_entry *ce = the_index.cache[pos];
                if (ce->ce_flags & CE_MATCHED) {
                        if (!ce_stage(ce))
                                continue;
@@ -613,10 +643,11 @@ static void show_local_changes(struct object *head,
        repo_init_revisions(the_repository, &rev, NULL);
        rev.diffopt.flags = opts->flags;
        rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
+       rev.diffopt.flags.recursive = 1;
        diff_setup_done(&rev.diffopt);
        add_pending_object(&rev, head, NULL);
        run_diff_index(&rev, 0);
-       object_array_clear(&rev.pending);
+       release_revisions(&rev);
 }
 
 static void describe_detached_head(const char *msg, struct commit *commit)
@@ -646,7 +677,9 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
        opts.head_idx = -1;
        opts.update = worktree;
        opts.skip_unmerged = !worktree;
-       opts.reset = 1;
+       opts.reset = o->force ? UNPACK_RESET_OVERWRITE_UNTRACKED :
+                               UNPACK_RESET_PROTECT_UNTRACKED;
+       opts.preserve_ignored = (!o->force && !o->overwrite_ignore);
        opts.merge = 1;
        opts.fn = oneway_merge;
        opts.verbose_update = o->show_progress;
@@ -686,12 +719,35 @@ static void setup_branch_path(struct branch_info *branch)
                repo_get_oid_committish(the_repository, branch->name, &branch->oid);
 
        strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);
-       if (strcmp(buf.buf, branch->name))
+       if (strcmp(buf.buf, branch->name)) {
+               free(branch->name);
                branch->name = xstrdup(buf.buf);
+       }
        strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
+       free(branch->path);
        branch->path = strbuf_detach(&buf, NULL);
 }
 
+static void init_topts(struct unpack_trees_options *topts, int merge,
+                      int show_progress, int overwrite_ignore,
+                      struct commit *old_commit)
+{
+       memset(topts, 0, sizeof(*topts));
+       topts->head_idx = -1;
+       topts->src_index = &the_index;
+       topts->dst_index = &the_index;
+
+       setup_unpack_trees_porcelain(topts, "checkout");
+
+       topts->initial_checkout = is_index_unborn(&the_index);
+       topts->update = 1;
+       topts->merge = 1;
+       topts->quiet = merge && old_commit;
+       topts->verbose_update = show_progress;
+       topts->fn = twoway_merge;
+       topts->preserve_ignored = !overwrite_ignore;
+}
+
 static int merge_working_tree(const struct checkout_opts *opts,
                              struct branch_info *old_branch_info,
                              struct branch_info *new_branch_info,
@@ -701,11 +757,11 @@ static int merge_working_tree(const struct checkout_opts *opts,
        struct lock_file lock_file = LOCK_INIT;
        struct tree *new_tree;
 
-       hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
-       if (read_cache_preload(NULL) < 0)
+       repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
+       if (repo_read_index_preload(the_repository, NULL, 0) < 0)
                return error(_("index file corrupt"));
 
-       resolve_undo_clear();
+       resolve_undo_clear_index(&the_index);
        if (opts->new_orphan_branch && opts->orphan_from_empty_tree) {
                if (new_branch_info->commit)
                        BUG("'switch --orphan' should never accept a commit as starting point");
@@ -720,40 +776,31 @@ static int merge_working_tree(const struct checkout_opts *opts,
                struct tree_desc trees[2];
                struct tree *tree;
                struct unpack_trees_options topts;
+               const struct object_id *old_commit_oid;
 
-               memset(&topts, 0, sizeof(topts));
-               topts.head_idx = -1;
-               topts.src_index = &the_index;
-               topts.dst_index = &the_index;
+               refresh_index(&the_index, REFRESH_QUIET, NULL, NULL, NULL);
 
-               setup_unpack_trees_porcelain(&topts, "checkout");
-
-               refresh_cache(REFRESH_QUIET);
-
-               if (unmerged_cache()) {
+               if (unmerged_index(&the_index)) {
                        error(_("you need to resolve your current index first"));
                        return 1;
                }
 
                /* 2-way merge to the new branch */
-               topts.initial_checkout = is_cache_unborn();
-               topts.update = 1;
-               topts.merge = 1;
-               topts.quiet = opts->merge && old_branch_info->commit;
-               topts.verbose_update = opts->show_progress;
-               topts.fn = twoway_merge;
+               init_topts(&topts, opts->merge, opts->show_progress,
+                          opts->overwrite_ignore, old_branch_info->commit);
                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;
-                       setup_standard_excludes(topts.dir);
-               }
-               tree = parse_tree_indirect(old_branch_info->commit ?
-                                          &old_branch_info->commit->object.oid :
-                                          the_hash_algo->empty_tree);
+
+               old_commit_oid = old_branch_info->commit ?
+                       &old_branch_info->commit->object.oid :
+                       the_hash_algo->empty_tree;
+               tree = parse_tree_indirect(old_commit_oid);
+               if (!tree)
+                       die(_("unable to parse commit %s"),
+                               oid_to_hex(old_commit_oid));
+
                init_tree_desc(&trees[0], tree->buffer, tree->size);
                parse_tree(new_tree);
                tree = new_tree;
@@ -813,7 +860,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
                        if (ret)
                                return ret;
                        o.ancestor = old_branch_info->name;
-                       if (old_branch_info->name == NULL) {
+                       if (!old_branch_info->name) {
                                strbuf_add_unique_abbrev(&old_commit_shortname,
                                                         &old_branch_info->commit->object.oid,
                                                         DEFAULT_ABBREV);
@@ -837,7 +884,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
                }
        }
 
-       if (!cache_tree_fully_valid(active_cache_tree))
+       if (!cache_tree_fully_valid(the_index.cache_tree))
                cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR);
 
        if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
@@ -876,7 +923,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
                                int ret;
                                struct strbuf err = STRBUF_INIT;
 
-                               ret = safe_create_reflog(refname, 1, &err);
+                               ret = safe_create_reflog(refname, &err);
                                if (ret) {
                                        fprintf(stderr, _("Can not do reflog for '%s': %s\n"),
                                                opts->new_orphan_branch, err.buf);
@@ -895,8 +942,11 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
                                      opts->new_branch_force ? 1 : 0,
                                      opts->new_branch_log,
                                      opts->quiet,
-                                     opts->track);
-               new_branch_info->name = opts->new_branch;
+                                     opts->track,
+                                     0);
+               free(new_branch_info->name);
+               free(new_branch_info->refname);
+               new_branch_info->name = xstrdup(opts->new_branch);
                setup_branch_path(new_branch_info);
        }
 
@@ -918,7 +968,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
                           REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);
                if (!opts->quiet) {
                        if (old_branch_info->path &&
-                           advice_detached_head && !opts->force_detach)
+                           advice_enabled(ADVICE_DETACHED_HEAD) && !opts->force_detach)
                                detach_advice(new_branch_info->name);
                        describe_detached_head(_("HEAD is now at"), new_branch_info->commit);
                }
@@ -957,7 +1007,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 
 static int add_pending_uninteresting_ref(const char *refname,
                                         const struct object_id *oid,
-                                        int flags, void *cb_data)
+                                        int flags UNUSED, void *cb_data)
 {
        add_pending_oid(cb_data, refname, oid, UNINTERESTING);
        return 0;
@@ -1011,7 +1061,7 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
                sb.buf);
        strbuf_release(&sb);
 
-       if (advice_detached_head)
+       if (advice_enabled(ADVICE_DETACHED_HEAD))
                fprintf(stderr,
                        Q_(
                        /* The singular version */
@@ -1058,14 +1108,14 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne
 
        /* Clean up objects used, as they will be reused. */
        repo_clear_commit_marks(the_repository, ALL_REV_FLAGS);
+       release_revisions(&revs);
 }
 
 static int switch_branches(const struct checkout_opts *opts,
                           struct branch_info *new_branch_info)
 {
        int ret = 0;
-       struct branch_info old_branch_info;
-       void *path_to_free;
+       struct branch_info old_branch_info = { 0 };
        struct object_id rev;
        int flag, writeout_error = 0;
        int do_merge = 1;
@@ -1073,25 +1123,29 @@ static int switch_branches(const struct checkout_opts *opts,
        trace2_cmd_mode("branch");
 
        memset(&old_branch_info, 0, sizeof(old_branch_info));
-       old_branch_info.path = path_to_free = resolve_refdup("HEAD", 0, &rev, &flag);
+       old_branch_info.path = resolve_refdup("HEAD", 0, &rev, &flag);
        if (old_branch_info.path)
                old_branch_info.commit = lookup_commit_reference_gently(the_repository, &rev, 1);
        if (!(flag & REF_ISSYMREF))
-               old_branch_info.path = NULL;
+               FREE_AND_NULL(old_branch_info.path);
 
-       if (old_branch_info.path)
-               skip_prefix(old_branch_info.path, "refs/heads/", &old_branch_info.name);
+       if (old_branch_info.path) {
+               const char *const prefix = "refs/heads/";
+               const char *p;
+               if (skip_prefix(old_branch_info.path, prefix, &p))
+                       old_branch_info.name = xstrdup(p);
+       }
 
        if (opts->new_orphan_branch && opts->orphan_from_empty_tree) {
                if (new_branch_info->name)
                        BUG("'switch --orphan' should never accept a commit as starting point");
                new_branch_info->commit = NULL;
-               new_branch_info->name = "(empty)";
+               new_branch_info->name = xstrdup("(empty)");
                do_merge = 1;
        }
 
        if (!new_branch_info->name) {
-               new_branch_info->name = "HEAD";
+               new_branch_info->name = xstrdup("HEAD");
                new_branch_info->commit = old_branch_info.commit;
                if (!new_branch_info->commit)
                        die(_("You are on a branch yet to be born"));
@@ -1104,7 +1158,7 @@ static int switch_branches(const struct checkout_opts *opts,
        if (do_merge) {
                ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
                if (ret) {
-                       free(path_to_free);
+                       branch_info_release(&old_branch_info);
                        return ret;
                }
        }
@@ -1115,7 +1169,8 @@ static int switch_branches(const struct checkout_opts *opts,
        update_refs_for_switch(opts, &old_branch_info, new_branch_info);
 
        ret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);
-       free(path_to_free);
+       branch_info_release(&old_branch_info);
+
        return ret || writeout_error;
 }
 
@@ -1147,16 +1202,15 @@ static void setup_new_branch_info_and_source_tree(
        struct tree **source_tree = &opts->source_tree;
        struct object_id branch_rev;
 
-       new_branch_info->name = arg;
+       new_branch_info->name = xstrdup(arg);
        setup_branch_path(new_branch_info);
 
        if (!check_refname_format(new_branch_info->path, 0) &&
            !read_ref(new_branch_info->path, &branch_rev))
                oidcpy(rev, &branch_rev);
-       else {
-               free((char *)new_branch_info->path);
-               new_branch_info->path = NULL; /* not an existing branch */
-       }
+       else
+               /* not an existing branch */
+               FREE_AND_NULL(new_branch_info->path);
 
        new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
        if (!new_branch_info->commit) {
@@ -1182,7 +1236,7 @@ static const char *parse_remote_branch(const char *arg,
        }
 
        if (!remote && num_matches > 1) {
-           if (advice_checkout_ambiguous_remote_branch_name) {
+           if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) {
                    advise(_("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"
@@ -1232,7 +1286,7 @@ static int parse_branchname_arg(int argc, const char **argv,
         *       between A and B, A...B names that merge base.
         *
         *   (b) If <something> is _not_ a commit, either "--" is present
-        *       or <something> is not a path, no -t or -b was given, and
+        *       or <something> is not a path, no -t or -b was given,
         *       and there is a tracking branch whose name is <something>
         *       in one and only one remote (or if the branch exists on the
         *       remote named in checkout.defaultRemote), then this is a
@@ -1377,23 +1431,31 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)
 {
        struct object_id oid;
        char *to_free;
+       int code;
 
        if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free, 0) == 1) {
                const char *ref = to_free;
 
                if (skip_prefix(ref, "refs/tags/", &ref))
-                       die(_("a branch is expected, got tag '%s'"), ref);
-               if (skip_prefix(ref, "refs/remotes/", &ref))
-                       die(_("a branch is expected, got remote branch '%s'"), ref);
-               die(_("a branch is expected, got '%s'"), ref);
+                       code = die_message(_("a branch is expected, got tag '%s'"), ref);
+               else if (skip_prefix(ref, "refs/remotes/", &ref))
+                       code = die_message(_("a branch is expected, got remote branch '%s'"), ref);
+               else
+                       code = die_message(_("a branch is expected, got '%s'"), ref);
        }
-       if (branch_info->commit)
-               die(_("a branch is expected, got commit '%s'"), branch_info->name);
-       /*
-        * This case should never happen because we already die() on
-        * non-commit, but just in case.
-        */
-       die(_("a branch is expected, got '%s'"), branch_info->name);
+       else if (branch_info->commit)
+               code = die_message(_("a branch is expected, got commit '%s'"), branch_info->name);
+       else
+               /*
+                * This case should never happen because we already die() on
+                * non-commit, but just in case.
+                */
+               code = die_message(_("a branch is expected, got '%s'"), branch_info->name);
+
+       if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD))
+               advise(_("If you want to detach HEAD at the commit, try again with the --detach option."));
+
+       exit(code);
 }
 
 static void die_if_some_operation_in_progress(void)
@@ -1425,6 +1487,8 @@ static void die_if_some_operation_in_progress(void)
                      "or \"git worktree add\"."));
        if (state.bisect_in_progress)
                warning(_("you are switching branch while bisecting"));
+
+       wt_status_state_free_buffers(&state);
 }
 
 static int checkout_branch(struct checkout_opts *opts,
@@ -1519,7 +1583,7 @@ static struct option *add_common_options(struct checkout_opts *opts,
                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"),
-                          N_("conflict style (merge or diff3)")),
+                          N_("conflict style (merge, diff3, or zdiff3)")),
                OPT_END()
        };
        struct option *newopts = parse_options_concat(prevopts, options);
@@ -1532,8 +1596,10 @@ static struct option *add_common_switch_branch_options(
 {
        struct option options[] = {
                OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
-               OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
-                       BRANCH_TRACK_EXPLICIT),
+               OPT_CALLBACK_F('t', "track",  &opts->track, "(direct|inherit)",
+                       N_("set branch tracking configuration"),
+                       PARSE_OPT_OPTARG,
+                       parse_opt_tracking_mode),
                OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
                           PARSE_OPT_NOCOMPLETE),
                OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
@@ -1576,20 +1642,20 @@ 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[])
+                        const char * const usagestr[],
+                        struct branch_info *new_branch_info)
 {
-       struct branch_info new_branch_info;
        int parseopt_flags = 0;
 
-       memset(&new_branch_info, 0, sizeof(new_branch_info));
        opts->overwrite_ignore = 1;
        opts->prefix = prefix;
        opts->show_progress = -1;
 
        git_config(git_checkout_config, opts);
-
-       prepare_repo_settings(the_repository);
-       the_repository->settings.command_requires_full_index = 0;
+       if (the_repository->gitdir) {
+               prepare_repo_settings(the_repository);
+               the_repository->settings.command_requires_full_index = 0;
+       }
 
        opts->track = BRANCH_TRACK_UNSPECIFIED;
 
@@ -1619,11 +1685,11 @@ 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(_("-%c, -%c and --orphan are mutually exclusive"),
-                               cb_option, toupper(cb_option));
+               die(_("options '-%c', '-%c', and '%s' cannot be used together"),
+                       cb_option, toupper(cb_option), "--orphan");
 
        if (opts->overlay_mode == 1 && opts->patch_mode)
-               die(_("-p and --overlay are mutually exclusive"));
+               die(_("options '%s' and '%s' cannot be used together"), "-p", "--overlay");
 
        if (opts->checkout_index >= 0 || opts->checkout_worktree >= 0) {
                if (opts->checkout_index < 0)
@@ -1690,7 +1756,7 @@ static int checkout_main(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);
                argv += n;
                argc -= n;
        } else if (!opts->accept_ref && opts->from_treeish) {
@@ -1699,7 +1765,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
                if (get_oid_mb(opts->from_treeish, &rev))
                        die(_("could not resolve %s"), opts->from_treeish);
 
-               setup_new_branch_info_and_source_tree(&new_branch_info,
+               setup_new_branch_info_and_source_tree(new_branch_info,
                                                      opts, &rev,
                                                      opts->from_treeish);
 
@@ -1719,7 +1785,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 && !new_branch_info.commit)
+               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);
 
@@ -1730,19 +1796,19 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 
        if (opts->pathspec_from_file) {
                if (opts->pathspec.nr)
-                       die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+                       die(_("'%s' and pathspec arguments cannot be used together"), "--pathspec-from-file");
 
                if (opts->force_detach)
-                       die(_("--pathspec-from-file is incompatible with --detach"));
+                       die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--detach");
 
                if (opts->patch_mode)
-                       die(_("--pathspec-from-file is incompatible with --patch"));
+                       die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--patch");
 
                parse_pathspec_file(&opts->pathspec, 0,
                                    0,
                                    prefix, opts->pathspec_from_file, opts->pathspec_file_nul);
        } else if (opts->pathspec_file_nul) {
-               die(_("--pathspec-file-nul requires --pathspec-from-file"));
+               die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file");
        }
 
        opts->pathspec.recursive = 1;
@@ -1768,11 +1834,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
                strbuf_release(&buf);
        }
 
-       UNLEAK(opts);
        if (opts->patch_mode || opts->pathspec.nr)
-               return checkout_paths(opts, &new_branch_info);
+               return checkout_paths(opts, new_branch_info);
        else
-               return checkout_branch(opts, &new_branch_info);
+               return checkout_branch(opts, new_branch_info);
 }
 
 int cmd_checkout(int argc, const char **argv, const char *prefix)
@@ -1791,6 +1856,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
        int ret;
+       struct branch_info new_branch_info = { 0 };
 
        memset(&opts, 0, sizeof(opts));
        opts.dwim_new_local_branch = 1;
@@ -1821,7 +1887,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        options = add_checkout_path_options(&opts, options);
 
        ret = checkout_main(argc, argv, prefix, &opts,
-                           options, checkout_usage);
+                           options, checkout_usage, &new_branch_info);
+       branch_info_release(&new_branch_info);
+       clear_pathspec(&opts.pathspec);
+       free(opts.pathspec_from_file);
        FREE_AND_NULL(options);
        return ret;
 }
@@ -1842,6 +1911,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
        int ret;
+       struct branch_info new_branch_info = { 0 };
 
        memset(&opts, 0, sizeof(opts));
        opts.dwim_new_local_branch = 1;
@@ -1861,7 +1931,8 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
        cb_option = 'c';
 
        ret = checkout_main(argc, argv, prefix, &opts,
-                           options, switch_branch_usage);
+                           options, switch_branch_usage, &new_branch_info);
+       branch_info_release(&new_branch_info);
        FREE_AND_NULL(options);
        return ret;
 }
@@ -1883,6 +1954,7 @@ int cmd_restore(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
        int ret;
+       struct branch_info new_branch_info = { 0 };
 
        memset(&opts, 0, sizeof(opts));
        opts.accept_ref = 0;
@@ -1898,7 +1970,8 @@ int cmd_restore(int argc, const char **argv, const char *prefix)
        options = add_checkout_path_options(&opts, options);
 
        ret = checkout_main(argc, argv, prefix, &opts,
-                           options, restore_usage);
+                           options, restore_usage, &new_branch_info);
+       branch_info_release(&new_branch_info);
        FREE_AND_NULL(options);
        return ret;
 }