Merge branch 'jc/checkout-refactor' into seen
The front-end code for 'git checkout', 'git switch', and 'git restore' has been restructured to cleanly separate their pathspec and branch handling, eliminating a common bottleneck and paving the way to libify utility helpers. * jc/checkout-refactor: checkout: move post_checkout_hook() to checkout.c checkout: wrap overly long lines checkout: restructure switch, restore, and checkout entrypoints checkout: extract branch setup and tracking helpers checkout: extract option validation and pathspec helpers checkout: validate stage and merge option compatibility in checkout_paths() checkout: validate new branch name in checkout_branch() checkout: pass cb_option explicitly to branch name parsersseen
commit
2934e3d3ef
|
|
@ -124,24 +124,6 @@ static void branch_info_release(struct branch_info *info)
|
||||||
free(info->checkout);
|
free(info->checkout);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
|
|
||||||
int changed)
|
|
||||||
{
|
|
||||||
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_FORCE_SERIAL;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* "new_commit" can be NULL when checking out from the index before
|
|
||||||
* a commit exists.
|
|
||||||
*/
|
|
||||||
strvec_pushl(&opt.args,
|
|
||||||
oid_to_hex(old_commit ? &old_commit->object.oid : null_oid(the_hash_algo)),
|
|
||||||
oid_to_hex(new_commit ? &new_commit->object.oid : null_oid(the_hash_algo)),
|
|
||||||
changed ? "1" : "0",
|
|
||||||
NULL);
|
|
||||||
|
|
||||||
return run_hooks_opt(the_repository, "post-checkout", &opt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Handle a tree object and determine if we need to recurse into the
|
* Handle a tree object and determine if we need to recurse into the
|
||||||
* tree (READ_TREE_RECURSIVE) or skip it (0).
|
* tree (READ_TREE_RECURSIVE) or skip it (0).
|
||||||
|
|
@ -591,6 +573,10 @@ static int checkout_paths(const struct checkout_opts *opts,
|
||||||
die(_("'%s', '%s', or '%s' cannot be used when checking out of a tree"),
|
die(_("'%s', '%s', or '%s' cannot be used when checking out of a tree"),
|
||||||
"--merge", "--ours", "--theirs");
|
"--merge", "--ours", "--theirs");
|
||||||
|
|
||||||
|
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."));
|
||||||
|
|
||||||
if (opts->patch_mode) {
|
if (opts->patch_mode) {
|
||||||
enum add_p_mode patch_mode;
|
enum add_p_mode patch_mode;
|
||||||
struct interactive_options interactive_opts = {
|
struct interactive_options interactive_opts = {
|
||||||
|
|
@ -714,7 +700,7 @@ static int checkout_paths(const struct checkout_opts *opts,
|
||||||
&rev, NULL);
|
&rev, NULL);
|
||||||
head = lookup_commit_reference_gently(the_repository, &rev, 1);
|
head = lookup_commit_reference_gently(the_repository, &rev, 1);
|
||||||
|
|
||||||
errs |= post_checkout_hook(head, head, 0);
|
errs |= post_checkout_hook(the_repository, head, head, 0);
|
||||||
return errs;
|
return errs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1272,7 +1258,8 @@ static int switch_branches(const struct checkout_opts *opts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);
|
ret = post_checkout_hook(the_repository,
|
||||||
|
old_branch_info.commit, new_branch_info->commit, 1);
|
||||||
branch_info_release(&old_branch_info);
|
branch_info_release(&old_branch_info);
|
||||||
strbuf_release(&old_commit_shortname);
|
strbuf_release(&old_commit_shortname);
|
||||||
strbuf_release(&autostash_msg);
|
strbuf_release(&autostash_msg);
|
||||||
|
|
@ -1340,29 +1327,23 @@ static void setup_new_branch_info_and_source_tree(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
enum checkout_command {
|
static void advise_disambiguating_remotes(char cb_option,
|
||||||
CHECKOUT_CHECKOUT = 1,
|
|
||||||
CHECKOUT_SWITCH = 2,
|
|
||||||
CHECKOUT_RESTORE = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void advise_disambiguating_remotes(enum checkout_command which_command,
|
|
||||||
const char *branch,
|
const char *branch,
|
||||||
const struct string_list *matched_remote_names)
|
const struct string_list *matched_remote_names)
|
||||||
{
|
{
|
||||||
const char *cmdname;
|
const char *cmdname;
|
||||||
struct string_list_item *item;
|
struct string_list_item *item;
|
||||||
|
|
||||||
switch (which_command) {
|
switch (cb_option) {
|
||||||
case CHECKOUT_CHECKOUT:
|
case 'b':
|
||||||
cmdname = "checkout";
|
cmdname = "checkout";
|
||||||
break;
|
break;
|
||||||
case CHECKOUT_SWITCH:
|
case 'c':
|
||||||
cmdname = "switch";
|
cmdname = "switch";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
BUG("command <%d> should not reach advise_disambiguating_remotes",
|
BUG("command <%c> should not reach advise_disambiguating_remotes",
|
||||||
which_command);
|
cb_option);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1384,7 +1365,7 @@ static void advise_disambiguating_remotes(enum checkout_command which_command,
|
||||||
static char *parse_remote_branch(const char *arg,
|
static char *parse_remote_branch(const char *arg,
|
||||||
struct object_id *rev,
|
struct object_id *rev,
|
||||||
int could_be_checkout_paths,
|
int could_be_checkout_paths,
|
||||||
enum checkout_command which_command)
|
char cb_option)
|
||||||
{
|
{
|
||||||
int num_matches = 0;
|
int num_matches = 0;
|
||||||
struct string_list matched_remote_names = STRING_LIST_INIT_DUP;
|
struct string_list matched_remote_names = STRING_LIST_INIT_DUP;
|
||||||
|
|
@ -1400,7 +1381,7 @@ static char *parse_remote_branch(const char *arg,
|
||||||
|
|
||||||
if (!remote && num_matches > 1) {
|
if (!remote && num_matches > 1) {
|
||||||
if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME))
|
if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME))
|
||||||
advise_disambiguating_remotes(which_command, arg,
|
advise_disambiguating_remotes(cb_option, arg,
|
||||||
&matched_remote_names);
|
&matched_remote_names);
|
||||||
die(_("'%s' matched multiple (%d) remote tracking branches"),
|
die(_("'%s' matched multiple (%d) remote tracking branches"),
|
||||||
arg, num_matches);
|
arg, num_matches);
|
||||||
|
|
@ -1413,7 +1394,7 @@ static char *parse_remote_branch(const char *arg,
|
||||||
|
|
||||||
static int parse_branchname_arg(int argc, const char **argv,
|
static int parse_branchname_arg(int argc, const char **argv,
|
||||||
int dwim_new_local_branch_ok,
|
int dwim_new_local_branch_ok,
|
||||||
enum checkout_command which_command,
|
char cb_option,
|
||||||
struct branch_info *new_branch_info,
|
struct branch_info *new_branch_info,
|
||||||
struct checkout_opts *opts,
|
struct checkout_opts *opts,
|
||||||
struct object_id *rev)
|
struct object_id *rev)
|
||||||
|
|
@ -1524,7 +1505,7 @@ static int parse_branchname_arg(int argc, const char **argv,
|
||||||
if (recover_with_dwim) {
|
if (recover_with_dwim) {
|
||||||
remote = parse_remote_branch(arg, rev,
|
remote = parse_remote_branch(arg, rev,
|
||||||
could_be_checkout_paths,
|
could_be_checkout_paths,
|
||||||
which_command);
|
cb_option);
|
||||||
if (remote) {
|
if (remote) {
|
||||||
*new_branch = arg;
|
*new_branch = arg;
|
||||||
arg = remote;
|
arg = remote;
|
||||||
|
|
@ -1754,6 +1735,17 @@ static int checkout_branch(struct checkout_opts *opts,
|
||||||
free(full_ref);
|
free(full_ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts->new_branch) {
|
||||||
|
struct strbuf buf = STRBUF_INIT;
|
||||||
|
|
||||||
|
if (opts->new_branch_force)
|
||||||
|
opts->branch_exists = validate_branchname(opts->new_branch, &buf);
|
||||||
|
else
|
||||||
|
opts->branch_exists =
|
||||||
|
validate_new_branchname(opts->new_branch, &buf, 0);
|
||||||
|
strbuf_release(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
if (!new_branch_info->commit && opts->new_branch) {
|
if (!new_branch_info->commit && opts->new_branch) {
|
||||||
struct object_id rev;
|
struct object_id rev;
|
||||||
int flag;
|
int flag;
|
||||||
|
|
@ -1851,49 +1843,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,
|
||||||
return newopts;
|
return newopts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* create-branch option (either b or c) */
|
static void init_checkout_opts(struct checkout_opts *opts, const char *prefix)
|
||||||
static char cb_option = 'b';
|
|
||||||
|
|
||||||
static int checkout_main(int argc, const char **argv, const char *prefix,
|
|
||||||
struct checkout_opts *opts, struct option *options,
|
|
||||||
enum checkout_command which_command)
|
|
||||||
{
|
{
|
||||||
int parseopt_flags = 0;
|
|
||||||
struct branch_info new_branch_info = { 0 };
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
static const char * const checkout_usage[] = {
|
|
||||||
N_("git checkout [<options>] <branch>"),
|
|
||||||
N_("git checkout [<options>] [<branch>] -- <file>..."),
|
|
||||||
NULL,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char * const switch_branch_usage[] = {
|
|
||||||
N_("git switch [<options>] [<branch>]"),
|
|
||||||
NULL,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char * const restore_usage[] = {
|
|
||||||
N_("git restore [<options>] [--source=<branch>] <file>..."),
|
|
||||||
NULL,
|
|
||||||
};
|
|
||||||
|
|
||||||
const char * const *usagestr;
|
|
||||||
|
|
||||||
switch (which_command) {
|
|
||||||
case CHECKOUT_CHECKOUT:
|
|
||||||
usagestr = checkout_usage;
|
|
||||||
break;
|
|
||||||
case CHECKOUT_SWITCH:
|
|
||||||
usagestr = switch_branch_usage;
|
|
||||||
break;
|
|
||||||
case CHECKOUT_RESTORE:
|
|
||||||
usagestr = restore_usage;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
BUG("no such checkout variant %d", which_command);
|
|
||||||
}
|
|
||||||
|
|
||||||
opts->overwrite_ignore = 1;
|
opts->overwrite_ignore = 1;
|
||||||
opts->prefix = prefix;
|
opts->prefix = prefix;
|
||||||
opts->show_progress = -1;
|
opts->show_progress = -1;
|
||||||
|
|
@ -1905,15 +1856,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
opts->track = BRANCH_TRACK_UNSPECIFIED;
|
opts->track = BRANCH_TRACK_UNSPECIFIED;
|
||||||
|
}
|
||||||
|
|
||||||
if (!opts->accept_pathspec && !opts->accept_ref)
|
static void validate_path_options(struct checkout_opts *opts)
|
||||||
BUG("make up your mind, you need to take _something_");
|
{
|
||||||
if (opts->accept_pathspec && opts->accept_ref)
|
|
||||||
parseopt_flags = PARSE_OPT_KEEP_DASHDASH;
|
|
||||||
|
|
||||||
argc = parse_options(argc, argv, prefix, options,
|
|
||||||
usagestr, parseopt_flags);
|
|
||||||
|
|
||||||
if (opts->patch_context < -1)
|
if (opts->patch_context < -1)
|
||||||
die(_("'%s' cannot be negative"), "--unified");
|
die(_("'%s' cannot be negative"), "--unified");
|
||||||
if (opts->patch_interhunk_context < -1)
|
if (opts->patch_interhunk_context < -1)
|
||||||
|
|
@ -1921,13 +1867,36 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
|
||||||
|
|
||||||
if (!opts->patch_mode) {
|
if (!opts->patch_mode) {
|
||||||
if (opts->patch_context != -1)
|
if (opts->patch_context != -1)
|
||||||
die(_("the option '%s' requires '%s'"), "--unified", "--patch");
|
die(_("the option '%s' requires '%s'"),
|
||||||
|
"--unified", "--patch");
|
||||||
if (opts->patch_interhunk_context != -1)
|
if (opts->patch_interhunk_context != -1)
|
||||||
die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch");
|
die(_("the option '%s' requires '%s'"),
|
||||||
|
"--inter-hunk-context", "--patch");
|
||||||
if (!opts->auto_advance)
|
if (!opts->auto_advance)
|
||||||
die(_("the option '%s' requires '%s'"), "--no-auto-advance", "--patch");
|
die(_("the option '%s' requires '%s'"),
|
||||||
|
"--no-auto-advance", "--patch");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
die_for_incompatible_opt2(opts->patch_mode, "-p",
|
||||||
|
opts->overlay_mode == 1, "--overlay");
|
||||||
|
|
||||||
|
if (opts->checkout_index >= 0 || opts->checkout_worktree >= 0) {
|
||||||
|
if (opts->checkout_index < 0)
|
||||||
|
opts->checkout_index = 0;
|
||||||
|
if (opts->checkout_worktree < 0)
|
||||||
|
opts->checkout_worktree = 0;
|
||||||
|
} else {
|
||||||
|
if (opts->checkout_index < 0)
|
||||||
|
opts->checkout_index = -opts->checkout_index - 1;
|
||||||
|
if (opts->checkout_worktree < 0)
|
||||||
|
opts->checkout_worktree = -opts->checkout_worktree - 1;
|
||||||
|
}
|
||||||
|
if (opts->checkout_index < 0 || opts->checkout_worktree < 0)
|
||||||
|
BUG("these flags should be non-negative by now");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prepare_common_options(struct checkout_opts *opts)
|
||||||
|
{
|
||||||
if (opts->show_progress < 0) {
|
if (opts->show_progress < 0) {
|
||||||
if (opts->quiet)
|
if (opts->quiet)
|
||||||
opts->show_progress = 0;
|
opts->show_progress = 0;
|
||||||
|
|
@ -1944,45 +1913,61 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
|
||||||
opts->ignore_unmerged_opt = "--force";
|
opts->ignore_unmerged_opt = "--force";
|
||||||
opts->ignore_unmerged = 1;
|
opts->ignore_unmerged = 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
|
static void parse_pathspec_from_file_options(struct checkout_opts *opts,
|
||||||
die(_("options '-%c', '-%c', and '%s' cannot be used together"),
|
const char *prefix)
|
||||||
cb_option, toupper(cb_option), "--orphan");
|
{
|
||||||
|
if (opts->pathspec_from_file) {
|
||||||
|
if (opts->pathspec.nr)
|
||||||
|
die(_("'%s' and pathspec arguments cannot be used together"),
|
||||||
|
"--pathspec-from-file");
|
||||||
|
|
||||||
if (opts->overlay_mode == 1 && opts->patch_mode)
|
die_for_incompatible_opt2(!!opts->pathspec_from_file,
|
||||||
die(_("options '%s' and '%s' cannot be used together"), "-p", "--overlay");
|
"--pathspec-from-file",
|
||||||
|
opts->force_detach,
|
||||||
|
"--detach");
|
||||||
|
|
||||||
if (opts->checkout_index >= 0 || opts->checkout_worktree >= 0) {
|
die_for_incompatible_opt2(!!opts->pathspec_from_file,
|
||||||
if (opts->checkout_index < 0)
|
"--pathspec-from-file",
|
||||||
opts->checkout_index = 0;
|
opts->patch_mode,
|
||||||
if (opts->checkout_worktree < 0)
|
"--patch");
|
||||||
opts->checkout_worktree = 0;
|
|
||||||
} else {
|
parse_pathspec_file(&opts->pathspec, 0, 0,
|
||||||
if (opts->checkout_index < 0)
|
prefix, opts->pathspec_from_file,
|
||||||
opts->checkout_index = -opts->checkout_index - 1;
|
opts->pathspec_file_nul);
|
||||||
if (opts->checkout_worktree < 0)
|
} else if (opts->pathspec_file_nul) {
|
||||||
opts->checkout_worktree = -opts->checkout_worktree - 1;
|
die(_("the option '%s' requires '%s'"),
|
||||||
|
"--pathspec-file-nul", "--pathspec-from-file");
|
||||||
}
|
}
|
||||||
if (opts->checkout_index < 0 || opts->checkout_worktree < 0)
|
|
||||||
BUG("these flags should be non-negative by now");
|
|
||||||
/*
|
|
||||||
* convenient shortcut: "git restore --staged [--worktree]" equals
|
|
||||||
* "git restore --staged [--worktree] --source HEAD"
|
|
||||||
*/
|
|
||||||
if (!opts->from_treeish && opts->checkout_index)
|
|
||||||
opts->from_treeish = "HEAD";
|
|
||||||
|
|
||||||
/*
|
opts->pathspec.recursive = 1;
|
||||||
* 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/-c/-C/--orphan is being used.
|
static void validate_branch_options(struct checkout_opts *opts, char cb_option)
|
||||||
*/
|
{
|
||||||
|
char new_branch_opt[] = "-c";
|
||||||
|
char new_branch_force_opt[] = "-C";
|
||||||
|
|
||||||
|
new_branch_opt[1] = cb_option;
|
||||||
|
new_branch_force_opt[1] = toupper(cb_option);
|
||||||
|
|
||||||
|
die_for_incompatible_opt3(!!opts->new_branch,
|
||||||
|
new_branch_opt,
|
||||||
|
!!opts->new_branch_force,
|
||||||
|
new_branch_force_opt,
|
||||||
|
!!opts->new_orphan_branch, "--orphan");
|
||||||
|
|
||||||
if (opts->new_branch_force)
|
if (opts->new_branch_force)
|
||||||
opts->new_branch = opts->new_branch_force;
|
opts->new_branch = opts->new_branch_force;
|
||||||
|
|
||||||
if (opts->new_orphan_branch)
|
if (opts->new_orphan_branch)
|
||||||
opts->new_branch = opts->new_orphan_branch;
|
opts->new_branch = opts->new_orphan_branch;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dwim_branch_track_option(int argc, const char **argv,
|
||||||
|
struct checkout_opts *opts, char cb_option)
|
||||||
|
{
|
||||||
/* --track without -c/-C/-b/-B/--orphan should DWIM */
|
/* --track without -c/-C/-b/-B/--orphan should DWIM */
|
||||||
if (opts->track != BRANCH_TRACK_UNSPECIFIED && !opts->new_branch) {
|
if (opts->track != BRANCH_TRACK_UNSPECIFIED && !opts->new_branch) {
|
||||||
const char *argv0 = argv[0];
|
const char *argv0 = argv[0];
|
||||||
|
|
@ -1995,114 +1980,174 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
|
||||||
die(_("missing branch name; try -%c"), cb_option);
|
die(_("missing branch name; try -%c"), cb_option);
|
||||||
opts->new_branch = argv0 + 1;
|
opts->new_branch = argv0 + 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
static int setup_branch_name_and_info(int argc, const char **argv,
|
||||||
* Extract branch name from command line arguments, so
|
struct checkout_opts *opts,
|
||||||
* all that is left is pathspecs.
|
struct branch_info *new_branch_info,
|
||||||
*
|
char cb_option)
|
||||||
* Handle
|
{
|
||||||
*
|
validate_branch_options(opts, cb_option);
|
||||||
* 1) git checkout <tree> -- [<paths>]
|
dwim_branch_track_option(argc, argv, opts, cb_option);
|
||||||
* 2) git checkout -- [<paths>]
|
|
||||||
* 3) git checkout <something> [<paths>]
|
if (argc) {
|
||||||
*
|
|
||||||
* including "last branch" syntax and DWIM-ery for names of
|
|
||||||
* remote branches, erroring out for invalid or ambiguous cases.
|
|
||||||
*/
|
|
||||||
if (argc && opts->accept_ref) {
|
|
||||||
struct object_id rev;
|
struct object_id rev;
|
||||||
int dwim_ok =
|
int dwim_ok =
|
||||||
!opts->patch_mode &&
|
!opts->patch_mode &&
|
||||||
opts->dwim_new_local_branch &&
|
opts->dwim_new_local_branch &&
|
||||||
opts->track == BRANCH_TRACK_UNSPECIFIED &&
|
opts->track == BRANCH_TRACK_UNSPECIFIED &&
|
||||||
!opts->new_branch;
|
!opts->new_branch;
|
||||||
int n = parse_branchname_arg(argc, argv, dwim_ok, which_command,
|
return parse_branchname_arg(argc, argv, dwim_ok, cb_option,
|
||||||
&new_branch_info, opts, &rev);
|
new_branch_info, opts, &rev);
|
||||||
argv += n;
|
}
|
||||||
argc -= n;
|
return 0;
|
||||||
} else if (!opts->accept_ref && opts->from_treeish) {
|
}
|
||||||
|
|
||||||
|
int cmd_switch(int argc,
|
||||||
|
const char **argv,
|
||||||
|
const char *prefix,
|
||||||
|
struct repository *repo UNUSED)
|
||||||
|
{
|
||||||
|
struct checkout_opts opts = CHECKOUT_OPTS_INIT;
|
||||||
|
struct option *options = NULL;
|
||||||
|
struct branch_info new_branch_info = { 0 };
|
||||||
|
int ret;
|
||||||
|
struct option switch_options[] = {
|
||||||
|
OPT_STRING('c', "create", &opts.new_branch, N_("branch"),
|
||||||
|
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()
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char * const switch_branch_usage[] = {
|
||||||
|
N_("git switch [<options>] [<branch>]"),
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
opts.dwim_new_local_branch = 1;
|
||||||
|
opts.accept_ref = 1;
|
||||||
|
opts.accept_pathspec = 0;
|
||||||
|
opts.switch_branch_doing_nothing_is_ok = 0;
|
||||||
|
opts.only_merge_on_switching_branches = 1;
|
||||||
|
opts.implicit_detach = 0;
|
||||||
|
opts.can_switch_when_in_progress = 0;
|
||||||
|
opts.orphan_from_empty_tree = 1;
|
||||||
|
opts.overlay_mode = -1;
|
||||||
|
|
||||||
|
init_checkout_opts(&opts, prefix);
|
||||||
|
|
||||||
|
options = parse_options_dup(switch_options);
|
||||||
|
options = add_common_options(&opts, options);
|
||||||
|
options = add_common_switch_branch_options(&opts, options);
|
||||||
|
|
||||||
|
argc = parse_options(argc, argv, prefix, options,
|
||||||
|
switch_branch_usage, 0);
|
||||||
|
|
||||||
|
prepare_common_options(&opts);
|
||||||
|
setup_branch_name_and_info(argc, argv, &opts, &new_branch_info, 'c');
|
||||||
|
|
||||||
|
ret = checkout_branch(&opts, &new_branch_info);
|
||||||
|
|
||||||
|
branch_info_release(&new_branch_info);
|
||||||
|
clear_pathspec(&opts.pathspec);
|
||||||
|
free(opts.pathspec_from_file);
|
||||||
|
free(options);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cmd_restore(int argc,
|
||||||
|
const char **argv,
|
||||||
|
const char *prefix,
|
||||||
|
struct repository *repo UNUSED)
|
||||||
|
{
|
||||||
|
struct checkout_opts opts = CHECKOUT_OPTS_INIT;
|
||||||
|
struct option *options;
|
||||||
|
struct branch_info new_branch_info = { 0 };
|
||||||
|
int ret;
|
||||||
|
struct option restore_options[] = {
|
||||||
|
OPT_STRING('s', "source", &opts.from_treeish, "<tree-ish>",
|
||||||
|
N_("which tree-ish to checkout from")),
|
||||||
|
OPT_BOOL('S', "staged", &opts.checkout_index,
|
||||||
|
N_("restore the index")),
|
||||||
|
OPT_BOOL('W', "worktree", &opts.checkout_worktree,
|
||||||
|
N_("restore the working tree (default)")),
|
||||||
|
OPT_BOOL(0, "ignore-unmerged", &opts.ignore_unmerged,
|
||||||
|
N_("ignore unmerged entries")),
|
||||||
|
OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode")),
|
||||||
|
OPT_END()
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char * const restore_usage[] = {
|
||||||
|
N_("git restore [<options>] [--source=<branch>] <file>..."),
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
opts.accept_ref = 0;
|
||||||
|
opts.accept_pathspec = 1;
|
||||||
|
opts.empty_pathspec_ok = 0;
|
||||||
|
opts.overlay_mode = 0;
|
||||||
|
opts.checkout_index = -1; /* default off */
|
||||||
|
opts.checkout_worktree = -2; /* default on */
|
||||||
|
opts.ignore_unmerged_opt = "--ignore-unmerged";
|
||||||
|
|
||||||
|
init_checkout_opts(&opts, prefix);
|
||||||
|
|
||||||
|
options = parse_options_dup(restore_options);
|
||||||
|
options = add_common_options(&opts, options);
|
||||||
|
options = add_checkout_path_options(&opts, options);
|
||||||
|
|
||||||
|
argc = parse_options(argc, argv, prefix, options,
|
||||||
|
restore_usage, 0);
|
||||||
|
|
||||||
|
validate_path_options(&opts);
|
||||||
|
prepare_common_options(&opts);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* convenient shortcut: "git restore --staged [--worktree]" equals
|
||||||
|
* "git restore --staged [--worktree] --source HEAD"
|
||||||
|
*/
|
||||||
|
if (!opts.from_treeish && opts.checkout_index)
|
||||||
|
opts.from_treeish = "HEAD";
|
||||||
|
|
||||||
|
if (opts.from_treeish) {
|
||||||
struct object_id rev;
|
struct object_id rev;
|
||||||
|
|
||||||
if (repo_get_oid_mb(the_repository, opts->from_treeish, &rev))
|
if (repo_get_oid_mb(the_repository, opts.from_treeish, &rev))
|
||||||
die(_("could not resolve '%s'"), opts->from_treeish);
|
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, &rev,
|
||||||
opts->from_treeish);
|
opts.from_treeish);
|
||||||
|
|
||||||
if (!opts->source_tree)
|
if (!opts.source_tree)
|
||||||
die(_("reference is not a tree: %s"), opts->from_treeish);
|
die(_("reference is not a tree: %s"), opts.from_treeish);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argc) {
|
if (argc) {
|
||||||
parse_pathspec(&opts->pathspec, 0,
|
parse_pathspec(&opts.pathspec, 0,
|
||||||
opts->patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,
|
opts.patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,
|
||||||
prefix, argv);
|
prefix, argv);
|
||||||
|
|
||||||
if (!opts->pathspec.nr)
|
if (!opts.pathspec.nr)
|
||||||
die(_("invalid path specification"));
|
die(_("invalid path specification"));
|
||||||
|
|
||||||
/*
|
|
||||||
* Try to give more helpful suggestion.
|
|
||||||
* new_branch && argc > 1 will be caught later.
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (opts->force_detach)
|
|
||||||
die(_("git checkout: --detach does not take a path argument '%s'"),
|
|
||||||
argv[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts->pathspec_from_file) {
|
parse_pathspec_from_file_options(&opts, prefix);
|
||||||
if (opts->pathspec.nr)
|
|
||||||
die(_("'%s' and pathspec arguments cannot be used together"), "--pathspec-from-file");
|
|
||||||
|
|
||||||
if (opts->force_detach)
|
if (!opts.pathspec.nr && !opts.patch_mode)
|
||||||
die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--detach");
|
die(_("you must specify path(s) to restore"));
|
||||||
|
|
||||||
if (opts->patch_mode)
|
ret = checkout_paths(&opts, &new_branch_info);
|
||||||
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(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file");
|
|
||||||
}
|
|
||||||
|
|
||||||
opts->pathspec.recursive = 1;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
struct strbuf buf = STRBUF_INIT;
|
|
||||||
|
|
||||||
if (opts->new_branch_force)
|
|
||||||
opts->branch_exists = validate_branchname(opts->new_branch, &buf);
|
|
||||||
else
|
|
||||||
opts->branch_exists =
|
|
||||||
validate_new_branchname(opts->new_branch, &buf, 0);
|
|
||||||
strbuf_release(&buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts->patch_mode || opts->pathspec.nr)
|
|
||||||
ret = checkout_paths(opts, &new_branch_info);
|
|
||||||
else
|
|
||||||
ret = checkout_branch(opts, &new_branch_info);
|
|
||||||
|
|
||||||
branch_info_release(&new_branch_info);
|
branch_info_release(&new_branch_info);
|
||||||
clear_pathspec(&opts->pathspec);
|
clear_pathspec(&opts.pathspec);
|
||||||
free(opts->pathspec_from_file);
|
free(opts.pathspec_from_file);
|
||||||
free(options);
|
free(options);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
@ -2115,20 +2160,30 @@ int cmd_checkout(int argc,
|
||||||
{
|
{
|
||||||
struct checkout_opts opts = CHECKOUT_OPTS_INIT;
|
struct checkout_opts opts = CHECKOUT_OPTS_INIT;
|
||||||
struct option *options;
|
struct option *options;
|
||||||
|
struct branch_info new_branch_info = { 0 };
|
||||||
|
int ret, n;
|
||||||
struct option checkout_options[] = {
|
struct option checkout_options[] = {
|
||||||
OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
|
OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
|
||||||
N_("create and checkout a new branch")),
|
N_("create and checkout a new branch")),
|
||||||
OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"),
|
OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"),
|
||||||
N_("create/reset and checkout a branch")),
|
N_("create/reset and checkout a branch")),
|
||||||
OPT_BOOL('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")),
|
OPT_BOOL('l', NULL, &opts.new_branch_log,
|
||||||
|
N_("create reflog for new branch")),
|
||||||
OPT_BOOL(0, "guess", &opts.dwim_new_local_branch,
|
OPT_BOOL(0, "guess", &opts.dwim_new_local_branch,
|
||||||
N_("second guess 'git checkout <no-such-branch>' (default)")),
|
N_("second guess 'git checkout <no-such-branch>' (default)")),
|
||||||
OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")),
|
OPT_BOOL(0, "overlay", &opts.overlay_mode,
|
||||||
|
N_("use overlay mode (default)")),
|
||||||
OPT_BOOL(0, "auto-advance", &opts.auto_advance,
|
OPT_BOOL(0, "auto-advance", &opts.auto_advance,
|
||||||
N_("auto advance to the next file when selecting hunks interactively")),
|
N_("auto advance to the next file when selecting hunks interactively")),
|
||||||
OPT_END()
|
OPT_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const char * const checkout_usage[] = {
|
||||||
|
N_("git checkout [<options>] <branch>"),
|
||||||
|
N_("git checkout [<options>] [<branch>] -- <file>..."),
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
opts.dwim_new_local_branch = 1;
|
opts.dwim_new_local_branch = 1;
|
||||||
opts.switch_branch_doing_nothing_is_ok = 1;
|
opts.switch_branch_doing_nothing_is_ok = 1;
|
||||||
opts.only_merge_on_switching_branches = 0;
|
opts.only_merge_on_switching_branches = 0;
|
||||||
|
|
@ -2151,86 +2206,55 @@ int cmd_checkout(int argc,
|
||||||
opts.only_merge_on_switching_branches = 1;
|
opts.only_merge_on_switching_branches = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init_checkout_opts(&opts, prefix);
|
||||||
|
|
||||||
options = parse_options_dup(checkout_options);
|
options = parse_options_dup(checkout_options);
|
||||||
options = add_common_options(&opts, options);
|
options = add_common_options(&opts, options);
|
||||||
options = add_common_switch_branch_options(&opts, options);
|
options = add_common_switch_branch_options(&opts, options);
|
||||||
options = add_checkout_path_options(&opts, options);
|
options = add_checkout_path_options(&opts, options);
|
||||||
|
|
||||||
return checkout_main(argc, argv, prefix, &opts, options,
|
argc = parse_options(argc, argv, prefix, options,
|
||||||
CHECKOUT_CHECKOUT);
|
checkout_usage, PARSE_OPT_KEEP_DASHDASH);
|
||||||
}
|
|
||||||
|
validate_path_options(&opts);
|
||||||
int cmd_switch(int argc,
|
prepare_common_options(&opts);
|
||||||
const char **argv,
|
|
||||||
const char *prefix,
|
n = setup_branch_name_and_info(argc, argv, &opts, &new_branch_info, 'b');
|
||||||
struct repository *repo UNUSED)
|
argv += n;
|
||||||
{
|
argc -= n;
|
||||||
struct checkout_opts opts = CHECKOUT_OPTS_INIT;
|
|
||||||
struct option *options = NULL;
|
if (argc) {
|
||||||
struct option switch_options[] = {
|
parse_pathspec(&opts.pathspec, 0,
|
||||||
OPT_STRING('c', "create", &opts.new_branch, N_("branch"),
|
opts.patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,
|
||||||
N_("create and switch to a new branch")),
|
prefix, argv);
|
||||||
OPT_STRING('C', "force-create", &opts.new_branch_force, N_("branch"),
|
|
||||||
N_("create/reset and switch to a branch")),
|
if (!opts.pathspec.nr)
|
||||||
OPT_BOOL(0, "guess", &opts.dwim_new_local_branch,
|
die(_("invalid path specification"));
|
||||||
N_("second guess 'git switch <no-such-branch>'")),
|
|
||||||
OPT_BOOL(0, "discard-changes", &opts.discard_changes,
|
/*
|
||||||
N_("throw away local modifications")),
|
* Try to give more helpful suggestion.
|
||||||
OPT_END()
|
* new_branch && argc > 1 will be caught later.
|
||||||
};
|
*/
|
||||||
|
if (opts.new_branch && argc == 1 && !new_branch_info.commit)
|
||||||
opts.dwim_new_local_branch = 1;
|
die(_("'%s' is not a commit and a branch '%s' cannot be created from it"),
|
||||||
opts.accept_ref = 1;
|
argv[0], opts.new_branch);
|
||||||
opts.accept_pathspec = 0;
|
|
||||||
opts.switch_branch_doing_nothing_is_ok = 0;
|
if (opts.force_detach)
|
||||||
opts.only_merge_on_switching_branches = 1;
|
die(_("git checkout: --detach does not take a path argument '%s'"),
|
||||||
opts.implicit_detach = 0;
|
argv[0]);
|
||||||
opts.can_switch_when_in_progress = 0;
|
}
|
||||||
opts.orphan_from_empty_tree = 1;
|
|
||||||
opts.overlay_mode = -1;
|
parse_pathspec_from_file_options(&opts, prefix);
|
||||||
|
|
||||||
options = parse_options_dup(switch_options);
|
if (opts.patch_mode || opts.pathspec.nr)
|
||||||
options = add_common_options(&opts, options);
|
ret = checkout_paths(&opts, &new_branch_info);
|
||||||
options = add_common_switch_branch_options(&opts, options);
|
else
|
||||||
|
ret = checkout_branch(&opts, &new_branch_info);
|
||||||
cb_option = 'c';
|
|
||||||
|
branch_info_release(&new_branch_info);
|
||||||
return checkout_main(argc, argv, prefix, &opts, options,
|
clear_pathspec(&opts.pathspec);
|
||||||
CHECKOUT_SWITCH);
|
free(opts.pathspec_from_file);
|
||||||
}
|
free(options);
|
||||||
|
|
||||||
int cmd_restore(int argc,
|
return ret;
|
||||||
const char **argv,
|
|
||||||
const char *prefix,
|
|
||||||
struct repository *repo UNUSED)
|
|
||||||
{
|
|
||||||
struct checkout_opts opts = CHECKOUT_OPTS_INIT;
|
|
||||||
struct option *options;
|
|
||||||
struct option restore_options[] = {
|
|
||||||
OPT_STRING('s', "source", &opts.from_treeish, "<tree-ish>",
|
|
||||||
N_("which tree-ish to checkout from")),
|
|
||||||
OPT_BOOL('S', "staged", &opts.checkout_index,
|
|
||||||
N_("restore the index")),
|
|
||||||
OPT_BOOL('W', "worktree", &opts.checkout_worktree,
|
|
||||||
N_("restore the working tree (default)")),
|
|
||||||
OPT_BOOL(0, "ignore-unmerged", &opts.ignore_unmerged,
|
|
||||||
N_("ignore unmerged entries")),
|
|
||||||
OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode")),
|
|
||||||
OPT_END()
|
|
||||||
};
|
|
||||||
|
|
||||||
opts.accept_ref = 0;
|
|
||||||
opts.accept_pathspec = 1;
|
|
||||||
opts.empty_pathspec_ok = 0;
|
|
||||||
opts.overlay_mode = 0;
|
|
||||||
opts.checkout_index = -1; /* default off */
|
|
||||||
opts.checkout_worktree = -2; /* default on */
|
|
||||||
opts.ignore_unmerged_opt = "--ignore-unmerged";
|
|
||||||
|
|
||||||
options = parse_options_dup(restore_options);
|
|
||||||
options = add_common_options(&opts, options);
|
|
||||||
options = add_checkout_path_options(&opts, options);
|
|
||||||
|
|
||||||
return checkout_main(argc, argv, prefix, &opts, options,
|
|
||||||
CHECKOUT_RESTORE);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28
checkout.c
28
checkout.c
|
|
@ -1,6 +1,9 @@
|
||||||
#define USE_THE_REPOSITORY_VARIABLE
|
#define USE_THE_REPOSITORY_VARIABLE
|
||||||
|
|
||||||
#include "git-compat-util.h"
|
#include "git-compat-util.h"
|
||||||
|
#include "commit.h"
|
||||||
|
#include "hex.h"
|
||||||
|
#include "hook.h"
|
||||||
#include "object-name.h"
|
#include "object-name.h"
|
||||||
#include "remote.h"
|
#include "remote.h"
|
||||||
#include "refspec.h"
|
#include "refspec.h"
|
||||||
|
|
@ -9,6 +12,7 @@
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "strbuf.h"
|
#include "strbuf.h"
|
||||||
#include "string-list.h"
|
#include "string-list.h"
|
||||||
|
#include "strvec.h"
|
||||||
|
|
||||||
struct tracking_name_data {
|
struct tracking_name_data {
|
||||||
/* const */ char *src_ref;
|
/* const */ char *src_ref;
|
||||||
|
|
@ -82,3 +86,27 @@ char *unique_tracking_name(const char *name, struct object_id *oid,
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int post_checkout_hook(struct repository *repo,
|
||||||
|
struct commit *old_commit, struct commit *new_commit,
|
||||||
|
int changed)
|
||||||
|
{
|
||||||
|
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_FORCE_SERIAL;
|
||||||
|
const struct git_hash_algo *hash_algo = repo->hash_algo;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "new_commit" can be NULL when checking out from the index before
|
||||||
|
* a commit exists.
|
||||||
|
*/
|
||||||
|
strvec_pushl(&opt.args,
|
||||||
|
oid_to_hex(old_commit
|
||||||
|
? &old_commit->object.oid
|
||||||
|
: null_oid(hash_algo)),
|
||||||
|
oid_to_hex(new_commit ?
|
||||||
|
&new_commit->object.oid
|
||||||
|
: null_oid(hash_algo)),
|
||||||
|
changed ? "1" : "0",
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
return run_hooks_opt(repo, "post-checkout", &opt);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
|
|
||||||
#include "hash.h"
|
#include "hash.h"
|
||||||
|
|
||||||
|
struct commit;
|
||||||
struct string_list;
|
struct string_list;
|
||||||
|
struct repository;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check if the branch name uniquely matches a branch name on a remote
|
* Check if the branch name uniquely matches a branch name on a remote
|
||||||
|
|
@ -15,4 +17,11 @@ char *unique_tracking_name(const char *name,
|
||||||
int *dwim_remotes_matched,
|
int *dwim_remotes_matched,
|
||||||
struct string_list *dwim_remote_names);
|
struct string_list *dwim_remote_names);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Run the post-checkout hook.
|
||||||
|
*/
|
||||||
|
int post_checkout_hook(struct repository *,
|
||||||
|
struct commit *old_commit, struct commit *new_commit,
|
||||||
|
int changed);
|
||||||
|
|
||||||
#endif /* CHECKOUT_H */
|
#endif /* CHECKOUT_H */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue