git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v2 6/8] checkout: restructure switch, restore, and checkout entrypoints

From
Junio C Hamano <gitster@pobox.com>
Date
Aug 30, 2026, 20:48 UTC
Message-ID
<20260830204835.1040408-7-gitster@pobox.com>
In-Reply-To
<20260830204835.1040408-1-gitster@pobox.com>

cmd_switch(), cmd_restore(), and cmd_checkout() pass their options to checkout_main(), which parses options and configuration, validates and dispatches to checkout_branch() or checkout_paths().

Now that option initialization, validation, and branch setup have been split into dedicated helper functions, restructure cmd_switch(), cmd_restore(), and cmd_checkout() to invoke these helpers directly and dispatch to checkout_branch() or checkout_paths().

In cmd_restore(), handle the --staged default from_treeish = "HEAD" and resolve opts.from_treeish into new_branch_info and opts.source_tree.

This allows us to remove checkout_main() and enum checkout_command as they are no longer needed.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/checkout.c | 297 +++++++++++++++++++++++----------------------
 1 file changed, 149 insertions(+), 148 deletions(-)
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2edaca5539..b18515ac7f 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1341,12 +1341,6 @@ static void setup_new_branch_info_and_source_tree(
 }
 
 
-enum checkout_command {
-	CHECKOUT_CHECKOUT = 1,
-	CHECKOUT_SWITCH = 2,
-	CHECKOUT_RESTORE = 3,
-};
-
 static char *parse_remote_branch(const char *arg,
 				 struct object_id *rev,
 				 int could_be_checkout_paths,
@@ -1989,19 +1983,25 @@ static int setup_branch_name_and_info(int argc, const char **argv,
 	return 0;
 }
 
-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 cmd_switch(int argc,
+	       const char **argv,
+	       const char *prefix,
+	       struct repository *repo UNUSED)
 {
-	int parseopt_flags = 0;
+	struct checkout_opts opts = CHECKOUT_OPTS_INIT;
+	struct option *options = NULL;
 	struct branch_info new_branch_info = { 0 };
 	int ret;
-	char cb_option = (which_command == CHECKOUT_SWITCH) ? 'c' : 'b';
-
-	static const char * const checkout_usage[] = {
-		N_("git checkout [<options>] <branch>"),
-		N_("git checkout [<options>] [<branch>] -- <file>..."),
-		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 <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[] = {
@@ -2009,103 +2009,125 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 		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,
 	};
 
-	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.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);
+	init_checkout_opts(&opts, prefix);
 
-	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;
+	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,
-			     usagestr, parseopt_flags);
+			     restore_usage, 0);
 
-	validate_path_options(opts);
-	prepare_common_options(opts);
+	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->accept_ref) {
-		int n = setup_branch_name_and_info(argc, argv, opts,
-						   &new_branch_info, cb_option);
-		argv += n;
-		argc -= n;
-	} else if (!opts->accept_ref && opts->from_treeish) {
+	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]);
 	}
 
-	parse_pathspec_from_file_options(opts, prefix);
+	parse_pathspec_from_file_options(&opts, prefix);
 
-	if (!opts->pathspec.nr) {
-		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.pathspec.nr && !opts.patch_mode)
+		die(_("you must specify path(s) to restore"));
 
-	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;
@@ -2118,6 +2140,8 @@ 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")),
@@ -2132,6 +2156,12 @@ int cmd_checkout(int argc,
 		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.switch_branch_doing_nothing_is_ok = 1;
 	opts.only_merge_on_switching_branches = 0;
@@ -2154,84 +2184,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);
-}
+	argc = parse_options(argc, argv, prefix, options,
+			     checkout_usage, PARSE_OPT_KEEP_DASHDASH);
 
-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 <no-such-branch>'")),
-		OPT_BOOL(0, "discard-changes", &opts.discard_changes,
-			 N_("throw away local modifications")),
-		OPT_END()
-	};
+	validate_path_options(&opts);
+	prepare_common_options(&opts);
 
-	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;
+	n = setup_branch_name_and_info(argc, argv, &opts, &new_branch_info, 'b');
+	argv += n;
+	argc -= n;
 
-	options = parse_options_dup(switch_options);
-	options = add_common_options(&opts, options);
-	options = add_common_switch_branch_options(&opts, options);
+	if (argc) {
+		parse_pathspec(&opts.pathspec, 0,
+			       opts.patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,
+			       prefix, argv);
 
-	return checkout_main(argc, argv, prefix, &opts, options,
-			     CHECKOUT_SWITCH);
-}
+		if (!opts.pathspec.nr)
+			die(_("invalid path specification"));
 
-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, "<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()
-	};
+		/*
+		 * 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);
 
-	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";
+		if (opts.force_detach)
+			die(_("git checkout: --detach does not take a path argument '%s'"),
+			    argv[0]);
+	}
 
-	options = parse_options_dup(restore_options);
-	options = add_common_options(&opts, options);
-	options = add_checkout_path_options(&opts, options);
+	parse_pathspec_from_file_options(&opts, prefix);
 
-	return checkout_main(argc, argv, prefix, &opts, options,
-			     CHECKOUT_RESTORE);
+	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;
 }
-- 
2.55.0-884-g76cf8659c2
Previous: Junio C HamanoNext: Karthik Nayak
Message 36 of 47 in “builtin: replace the_repository parameter in is_bare_repository()”
  1. builtin: replace the_repository parameter in is_bare_repository()Hardik Kumar, Aug 27, 2026
  2. Junio C HamanoAug 27, 2026
  3. Junio C HamanoAug 27, 2026
  4. Hardik KumarAug 27, 2026
  5. Junio C HamanoAug 27, 2026
  6. Ben KnobleAug 27, 2026
  7. Junio C HamanoAug 27, 2026
  8. D. Ben KnobleAug 28, 2026
  9. Junio C HamanoAug 28, 2026
  10. 0/8 More sensible checkout/switch/restore code refactoringJunio C Hamano, Aug 28, 2026
  11. 1/8 checkout: pass cb_option explicitly to branch name parsersJunio C Hamano, Aug 28, 2026
  12. 2/8 checkout: validate new branch name in checkout_branch()Junio C Hamano, Aug 28, 2026
  13. 3/8 checkout: validate stage and merge option compatibility in checkout_paths()Junio C Hamano, Aug 28, 2026
  14. 4/8 checkout: extract option validation and pathspec helpersJunio C Hamano, Aug 28, 2026
  15. 5/8 checkout: extract branch setup and tracking helpersJunio C Hamano, Aug 28, 2026
  16. 6/8 checkout: restructure switch, restore, and checkout entrypointsJunio C Hamano, Aug 28, 2026
  17. 7/8 checkout: wrap overly long linesJunio C Hamano, Aug 28, 2026
  18. Junio C HamanoAug 28, 2026
  19. Junio C HamanoAug 29, 2026
  20. 8/8 checkout: move post_checkout_hook() to checkout.cJunio C Hamano, Aug 28, 2026
  21. Junio C HamanoAug 28, 2026
  22. Junio C HamanoAug 29, 2026
  23. 0/8 More sensible checkout/switch/restore code refactoringJunio C Hamano, Aug 30, 2026
  24. 1/8 checkout: pass cb_option explicitly to branch name parsersJunio C Hamano, Aug 30, 2026
  25. Karthik NayakSep 1, 2026
  26. 2/8 checkout: validate new branch name in checkout_branch()Junio C Hamano, Aug 30, 2026
  27. Karthik NayakSep 1, 2026
  28. 3/8 checkout: validate stage and merge option compatibility in checkout_paths()Junio C Hamano, Aug 30, 2026
  29. Karthik NayakSep 1, 2026
  30. Junio C HamanoSep 1, 2026
  31. Karthik NayakSep 2, 2026
  32. Junio C HamanoSep 2, 2026
  33. Karthik NayakSep 3, 2026
  34. 4/8 checkout: extract option validation and pathspec helpersJunio C Hamano, Aug 30, 2026
  35. 5/8 checkout: extract branch setup and tracking helpersJunio C Hamano, Aug 30, 2026
  36. 6/8 checkout: restructure switch, restore, and checkout entrypointsJunio C Hamano, Aug 30, 2026
  37. Karthik NayakSep 1, 2026
  38. Junio C HamanoSep 1, 2026
  39. Karthik NayakSep 2, 2026
  40. 7/8 checkout: wrap overly long linesJunio C Hamano, Aug 30, 2026
  41. 8/8 checkout: move post_checkout_hook() to checkout.cJunio C Hamano, Aug 30, 2026
  42. D. Ben KnobleAug 29, 2026
  43. do not pass "repo" to builtin commmand implementationsJunio C Hamano, Aug 27, 2026
  44. Hardik KumarAug 28, 2026
  45. Junio C HamanoAug 28, 2026
  46. Hardik KumarAug 28, 2026
  47. Hardik KumarAug 27, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.