diff options
Diffstat (limited to 'builtin/checkout.c')
-rw-r--r-- | builtin/checkout.c | 137 |
1 files changed, 89 insertions, 48 deletions
diff --git a/builtin/checkout.c b/builtin/checkout.c index 6123f732a2c8..d6773818b803 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -70,6 +70,8 @@ struct checkout_opts { int checkout_worktree; const char *ignore_unmerged_opt; int ignore_unmerged; + int pathspec_file_nul; + const char *pathspec_from_file; const char *new_branch; const char *new_branch_force; @@ -126,6 +128,7 @@ 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 && + !ce_intent_to_add(old) && oideq(&ce->oid, &old->oid)) { old->ce_flags |= CE_UPDATE; discard_cache_entry(ce); @@ -521,6 +524,8 @@ 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); + else + remove_marked_cache_entries(&the_index, 1); /* * Allow updating the index when checking out from the index. @@ -708,11 +713,11 @@ static int merge_working_tree(const struct checkout_opts *opts, * give up or do a real merge, depending on * whether the merge flag was used. */ - struct tree *result; struct tree *work; struct tree *old_tree; struct merge_options o; struct strbuf sb = STRBUF_INIT; + struct strbuf old_commit_shortname = STRBUF_INIT; if (!opts->merge) return 1; @@ -730,13 +735,6 @@ static int merge_working_tree(const struct checkout_opts *opts, "the following files:\n%s"), sb.buf); strbuf_release(&sb); - if (repo_index_has_changes(the_repository, - get_commit_tree(old_branch_info->commit), - &sb)) - warning(_("staged changes in the following files may be lost: %s"), - sb.buf); - strbuf_release(&sb); - /* Do more real merge */ /* @@ -760,7 +758,7 @@ static int merge_working_tree(const struct checkout_opts *opts, */ init_merge_options(&o, the_repository); o.verbosity = 0; - work = write_tree_from_memory(&o); + work = write_in_core_index_as_tree(the_repository); ret = reset_tree(new_tree, opts, 1, @@ -768,19 +766,25 @@ 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) { + strbuf_add_unique_abbrev(&old_commit_shortname, + &old_branch_info->commit->object.oid, + DEFAULT_ABBREV); + o.ancestor = old_commit_shortname.buf; + } o.branch1 = new_branch_info->name; o.branch2 = "local"; ret = merge_trees(&o, new_tree, work, - old_tree, - &result); + old_tree); if (ret < 0) exit(128); ret = reset_tree(new_tree, opts, 0, writeout_error); strbuf_release(&o.obuf); + strbuf_release(&old_commit_shortname); if (ret) return ret; } @@ -861,7 +865,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, strbuf_addf(&msg, "checkout: moving from %s to %s", old_desc ? old_desc : "(invalid)", new_branch_info->name); else - strbuf_insert(&msg, 0, reflog_msg, strlen(reflog_msg)); + strbuf_insertstr(&msg, 0, reflog_msg); if (!strcmp(new_branch_info->name, "HEAD") && !new_branch_info->path && !opts->force_detach) { /* Nothing to do. */ @@ -1113,12 +1117,43 @@ static void setup_new_branch_info_and_source_tree( } } +static const char *parse_remote_branch(const char *arg, + struct object_id *rev, + int could_be_checkout_paths) +{ + int num_matches = 0; + const char *remote = unique_tracking_name(arg, rev, &num_matches); + + if (remote && could_be_checkout_paths) { + die(_("'%s' could be both a local file and a tracking branch.\n" + "Please use -- (and optionally --no-guess) to disambiguate"), + arg); + } + + if (!remote && num_matches > 1) { + if (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" + " 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.")); + } + + die(_("'%s' matched multiple (%d) remote tracking branches"), + arg, num_matches); + } + + return remote; +} + 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, - int *dwim_remotes_matched) + struct object_id *rev) { const char **new_branch = &opts->new_branch; int argcount = 0; @@ -1223,13 +1258,9 @@ 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, - dwim_remotes_matched); + const char *remote = parse_remote_branch(arg, rev, + could_be_checkout_paths); if (remote) { - if (could_be_checkout_paths) - die(_("'%s' could be both a local file and a tracking branch.\n" - "Please use -- (and optionally --no-guess) to disambiguate"), - arg); *new_branch = arg; arg = remote; /* DWIMmed to create local branch, case (3).(b) */ @@ -1480,6 +1511,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts, OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")), OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree, N_("do not limit pathspecs to sparse entries only")), + OPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&opts->pathspec_file_nul), OPT_END() }; struct option *newopts = parse_options_concat(prevopts, options); @@ -1492,7 +1525,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix, const char * const usagestr[]) { struct branch_info new_branch_info; - int dwim_remotes_matched = 0; int parseopt_flags = 0; memset(&new_branch_info, 0, sizeof(new_branch_info)); @@ -1600,8 +1632,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, - &dwim_remotes_matched); + &new_branch_info, opts, &rev); argv += n; argc -= n; } else if (!opts->accept_ref && opts->from_treeish) { @@ -1618,10 +1649,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix, die(_("reference is not a tree: %s"), opts->from_treeish); } - if (opts->accept_pathspec && !opts->empty_pathspec_ok && !argc && - !opts->patch_mode) /* patch mode is special */ - die(_("you must specify path(s) to restore")); - if (argc) { parse_pathspec(&opts->pathspec, 0, opts->patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0, @@ -1641,10 +1668,33 @@ static int checkout_main(int argc, const char **argv, const char *prefix, if (opts->force_detach) die(_("git checkout: --detach does not take a path argument '%s'"), argv[0]); + } + if (opts->pathspec_from_file) { + if (opts->pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + if (opts->force_detach) + die(_("--pathspec-from-file is incompatible with --detach")); + + if (opts->patch_mode) + die(_("--pathspec-from-file is incompatible with --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")); + } + + if (opts->pathspec.nr) { if (1 < !!opts->writeout_stage + !!opts->force + !!opts->merge) die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\n" "checking out of the index.")); + } else { + if (opts->accept_pathspec && !opts->empty_pathspec_ok && + !opts->patch_mode) /* patch mode is special */ + die(_("you must specify path(s) to restore")); } if (opts->new_branch) { @@ -1659,28 +1709,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix, } UNLEAK(opts); - 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 { + if (opts->patch_mode || opts->pathspec.nr) + return checkout_paths(opts, new_branch_info.name); + else return checkout_branch(opts, &new_branch_info); - } } int cmd_checkout(int argc, const char **argv, const char *prefix) @@ -1714,6 +1746,15 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.checkout_index = -2; /* default on */ opts.checkout_worktree = -2; /* default on */ + if (argc == 3 && !strcmp(argv[1], "-b")) { + /* + * User ran 'git checkout -b <branch>' and expects + * the same behavior as 'git switch -c <branch>'. + */ + opts.switch_branch_doing_nothing_is_ok = 0; + opts.only_merge_on_switching_branches = 1; + } + options = parse_options_dup(checkout_options); options = add_common_options(&opts, options); options = add_common_switch_branch_options(&opts, options); |