#include "unpack-trees.h"
#include "xdiff-interface.h"
-static int checkout_optimize_new_branch;
-
static const char * const checkout_usage[] = {
N_("git checkout [<options>] <branch>"),
N_("git checkout [<options>] [<branch>] -- <file>..."),
int merge;
int force;
int force_detach;
+ int implicit_detach;
int writeout_stage;
int overwrite_ignore;
int ignore_skipworktree;
int show_progress;
int count_checkout_paths;
int overlay_mode;
- int no_dwim_new_local_branch;
+ int dwim_new_local_branch;
int discard_changes;
-
- /*
- * If new checkout options are added, skip_merge_working_tree
- * should be updated accordingly.
- */
+ int accept_pathspec;
+ int switch_branch_doing_nothing_is_ok;
+ int only_merge_on_switching_branches;
const char *new_branch;
const char *new_branch_force;
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
- */
-
- /*
- * opts->overlay_mode cannot be used with switching branches so is
- * not tested here
- */
-
- /*
- * 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
- */
-
- /*
- * Do the merge if this is the initial checkout. We cannot use
- * is_cache_unborn() here because the index hasn't been loaded yet
- * so cache_nr and timestamp.sec are always zero.
- */
- if (!file_exists(get_index_file()))
- return 0;
-
- return 1;
-}
-
static int merge_working_tree(const struct checkout_opts *opts,
struct branch_info *old_branch_info,
struct branch_info *new_branch_info,
void *path_to_free;
struct object_id rev;
int flag, writeout_error = 0;
+ int do_merge = 1;
trace2_cmd_mode("branch");
if (!new_branch_info->commit)
die(_("You are on a branch yet to be born"));
parse_commit_or_die(new_branch_info->commit);
+
+ if (opts->only_merge_on_switching_branches)
+ do_merge = 0;
}
- /* 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 {
+ if (do_merge) {
ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
if (ret) {
free(path_to_free);
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);
if (!argc)
return 0;
+ if (!opts->accept_pathspec) {
+ if (argc > 1)
+ die(_("only one reference expected"));
+ has_dash_dash = 1; /* helps disambiguate */
+ }
+
arg = argv[0];
dash_dash_pos = -1;
for (i = 0; i < argc; i++) {
- if (!strcmp(argv[i], "--")) {
+ if (opts->accept_pathspec && !strcmp(argv[i], "--")) {
dash_dash_pos = i;
break;
}
recover_with_dwim = 0;
/*
- * Accept "git checkout foo" and "git checkout foo --"
- * as candidates for dwim.
+ * Accept "git checkout foo", "git checkout foo --"
+ * and "git switch foo" as candidates for dwim.
*/
if (!(argc == 1 && !has_dash_dash) &&
- !(argc == 2 && has_dash_dash))
+ !(argc == 2 && has_dash_dash) &&
+ opts->accept_pathspec)
recover_with_dwim = 0;
if (recover_with_dwim) {
*/
if (argc)
verify_non_filename(opts->prefix, arg);
- } else {
+ } else if (opts->accept_pathspec) {
argcount++;
argv++;
argc--;
return status;
}
+static void die_expecting_a_branch(const struct branch_info *branch_info)
+{
+ struct object_id oid;
+ char *to_free;
+
+ if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free) == 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);
+ }
+ 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);
+}
+
static int checkout_branch(struct checkout_opts *opts,
struct branch_info *new_branch_info)
{
die(_("Cannot switch branch to a non-commit '%s'"),
new_branch_info->name);
+ if (!opts->switch_branch_doing_nothing_is_ok &&
+ !new_branch_info->name &&
+ !opts->new_branch &&
+ !opts->force_detach)
+ die(_("missing branch or commit argument"));
+
+ if (!opts->implicit_detach &&
+ !opts->force_detach &&
+ !opts->new_branch &&
+ !opts->new_branch_force &&
+ new_branch_info->name &&
+ !new_branch_info->path)
+ die_expecting_a_branch(new_branch_info);
+
if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
!opts->ignore_other_worktrees) {
int flag;
struct checkout_opts *opts, struct option *prevopts)
{
struct option options[] = {
- OPT_BOOL(0, "detach", &opts->force_detach, N_("detach HEAD at named commit")),
+ 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_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
N_("update ignored files (default)"),
PARSE_OPT_NOCOMPLETE),
- OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
- N_("second guess 'git checkout <no-such-branch>'")),
OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
N_("do not check if another worktree is holding the given ref")),
OPT_END()
{
struct branch_info new_branch_info;
int dwim_remotes_matched = 0;
- int dwim_new_local_branch;
memset(&new_branch_info, 0, sizeof(new_branch_info));
opts->overwrite_ignore = 1;
argc = parse_options(argc, argv, prefix, options, usagestr,
PARSE_OPT_KEEP_DASHDASH);
- dwim_new_local_branch = !opts->no_dwim_new_local_branch;
if (opts->show_progress < 0) {
if (opts->quiet)
opts->show_progress = 0;
struct object_id rev;
int dwim_ok =
!opts->patch_mode &&
- dwim_new_local_branch &&
+ opts->dwim_new_local_branch &&
opts->track == BRANCH_TRACK_UNSPECIFIED &&
!opts->new_branch;
int n = parse_branchname_arg(argc, argv, dwim_ok,
OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"),
N_("create/reset and checkout a branch")),
OPT_BOOL('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")),
+ OPT_BOOL(0, "guess", &opts.dwim_new_local_branch,
+ N_("second guess 'git checkout <no-such-branch>' (default)")),
OPT_END()
};
int ret;
memset(&opts, 0, sizeof(opts));
- opts.no_dwim_new_local_branch = 0;
+ opts.dwim_new_local_branch = 1;
+ opts.switch_branch_doing_nothing_is_ok = 1;
+ opts.only_merge_on_switching_branches = 0;
+ opts.accept_pathspec = 1;
+ opts.implicit_detach = 1;
options = parse_options_dup(checkout_options);
options = add_common_options(&opts, options);
N_("create and switch to a new branch")),
OPT_STRING('C', "force-create", &opts.new_branch_force, N_("branch"),
N_("create/reset and switch to a branch")),
+ OPT_BOOL(0, "guess", &opts.dwim_new_local_branch,
+ N_("second guess 'git switch <no-such-branch>'")),
OPT_BOOL(0, "discard-changes", &opts.discard_changes,
N_("throw away local modifications")),
OPT_END()
int ret;
memset(&opts, 0, sizeof(opts));
- opts.no_dwim_new_local_branch = 0;
+ opts.dwim_new_local_branch = 1;
+ opts.accept_pathspec = 0;
+ opts.switch_branch_doing_nothing_is_ok = 0;
+ opts.only_merge_on_switching_branches = 1;
+ opts.implicit_detach = 0;
options = parse_options_dup(switch_options);
options = add_common_options(&opts, options);