switch: reject "do nothing" case
[gitweb.git] / builtin / checkout.c
index c15dc91e16745007b72992765c15795a0f923ccf..e9dfe38d69d46b11b271995a7d68cf18db4795a1 100644 (file)
@@ -34,6 +34,11 @@ static const char * const checkout_usage[] = {
        NULL,
 };
 
+static const char * const switch_branch_usage[] = {
+       N_("git switch [<options>] [<branch>]"),
+       NULL,
+};
+
 struct checkout_opts {
        int patch_mode;
        int quiet;
@@ -48,6 +53,9 @@ struct checkout_opts {
        int count_checkout_paths;
        int overlay_mode;
        int no_dwim_new_local_branch;
+       int discard_changes;
+       int accept_pathspec;
+       int switch_branch_doing_nothing_is_ok;
 
        /*
         * If new checkout options are added, skip_merge_working_tree
@@ -675,7 +683,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
                return error(_("index file corrupt"));
 
        resolve_undo_clear();
-       if (opts->force) {
+       if (opts->discard_changes) {
                ret = reset_tree(get_commit_tree(new_branch_info->commit),
                                 opts, 1, writeout_error);
                if (ret)
@@ -797,7 +805,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
        if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
                die(_("unable to write new index file"));
 
-       if (!opts->force && !opts->quiet)
+       if (!opts->discard_changes && !opts->quiet)
                show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
 
        return 0;
@@ -1170,10 +1178,16 @@ static int parse_branchname_arg(int argc, const char **argv,
        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;
                }
@@ -1207,11 +1221,12 @@ static int parse_branchname_arg(int argc, const char **argv,
                        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) {
@@ -1256,7 +1271,7 @@ static int parse_branchname_arg(int argc, const char **argv,
                 */
                if (argc)
                        verify_non_filename(opts->prefix, arg);
-       } else {
+       } else if (opts->accept_pathspec) {
                argcount++;
                argv++;
                argc--;
@@ -1304,6 +1319,9 @@ static int checkout_branch(struct checkout_opts *opts,
        if (opts->force && opts->merge)
                die(_("'%s' cannot be used with '%s'"), "-f", "-m");
 
+       if (opts->discard_changes && opts->merge)
+               die(_("'%s' cannot be used with '%s'"), "--discard-changes", "--merge");
+
        if (opts->force_detach && opts->new_branch)
                die(_("'%s' cannot be used with '%s'"),
                    "--detach", "-b/-B/--orphan");
@@ -1321,6 +1339,12 @@ static int checkout_branch(struct checkout_opts *opts,
                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 (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
            !opts->ignore_other_worktrees) {
                int flag;
@@ -1363,15 +1387,10 @@ static struct option *add_common_options(struct checkout_opts *opts,
        return newopts;
 }
 
-static struct option *add_switch_branch_options(struct checkout_opts *opts,
-                                               struct option *prevopts)
+static struct option *add_common_switch_branch_options(
+       struct checkout_opts *opts, struct option *prevopts)
 {
        struct option 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(0, "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),
@@ -1411,33 +1430,25 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,
        return newopts;
 }
 
-int cmd_checkout(int argc, const char **argv, const char *prefix)
+static int checkout_main(int argc, const char **argv, const char *prefix,
+                        struct checkout_opts *opts, struct option *options,
+                        const char * const usagestr[])
 {
-       struct checkout_opts real_opts;
-       struct checkout_opts *opts = &real_opts;
        struct branch_info new_branch_info;
        int dwim_remotes_matched = 0;
        int dwim_new_local_branch;
-       struct option *options = NULL;
 
-       memset(opts, 0, sizeof(*opts));
        memset(&new_branch_info, 0, sizeof(new_branch_info));
        opts->overwrite_ignore = 1;
        opts->prefix = prefix;
        opts->show_progress = -1;
        opts->overlay_mode = -1;
-       opts->no_dwim_new_local_branch = 0;
 
        git_config(git_checkout_config, opts);
 
        opts->track = BRANCH_TRACK_UNSPECIFIED;
 
-       options = parse_options_dup(options);
-       options = add_common_options(opts, options);
-       options = add_switch_branch_options(opts, options);
-       options = add_checkout_path_options(opts, options);
-
-       argc = parse_options(argc, argv, prefix, options, checkout_usage,
+       argc = parse_options(argc, argv, prefix, options, usagestr,
                             PARSE_OPT_KEEP_DASHDASH);
 
        dwim_new_local_branch = !opts->no_dwim_new_local_branch;
@@ -1452,6 +1463,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                opts->merge = 1; /* implied */
                git_xmerge_config("merge.conflictstyle", opts->conflict_style, NULL);
        }
+       if (opts->force)
+               opts->discard_changes = 1;
 
        if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
                die(_("-b, -B and --orphan are mutually exclusive"));
@@ -1570,3 +1583,63 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                return checkout_branch(opts, &new_branch_info);
        }
 }
+
+int cmd_checkout(int argc, const char **argv, const char *prefix)
+{
+       struct checkout_opts opts;
+       struct option *options;
+       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_END()
+       };
+       int ret;
+
+       memset(&opts, 0, sizeof(opts));
+       opts.no_dwim_new_local_branch = 0;
+       opts.switch_branch_doing_nothing_is_ok = 1;
+       opts.accept_pathspec = 1;
+
+       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);
+
+       ret = checkout_main(argc, argv, prefix, &opts,
+                           options, checkout_usage);
+       FREE_AND_NULL(options);
+       return ret;
+}
+
+int cmd_switch(int argc, const char **argv, const char *prefix)
+{
+       struct checkout_opts opts;
+       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, "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.accept_pathspec = 0;
+       opts.switch_branch_doing_nothing_is_ok = 0;
+
+       options = parse_options_dup(switch_options);
+       options = add_common_options(&opts, options);
+       options = add_common_switch_branch_options(&opts, options);
+
+       ret = checkout_main(argc, argv, prefix, &opts,
+                           options, switch_branch_usage);
+       FREE_AND_NULL(options);
+       return ret;
+}