diff --git a/builtin/checkout.c b/builtin/checkout.c index 27f173eb46..9dfe17c54e 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -124,24 +124,6 @@ static void branch_info_release(struct branch_info *info) 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 * 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"), "--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) { enum add_p_mode patch_mode; struct interactive_options interactive_opts = { @@ -714,7 +700,7 @@ static int checkout_paths(const struct checkout_opts *opts, &rev, NULL); 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; } @@ -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); strbuf_release(&old_commit_shortname); strbuf_release(&autostash_msg); @@ -1340,29 +1327,23 @@ static void setup_new_branch_info_and_source_tree( } -enum checkout_command { - CHECKOUT_CHECKOUT = 1, - CHECKOUT_SWITCH = 2, - CHECKOUT_RESTORE = 3, -}; - -static void advise_disambiguating_remotes(enum checkout_command which_command, +static void advise_disambiguating_remotes(char cb_option, const char *branch, const struct string_list *matched_remote_names) { const char *cmdname; struct string_list_item *item; - switch (which_command) { - case CHECKOUT_CHECKOUT: + switch (cb_option) { + case 'b': cmdname = "checkout"; break; - case CHECKOUT_SWITCH: + case 'c': cmdname = "switch"; break; default: - BUG("command <%d> should not reach advise_disambiguating_remotes", - which_command); + BUG("command <%c> should not reach advise_disambiguating_remotes", + cb_option); break; } @@ -1384,7 +1365,7 @@ static void advise_disambiguating_remotes(enum checkout_command which_command, static char *parse_remote_branch(const char *arg, struct object_id *rev, int could_be_checkout_paths, - enum checkout_command which_command) + char cb_option) { int num_matches = 0; 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 (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) - advise_disambiguating_remotes(which_command, arg, + advise_disambiguating_remotes(cb_option, arg, &matched_remote_names); die(_("'%s' matched multiple (%d) remote tracking branches"), 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, int dwim_new_local_branch_ok, - enum checkout_command which_command, + char cb_option, struct branch_info *new_branch_info, struct checkout_opts *opts, struct object_id *rev) @@ -1524,7 +1505,7 @@ static int parse_branchname_arg(int argc, const char **argv, if (recover_with_dwim) { remote = parse_remote_branch(arg, rev, could_be_checkout_paths, - which_command); + cb_option); if (remote) { *new_branch = arg; arg = remote; @@ -1754,6 +1735,17 @@ static int checkout_branch(struct checkout_opts *opts, 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) { struct object_id rev; int flag; @@ -1851,49 +1843,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts, return newopts; } -/* create-branch option (either b or c) */ -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) +static void init_checkout_opts(struct checkout_opts *opts, const char *prefix) { - int parseopt_flags = 0; - struct branch_info new_branch_info = { 0 }; - int ret; - - static const char * const checkout_usage[] = { - N_("git checkout [] "), - N_("git checkout [] [] -- ..."), - NULL, - }; - - static const char * const switch_branch_usage[] = { - N_("git switch [] []"), - NULL, - }; - - static const char * const restore_usage[] = { - N_("git restore [] [--source=] ..."), - 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->prefix = prefix; 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; +} - if (!opts->accept_pathspec && !opts->accept_ref) - 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); - +static void validate_path_options(struct checkout_opts *opts) +{ if (opts->patch_context < -1) die(_("'%s' cannot be negative"), "--unified"); 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_context != -1) - die(_("the option '%s' requires '%s'"), "--unified", "--patch"); + die(_("the option '%s' requires '%s'"), + "--unified", "--patch"); 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) - 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->quiet) 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 = 1; } +} - if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1) - die(_("options '-%c', '-%c', and '%s' cannot be used together"), - cb_option, toupper(cb_option), "--orphan"); +static void parse_pathspec_from_file_options(struct checkout_opts *opts, + const char *prefix) +{ + 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(_("options '%s' and '%s' cannot be used together"), "-p", "--overlay"); + die_for_incompatible_opt2(!!opts->pathspec_from_file, + "--pathspec-from-file", + opts->force_detach, + "--detach"); - 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; + die_for_incompatible_opt2(!!opts->pathspec_from_file, + "--pathspec-from-file", + opts->patch_mode, + "--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"); } - 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"; - /* - * 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. - */ + opts->pathspec.recursive = 1; +} + +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) opts->new_branch = opts->new_branch_force; if (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 */ if (opts->track != BRANCH_TRACK_UNSPECIFIED && !opts->new_branch) { 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); opts->new_branch = argv0 + 1; } +} - /* - * Extract branch name from command line arguments, so - * all that is left is pathspecs. - * - * Handle - * - * 1) git checkout -- [] - * 2) git checkout -- [] - * 3) git checkout [] - * - * including "last branch" syntax and DWIM-ery for names of - * remote branches, erroring out for invalid or ambiguous cases. - */ - if (argc && opts->accept_ref) { +static int setup_branch_name_and_info(int argc, const char **argv, + struct checkout_opts *opts, + struct branch_info *new_branch_info, + char cb_option) +{ + validate_branch_options(opts, cb_option); + dwim_branch_track_option(argc, argv, opts, cb_option); + + if (argc) { struct object_id rev; int dwim_ok = !opts->patch_mode && opts->dwim_new_local_branch && opts->track == BRANCH_TRACK_UNSPECIFIED && !opts->new_branch; - int n = parse_branchname_arg(argc, argv, dwim_ok, which_command, - &new_branch_info, opts, &rev); - argv += n; - argc -= n; - } else if (!opts->accept_ref && opts->from_treeish) { + return parse_branchname_arg(argc, argv, dwim_ok, cb_option, + new_branch_info, opts, &rev); + } + return 0; +} + +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 '")), + 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 [] []"), + 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, "", + 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 [] [--source=] ..."), + 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; - if (repo_get_oid_mb(the_repository, opts->from_treeish, &rev)) - die(_("could not resolve '%s'"), opts->from_treeish); + if (repo_get_oid_mb(the_repository, opts.from_treeish, &rev)) + die(_("could not resolve '%s'"), opts.from_treeish); setup_new_branch_info_and_source_tree(&new_branch_info, - opts, &rev, - opts->from_treeish); + &opts, &rev, + opts.from_treeish); - if (!opts->source_tree) - die(_("reference is not a tree: %s"), opts->from_treeish); + if (!opts.source_tree) + die(_("reference is not a tree: %s"), opts.from_treeish); } if (argc) { - parse_pathspec(&opts->pathspec, 0, - opts->patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0, + parse_pathspec(&opts.pathspec, 0, + opts.patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0, prefix, argv); - if (!opts->pathspec.nr) + if (!opts.pathspec.nr) 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) { - if (opts->pathspec.nr) - die(_("'%s' and pathspec arguments cannot be used together"), "--pathspec-from-file"); + parse_pathspec_from_file_options(&opts, prefix); - if (opts->force_detach) - die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--detach"); + if (!opts.pathspec.nr && !opts.patch_mode) + die(_("you must specify path(s) to restore")); - if (opts->patch_mode) - 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); + ret = checkout_paths(&opts, &new_branch_info); branch_info_release(&new_branch_info); - clear_pathspec(&opts->pathspec); - free(opts->pathspec_from_file); + clear_pathspec(&opts.pathspec); + free(opts.pathspec_from_file); free(options); return ret; @@ -2115,20 +2160,30 @@ int cmd_checkout(int argc, { struct checkout_opts opts = CHECKOUT_OPTS_INIT; struct option *options; + struct branch_info new_branch_info = { 0 }; + int ret, n; struct option checkout_options[] = { OPT_STRING('b', NULL, &opts.new_branch, N_("branch"), N_("create and checkout a new branch")), 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('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 ' (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, N_("auto advance to the next file when selecting hunks interactively")), OPT_END() }; + static const char * const checkout_usage[] = { + N_("git checkout [] "), + N_("git checkout [] [] -- ..."), + NULL, + }; + opts.dwim_new_local_branch = 1; opts.switch_branch_doing_nothing_is_ok = 1; opts.only_merge_on_switching_branches = 0; @@ -2151,86 +2206,55 @@ int cmd_checkout(int argc, opts.only_merge_on_switching_branches = 1; } + init_checkout_opts(&opts, prefix); + options = parse_options_dup(checkout_options); options = add_common_options(&opts, options); options = add_common_switch_branch_options(&opts, options); options = add_checkout_path_options(&opts, options); - return checkout_main(argc, argv, prefix, &opts, options, - CHECKOUT_CHECKOUT); -} - -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 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 '")), - OPT_BOOL(0, "discard-changes", &opts.discard_changes, - N_("throw away local modifications")), - OPT_END() - }; - - 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; - - options = parse_options_dup(switch_options); - options = add_common_options(&opts, options); - options = add_common_switch_branch_options(&opts, options); - - cb_option = 'c'; - - return checkout_main(argc, argv, prefix, &opts, options, - CHECKOUT_SWITCH); -} - -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 option restore_options[] = { - OPT_STRING('s', "source", &opts.from_treeish, "", - 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); + argc = parse_options(argc, argv, prefix, options, + checkout_usage, PARSE_OPT_KEEP_DASHDASH); + + validate_path_options(&opts); + prepare_common_options(&opts); + + n = setup_branch_name_and_info(argc, argv, &opts, &new_branch_info, 'b'); + argv += n; + argc -= n; + + if (argc) { + parse_pathspec(&opts.pathspec, 0, + opts.patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0, + prefix, argv); + + if (!opts.pathspec.nr) + 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]); + } + + parse_pathspec_from_file_options(&opts, prefix); + + 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); + clear_pathspec(&opts.pathspec); + free(opts.pathspec_from_file); + free(options); + + return ret; } diff --git a/checkout.c b/checkout.c index a0d0229435..cf86266939 100644 --- a/checkout.c +++ b/checkout.c @@ -1,6 +1,9 @@ #define USE_THE_REPOSITORY_VARIABLE #include "git-compat-util.h" +#include "commit.h" +#include "hex.h" +#include "hook.h" #include "object-name.h" #include "remote.h" #include "refspec.h" @@ -9,6 +12,7 @@ #include "config.h" #include "strbuf.h" #include "string-list.h" +#include "strvec.h" struct tracking_name_data { /* const */ char *src_ref; @@ -82,3 +86,27 @@ char *unique_tracking_name(const char *name, struct object_id *oid, } 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); +} diff --git a/checkout.h b/checkout.h index 0b185a0fc9..9f034afb5b 100644 --- a/checkout.h +++ b/checkout.h @@ -3,7 +3,9 @@ #include "hash.h" +struct commit; struct string_list; +struct repository; /* * 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, 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 */