threads / patch / 50360

patch, 19 partsAdd new command "switch"

Subject: [PATCH 00/19] Add new command "switch"

## tl;dr

289 messages between Jan 30, 2019 and Jul 4, 2019. Diffs are folded; open one to read it.

replies: 288people: 11as markdown or json

Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · lore

This is the first half of the previous "switch-branch and restore-files" RFC series [1]. I only focus on switch-branch for now because it's already getting long and I still have some work to do on restore-files.

The command is, as the name implies, for switching branches. And it's supposed to be a (hopefully better) alternative for "git checkout". Head to 08/19 for the man page, which describes the command (with examples!) better than I do here.

I think I've addressed most of the issues from the last round. The only one left is adding options similar to "git reset" (but with better names this time than --soft and --hard, hopefully). I think that could be done in a follow-up series.

For a complete picture, you would need to consider "git restore" too (the new name of "git restore-files"). Full series is available [2] though I still need to work on the "restore from a tree" part of that command and thinking whether "restore -p" vs "reset -p" could be confusing...

This series is built on top of master + tg/checkout-no-overlay.

PS. One of the things I like about this, after using it for some time, is the "no ambiguation" UI design gives better completion support (much less useless completion candidates), which makes life much better.

[1] https://public-inbox.org/git/20181129215850.7278-1-pclouds@gmail.com/ [2] https://gitlab.com/pclouds/git/commits/switch-and-restore

Nguyễn Thái Ngọc Duy (19):
  git-checkout.txt: fix one syntax line
  doc: document --overwrite-ignore
  t: rename t2014-switch.sh to t2014-checkout-switch.sh
  checkout: factor out some code in parse_branchname_arg()
  checkout: make "opts" in cmd_checkout() a pointer
  checkout: move 'confict_style' and 'dwim_..' to checkout_opts
  checkout: split options[] array in three pieces
  checkout: split part of it to new command switch
  switch: better names for -b and -B
  switch: remove -l
  switch: stop accepting pathspec
  switch: reject "do nothing" case
  switch: only allow explicit detached HEAD
  switch: add short option for --detach
  switch: no implicit dwim, use --guess to dwim
  switch: no worktree status unless real branch switch happens
  t: add tests for switch
  completion: support switch
  doc: promote "git switch"
 .gitignore                                    |   1 +
 Documentation/config/advice.txt               |  13 +-
 Documentation/config/branch.txt               |   4 +-
 Documentation/config/checkout.txt             |  17 +-
 Documentation/config/diff.txt                 |   3 +-
 Documentation/git-branch.txt                  |   8 +-
 Documentation/git-check-ref-format.txt        |   3 +-
 Documentation/git-checkout.txt                |  48 +-
 Documentation/git-format-patch.txt            |   2 +-
 Documentation/git-merge-base.txt              |   2 +-
 Documentation/git-merge.txt                   |   4 +
 Documentation/git-rebase.txt                  |   2 +-
 Documentation/git-remote.txt                  |   2 +-
 Documentation/git-rerere.txt                  |  10 +-
 Documentation/git-reset.txt                   |  20 +-
 Documentation/git-stash.txt                   |   9 +-
 Documentation/git-switch.txt                  | 236 +++++++++
 Documentation/gitattributes.txt               |   3 +-
 Documentation/gitcore-tutorial.txt            |  18 +-
 Documentation/giteveryday.txt                 |  24 +-
 Documentation/githooks.txt                    |   8 +-
 Documentation/gittutorial.txt                 |   4 +-
 Documentation/revisions.txt                   |   2 +-
 Documentation/user-manual.txt                 |  52 +-
 Makefile                                      |   1 +
 advice.c                                      |  11 +-
 builtin.h                                     |   1 +
 builtin/checkout.c                            | 464 +++++++++---------
 builtin/clone.c                               |   2 +-
 command-list.txt                              |   1 +
 contrib/completion/git-completion.bash        |  26 +
 git.c                                         |   1 +
 parse-options-cb.c                            |  17 +
 parse-options.h                               |   3 +-
 sha1-name.c                                   |   2 +-
 t/t1090-sparse-checkout-scope.sh              |  14 -
 ...014-switch.sh => t2014-checkout-switch.sh} |   0
 t/t2020-checkout-detach.sh                    |   4 +-
 t/t2060-switch.sh                             |  91 ++++
 39 files changed, 770 insertions(+), 363 deletions(-)
 create mode 100644 Documentation/git-switch.txt
 rename t/{t2014-switch.sh => t2014-checkout-switch.sh} (100%)
 create mode 100755 t/t2060-switch.sh
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 01/19] git-checkout.txt: fix one syntax line

<branch> can be omitted in this syntax, and it's actually documented a few paragraphs down:

  You could omit <branch>, in which case the command degenerates to
  "check out the current branch", which is a glorified no-op with
  rather expensive side-effects to show only the tracking information,
  if exists, for the current branch.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to Documentation/git-checkout.txt +1 −1
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index ce7d38b8b7..bc7fbdb8d2 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -23,7 +23,7 @@ or the specified tree.  If no paths are given, 'git checkout' will
 also update `HEAD` to set the specified branch as the current
 branch.
 
-'git checkout' <branch>::
+'git checkout' [<branch>]::
 	To prepare for working on <branch>, switch to it by updating
 	the index and the files in the working tree, and by pointing
 	HEAD at the branch. Local modifications to the files in the
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 02/19] doc: document --overwrite-ignore

I added this option in git-checkout and git-merge in c1d7036b6b (checkout,merge: disallow overwriting ignored files with --no-overwrite-ignore - 2011-11-27) but did not remember to update documentation. This completes that commit.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt | 5 +++++
 Documentation/git-merge.txt    | 4 ++++
 2 files changed, 9 insertions(+)
Show changes to 2 files +9 −0

Documentation/git-checkout.txt, Documentation/git-merge.txt

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index bc7fbdb8d2..9ac01f7967 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -270,6 +270,11 @@ Note that this option uses the no overlay mode by default (see also
 	out anyway. In other words, the ref can be held by more than one
 	worktree.
 
+--[no-]overwrite-ignore::
+	Silently overwrite ignored files when switching branches. This
+	is the default behavior. Use --no-overwrite-ignore to abort
+	the operation when the new branch contains ignored files.
+
 --[no-]recurse-submodules::
 	Using --recurse-submodules will update the content of all initialized
 	submodules according to the commit recorded in the superproject. If
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index 4cc86469f3..0480e9d0f0 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -87,6 +87,10 @@ will be appended to the specified message.
 	Allow the rerere mechanism to update the index with the
 	result of auto-conflict resolution if possible.
 
+--[no-]overwrite-ignore::
+	Silently overwrite ignored files from the merge result. This
+	is the default behavior. Use --no-overwrite-ignore to abort.
+
 --abort::
 	Abort the current conflict resolution process, and
 	try to reconstruct the pre-merge state.
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 03/19] t: rename t2014-switch.sh to t2014-checkout-switch.sh

The old name does not really say that this is about 'checkout -b'. See 49d833dc07 (Revert "checkout branch: prime cache-tree fully" - 2009-05-12) for more information

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 t/{t2014-switch.sh => t2014-checkout-switch.sh} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename t/{t2014-switch.sh => t2014-checkout-switch.sh} (100%)
Show changes to t/t2014-switch.sh +0 −0
diff --git a/t/t2014-switch.sh b/t/t2014-checkout-switch.sh
similarity index 100%
rename from t/t2014-switch.sh
rename to t/t2014-checkout-switch.sh
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 04/19] checkout: factor out some code in parse_branchname_arg()

This is in preparation for the new command restore, which also needs to parse opts->source_tree but does not need all the disambiguation logic.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 51 ++++++++++++++++++++++++++++------------------
 1 file changed, 31 insertions(+), 20 deletions(-)
Show changes to builtin/checkout.c +31 −20
diff --git a/builtin/checkout.c b/builtin/checkout.c
index f63d864a91..12b7e08d4e 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1068,6 +1068,34 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
 	return git_xmerge_config(var, value, NULL);
 }
 
+static void setup_new_branch_info_and_source_tree(
+	struct branch_info *new_branch_info,
+	struct checkout_opts *opts,
+	struct object_id *rev,
+	const char *arg)
+{
+	struct tree **source_tree = &opts->source_tree;
+	struct object_id branch_rev;
+
+	new_branch_info->name = arg;
+	setup_branch_path(new_branch_info);
+
+	if (!check_refname_format(new_branch_info->path, 0) &&
+	    !read_ref(new_branch_info->path, &branch_rev))
+		oidcpy(rev, &branch_rev);
+	else
+		new_branch_info->path = NULL; /* not an existing branch */
+
+	new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
+	if (!new_branch_info->commit) {
+		/* not a commit */
+		*source_tree = parse_tree_indirect(rev);
+	} else {
+		parse_commit_or_die(new_branch_info->commit);
+		*source_tree = get_commit_tree(new_branch_info->commit);
+	}
+}
+
 static int parse_branchname_arg(int argc, const char **argv,
 				int dwim_new_local_branch_ok,
 				struct branch_info *new_branch_info,
@@ -1075,10 +1103,8 @@ static int parse_branchname_arg(int argc, const char **argv,
 				struct object_id *rev,
 				int *dwim_remotes_matched)
 {
-	struct tree **source_tree = &opts->source_tree;
 	const char **new_branch = &opts->new_branch;
 	int argcount = 0;
-	struct object_id branch_rev;
 	const char *arg;
 	int dash_dash_pos;
 	int has_dash_dash = 0;
@@ -1200,26 +1226,11 @@ static int parse_branchname_arg(int argc, const char **argv,
 	argv++;
 	argc--;
 
-	new_branch_info->name = arg;
-	setup_branch_path(new_branch_info);
-
-	if (!check_refname_format(new_branch_info->path, 0) &&
-	    !read_ref(new_branch_info->path, &branch_rev))
-		oidcpy(rev, &branch_rev);
-	else
-		new_branch_info->path = NULL; /* not an existing branch */
+	setup_new_branch_info_and_source_tree(new_branch_info, opts, rev, arg);
 
-	new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
-	if (!new_branch_info->commit) {
-		/* not a commit */
-		*source_tree = parse_tree_indirect(rev);
-	} else {
-		parse_commit_or_die(new_branch_info->commit);
-		*source_tree = get_commit_tree(new_branch_info->commit);
-	}
-
-	if (!*source_tree)                   /* case (1): want a tree */
+	if (!opts->source_tree)                   /* case (1): want a tree */
 		die(_("reference is not a tree: %s"), arg);
+
 	if (!has_dash_dash) {	/* case (3).(d) -> (1) */
 		/*
 		 * Do not complain the most common case
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 05/19] checkout: make "opts" in cmd_checkout() a pointer

"opts" will soon be moved out of cmd_checkout(). To keep changes in that patch smaller, convert "opts" to a pointer and keep the real thing behind "real_opts".

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 115 +++++++++++++++++++++++----------------------
 1 file changed, 58 insertions(+), 57 deletions(-)
Show changes to builtin/checkout.c +58 −57
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 12b7e08d4e..66f99e788d 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1326,82 +1326,83 @@ static int checkout_branch(struct checkout_opts *opts,
 
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
-	struct checkout_opts opts;
+	struct checkout_opts real_opts;
+	struct checkout_opts *opts = &real_opts;
 	struct branch_info new_branch_info;
 	char *conflict_style = NULL;
 	int dwim_new_local_branch, no_dwim_new_local_branch = 0;
 	int dwim_remotes_matched = 0;
 	struct option options[] = {
-		OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
-		OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
+		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
+		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"),
+		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"),
+		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),
-		OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
-		OPT_SET_INT_F('2', "ours", &opts.writeout_stage,
+		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
+		OPT_SET_INT_F('2', "ours", &opts->writeout_stage,
 			      N_("checkout our version for unmerged files"),
 			      2, PARSE_OPT_NONEG),
-		OPT_SET_INT_F('3', "theirs", &opts.writeout_stage,
+		OPT_SET_INT_F('3', "theirs", &opts->writeout_stage,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
-		OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)"),
+		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")),
-		OPT_BOOL_F(0, "overwrite-ignore", &opts.overwrite_ignore,
+		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
+		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
 		OPT_STRING(0, "conflict", &conflict_style, N_("style"),
 			   N_("conflict style (merge or diff3)")),
-		OPT_BOOL('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
-		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree,
+		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_BOOL(0, "no-guess", &no_dwim_new_local_branch,
 			 N_("do not second guess 'git checkout <no-such-branch>'")),
-		OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
+		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
 		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
 			    "checkout", "control recursive updating of submodules",
 			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
-		OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
-		OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")),
+		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
+		OPT_BOOL(0, "overlay", &opts->overlay_mode, N_("use overlay mode (default)")),
 		OPT_END(),
 	};
 
-	memset(&opts, 0, sizeof(opts));
+	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->overwrite_ignore = 1;
+	opts->prefix = prefix;
+	opts->show_progress = -1;
+	opts->overlay_mode = -1;
 
-	git_config(git_checkout_config, &opts);
+	git_config(git_checkout_config, opts);
 
-	opts.track = BRANCH_TRACK_UNSPECIFIED;
+	opts->track = BRANCH_TRACK_UNSPECIFIED;
 
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
 	dwim_new_local_branch = !no_dwim_new_local_branch;
-	if (opts.show_progress < 0) {
-		if (opts.quiet)
-			opts.show_progress = 0;
+	if (opts->show_progress < 0) {
+		if (opts->quiet)
+			opts->show_progress = 0;
 		else
-			opts.show_progress = isatty(2);
+			opts->show_progress = isatty(2);
 	}
 
 	if (conflict_style) {
-		opts.merge = 1; /* implied */
+		opts->merge = 1; /* implied */
 		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
 	}
 
-	if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)
+	if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
 		die(_("-b, -B and --orphan are mutually exclusive"));
 
-	if (opts.overlay_mode == 1 && opts.patch_mode)
+	if (opts->overlay_mode == 1 && opts->patch_mode)
 		die(_("-p and --overlay are mutually exclusive"));
 
 	/*
@@ -1409,14 +1410,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	 * and new_branch_force and new_orphan_branch will tell us which one of
 	 * -b/-B/--orphan is being used.
 	 */
-	if (opts.new_branch_force)
-		opts.new_branch = opts.new_branch_force;
+	if (opts->new_branch_force)
+		opts->new_branch = opts->new_branch_force;
 
-	if (opts.new_orphan_branch)
-		opts.new_branch = opts.new_orphan_branch;
+	if (opts->new_orphan_branch)
+		opts->new_branch = opts->new_orphan_branch;
 
 	/* --track without -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];
 		if (!argc || !strcmp(argv0, "--"))
 			die(_("--track needs a branch name"));
@@ -1425,7 +1426,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		argv0 = strchr(argv0, '/');
 		if (!argv0 || !argv0[1])
 			die(_("missing branch name; try -b"));
-		opts.new_branch = argv0 + 1;
+		opts->new_branch = argv0 + 1;
 	}
 
 	/*
@@ -1444,56 +1445,56 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	if (argc) {
 		struct object_id rev;
 		int dwim_ok =
-			!opts.patch_mode &&
+			!opts->patch_mode &&
 			dwim_new_local_branch &&
-			opts.track == BRANCH_TRACK_UNSPECIFIED &&
-			!opts.new_branch;
+			opts->track == BRANCH_TRACK_UNSPECIFIED &&
+			!opts->new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
-					     &new_branch_info, &opts, &rev,
+					     &new_branch_info, opts, &rev,
 					     &dwim_remotes_matched);
 		argv += n;
 		argc -= n;
 	}
 
 	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)
+		if (opts->new_branch && argc == 1)
 			die(_("'%s' is not a commit and a branch '%s' cannot be created from it"),
-				argv[0], opts.new_branch);
+				argv[0], opts->new_branch);
 
-		if (opts.force_detach)
+		if (opts->force_detach)
 			die(_("git checkout: --detach does not take a path argument '%s'"),
 			    argv[0]);
 
-		if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
+		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.new_branch) {
+	if (opts->new_branch) {
 		struct strbuf buf = STRBUF_INIT;
 
-		if (opts.new_branch_force)
-			opts.branch_exists = validate_branchname(opts.new_branch, &buf);
+		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);
+			opts->branch_exists =
+				validate_new_branchname(opts->new_branch, &buf, 0);
 		strbuf_release(&buf);
 	}
 
 	UNLEAK(opts);
-	if (opts.patch_mode || opts.pathspec.nr) {
-		int ret = checkout_paths(&opts, new_branch_info.name);
+	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"
@@ -1512,6 +1513,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			       dwim_remotes_matched);
 		return ret;
 	} else {
-		return checkout_branch(&opts, &new_branch_info);
+		return checkout_branch(opts, &new_branch_info);
 	}
 }
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 06/19] checkout: move 'confict_style' and 'dwim_..' to checkout_opts

These local variables are referenced by struct option[]. This struct will soon be broken down, moved away and we can't rely on local variables anymore. Move these two to struct checkout_opts in preparation for that.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)
Show changes to builtin/checkout.c +10 −7
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 66f99e788d..5914dd3619 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -46,6 +46,8 @@ struct checkout_opts {
 	int show_progress;
 	int count_checkout_paths;
 	int overlay_mode;
+	int no_dwim_new_local_branch;
+
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
 	 * should be updated accordingly.
@@ -57,6 +59,7 @@ struct checkout_opts {
 	int new_branch_log;
 	enum branch_track track;
 	struct diff_options diff_options;
+	char *conflict_style;
 
 	int branch_exists;
 	const char *prefix;
@@ -1329,8 +1332,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	struct checkout_opts real_opts;
 	struct checkout_opts *opts = &real_opts;
 	struct branch_info new_branch_info;
-	char *conflict_style = NULL;
-	int dwim_new_local_branch, no_dwim_new_local_branch = 0;
+	int dwim_new_local_branch;
 	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
@@ -1355,12 +1357,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_STRING(0, "conflict", &conflict_style, N_("style"),
+		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
 			   N_("conflict style (merge or diff3)")),
 		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_BOOL(0, "no-guess", &no_dwim_new_local_branch,
+		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
 			 N_("do not second guess 'git checkout <no-such-branch>'")),
 		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
@@ -1378,6 +1380,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts->prefix = prefix;
 	opts->show_progress = -1;
 	opts->overlay_mode = -1;
+	opts->no_dwim_new_local_branch = 0;
 
 	git_config(git_checkout_config, opts);
 
@@ -1386,7 +1389,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
-	dwim_new_local_branch = !no_dwim_new_local_branch;
+	dwim_new_local_branch = !opts->no_dwim_new_local_branch;
 	if (opts->show_progress < 0) {
 		if (opts->quiet)
 			opts->show_progress = 0;
@@ -1394,9 +1397,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			opts->show_progress = isatty(2);
 	}
 
-	if (conflict_style) {
+	if (opts->conflict_style) {
 		opts->merge = 1; /* implied */
-		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
+		git_xmerge_config("merge.conflictstyle", opts->conflict_style, NULL);
 	}
 
 	if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 07/19] checkout: split options[] array in three pieces

This is a preparation step for introducing new commands that do parts of what checkout does. There will be two new commands, one is about switching branches, detaching HEAD... one about checking out paths. These share the a subset of command line options. The rest of command line options are separate.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 82 +++++++++++++++++++++++++++++++++-------------
 parse-options-cb.c | 17 ++++++++++
 parse-options.h    |  3 +-
 3 files changed, 78 insertions(+), 24 deletions(-)
Show changes to 3 files +78 −24

builtin/checkout.c, parse-options-cb.c, parse-options.h

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 5914dd3619..8ea1349cce 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1327,15 +1327,31 @@ static int checkout_branch(struct checkout_opts *opts,
 	return switch_branches(opts, new_branch_info);
 }
 
-int cmd_checkout(int argc, const char **argv, const char *prefix)
+static struct option *add_common_options(struct checkout_opts *opts,
+					 struct option *prevopts)
 {
-	struct checkout_opts real_opts;
-	struct checkout_opts *opts = &real_opts;
-	struct branch_info new_branch_info;
-	int dwim_new_local_branch;
-	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
+		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
+			    "checkout", "control recursive updating of submodules",
+			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
+		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
+		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
+			   PARSE_OPT_NOCOMPLETE),
+		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
+		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
+			   N_("conflict style (merge or diff3)")),
+		OPT_END()
+	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+static struct option *add_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"),
@@ -1345,34 +1361,49 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
 			BRANCH_TRACK_EXPLICIT),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
+		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
+			   N_("update ignored files (default)"),
+			   PARSE_OPT_NOCOMPLETE),
+		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
+			 N_("second guess 'git checkout <no-such-branch>'")),
+		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
+			 N_("do not check if another worktree is holding the given ref")),
+		OPT_END()
+	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+static struct option *add_checkout_path_options(struct checkout_opts *opts,
+						struct option *prevopts)
+{
+	struct option options[] = {
 		OPT_SET_INT_F('2', "ours", &opts->writeout_stage,
 			      N_("checkout our version for unmerged files"),
 			      2, PARSE_OPT_NONEG),
 		OPT_SET_INT_F('3', "theirs", &opts->writeout_stage,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
-		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
-			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
-		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
-			   N_("update ignored files (default)"),
-			   PARSE_OPT_NOCOMPLETE),
-		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
-			   N_("conflict style (merge or diff3)")),
 		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_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
-			 N_("do not second guess 'git checkout <no-such-branch>'")),
-		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
-			 N_("do not check if another worktree is holding the given ref")),
-		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
-			    "checkout", "control recursive updating of submodules",
-			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
-		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
 		OPT_BOOL(0, "overlay", &opts->overlay_mode, N_("use overlay mode (default)")),
-		OPT_END(),
+		OPT_END()
 	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+int cmd_checkout(int argc, const char **argv, const char *prefix)
+{
+	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));
@@ -1386,6 +1417,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	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,
 			     PARSE_OPT_KEEP_DASHDASH);
 
diff --git a/parse-options-cb.c b/parse-options-cb.c
index e2f3eaed07..76eb5c211b 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -122,6 +122,23 @@ int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
 	return 0;
 }
 
+struct option *parse_options_dup(const struct option *o)
+{
+	struct option *opts;
+	int nr = 0;
+
+	while (o && o->type != OPTION_END) {
+		nr++;
+		o++;
+	}
+
+	ALLOC_ARRAY(opts, nr + 1);
+	memcpy(opts, o - nr, sizeof(*o) * nr);
+	memset(opts + nr, 0, sizeof(*opts));
+	opts[nr].type = OPTION_END;
+	return opts;
+}
+
 struct option *parse_options_concat(struct option *a, struct option *b)
 {
 	struct option *ret;
diff --git a/parse-options.h b/parse-options.h
index 14fe32428e..6846a5b1e0 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -237,7 +237,8 @@ extern int parse_options_step(struct parse_opt_ctx_t *ctx,
 
 extern int parse_options_end(struct parse_opt_ctx_t *ctx);
 
-extern struct option *parse_options_concat(struct option *a, struct option *b);
+struct option *parse_options_dup(const struct option *a);
+struct option *parse_options_concat(struct option *a, struct option *b);
 
 /*----- some often used options -----*/
 extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 08/19] checkout: split part of it to new command switch

"git checkout" doing too many things is a source of confusion for many users (and it even bites old timers sometimes). To remedy that, the command will be split into two new ones: switch and something-to-checkout-paths. The good old "git checkout" command is still here and will be until all (or most of users) are sick of it.

See the new man page for the final design of switch. The actual implementation though is still pretty much the same as "git checkout" and not completely aligned with the man page. Following patches will adjust their behavior to match the man page.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 .gitignore                        |   1 +
 Documentation/config/advice.txt   |  13 +-
 Documentation/config/branch.txt   |   4 +-
 Documentation/config/checkout.txt |   9 +-
 Documentation/config/diff.txt     |   3 +-
 Documentation/git-checkout.txt    |   4 +
 Documentation/git-switch.txt      | 236 ++++++++++++++++++++++++++++++
 Documentation/gitattributes.txt   |   3 +-
 Documentation/githooks.txt        |   8 +-
 Makefile                          |   1 +
 builtin.h                         |   1 +
 builtin/checkout.c                |  60 ++++++--
 command-list.txt                  |   1 +
 git.c                             |   1 +
 14 files changed, 318 insertions(+), 27 deletions(-)
 create mode 100644 Documentation/git-switch.txt
Show changes to 14 files +318 −27

.gitignore, Documentation/config/advice.txt, Documentation/config/branch.txt, Documentation/config/checkout.txt, Documentation/config/diff.txt, Documentation/git-checkout.txt, Documentation/git-switch.txt, Documentation/gitattributes.txt, Documentation/githooks.txt, Makefile, builtin.h, builtin/checkout.c, command-list.txt, git.c

diff --git a/.gitignore b/.gitignore
index 0d77ea5894..8e2b89047f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -167,6 +167,7 @@
 /git-submodule
 /git-submodule--helper
 /git-svn
+/git-switch
 /git-symbolic-ref
 /git-tag
 /git-unpack-file
diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
index 88620429ea..239d479506 100644
--- a/Documentation/config/advice.txt
+++ b/Documentation/config/advice.txt
@@ -42,7 +42,8 @@ advice.*::
 		state in the output of linkgit:git-status[1], in
 		the template shown when writing commit messages in
 		linkgit:git-commit[1], and in the help message shown
-		by linkgit:git-checkout[1] when switching branch.
+		by linkgit:git-switch[1] or
+		linkgit:git-checkout[1] when switching branch.
 	statusUoption::
 		Advise to consider using the `-u` option to linkgit:git-status[1]
 		when the command takes more than 2 seconds to enumerate untracked
@@ -62,12 +63,14 @@ advice.*::
 		your information is guessed from the system username and
 		domain name.
 	detachedHead::
-		Advice shown when you used linkgit:git-checkout[1] to
-		move to the detach HEAD state, to instruct how to create
-		a local branch after the fact.
+		Advice shown when you used
+		linkgit:git-switch[1] or linkgit:git-checkout[1]
+		to move to the detach HEAD state, to instruct how to
+		create a local branch after the fact.
 	checkoutAmbiguousRemoteBranchName::
 		Advice shown when the argument to
-		linkgit:git-checkout[1] ambiguously resolves to a
+		linkgit:git-checkout[1] and linkgit:git-switch[1]
+		ambiguously resolves to a
 		remote tracking branch on more than one remote in
 		situations where an unambiguous argument would have
 		otherwise caused a remote-tracking branch to be
diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index 019d60ede2..8050466159 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -1,5 +1,5 @@
 branch.autoSetupMerge::
-	Tells 'git branch' and 'git checkout' to set up new branches
+	Tells 'git branch', 'git switch' and 'git checkout' to set up new branches
 	so that linkgit:git-pull[1] will appropriately merge from the
 	starting point branch. Note that even if this option is not set,
 	this behavior can be chosen per-branch using the `--track`
@@ -11,7 +11,7 @@ branch.autoSetupMerge::
 	branch. This option defaults to true.
 
 branch.autoSetupRebase::
-	When a new branch is created with 'git branch' or 'git checkout'
+	When a new branch is created with 'git branch', 'git switch' or 'git checkout'
 	that tracks another branch, this variable tells Git to set
 	up pull to rebase instead of merge (see "branch.<name>.rebase").
 	When `never`, rebase is never automatically set to true.
diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt
index 73380a8d86..fc211eca58 100644
--- a/Documentation/config/checkout.txt
+++ b/Documentation/config/checkout.txt
@@ -1,5 +1,6 @@
 checkout.defaultRemote::
-	When you run 'git checkout <something>' and only have one
+	When you run 'git checkout <something>'
+	or 'git switch <something>' and only have one
 	remote, it may implicitly fall back on checking out and
 	tracking e.g. 'origin/<something>'. This stops working as soon
 	as you have more than one remote with a '<something>'
@@ -8,8 +9,10 @@ checkout.defaultRemote::
 	disambiguation. The typical use-case is to set this to
 	`origin`.
 +
-Currently this is used by linkgit:git-checkout[1] when 'git checkout
-<something>' will checkout the '<something>' branch on another remote,
+Currently this is used by linkgit:git-switch[1] and
+linkgit:git-checkout[1] when 'git checkout <something>'
+or 'git switch <something>'
+will checkout the '<something>' branch on another remote,
 and by linkgit:git-worktree[1] when 'git worktree add' refers to a
 remote branch. This setting might be used for other checkout-like
 commands or functionality in the future.
diff --git a/Documentation/config/diff.txt b/Documentation/config/diff.txt
index e48bb987d7..b3b304ee12 100644
--- a/Documentation/config/diff.txt
+++ b/Documentation/config/diff.txt
@@ -78,7 +78,8 @@ diff.external::
 diff.ignoreSubmodules::
 	Sets the default value of --ignore-submodules. Note that this
 	affects only 'git diff' Porcelain, and not lower level 'diff'
-	commands such as 'git diff-files'. 'git checkout' also honors
+	commands such as 'git diff-files'. 'git checkout'
+	and 'git switch' also honor
 	this setting when reporting uncommitted changes. Setting it to
 	'all' disables the submodule summary normally shown by 'git commit'
 	and 'git status' when `status.submoduleSummary` is set unless it is
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 9ac01f7967..31c6cbef19 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -556,6 +556,10 @@ $ edit frotz
 $ git add frotz
 ------------
 
+SEE ALSO
+--------
+linkgit:git-switch[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
new file mode 100644
index 0000000000..953c9747b2
--- /dev/null
+++ b/Documentation/git-switch.txt
@@ -0,0 +1,236 @@
+git-switch(1)
+=============
+
+NAME
+----
+git-switch - Switch branches
+
+SYNOPSIS
+--------
+[verse]
+'git switch' [<options>] [--guess] <branch>
+'git switch' [<options>] --detach [<start_point>>]
+'git switch' [<options>] [[-c|-C|--orphan] <new_branch>] [<start_point>]
+
+DESCRIPTION
+-----------
+Switch to a specified branch and update files in the working tree to
+match it. Optionally a new branch could be created with either `-c` or
+`-C`, or detach from any branch with `--detach`, along with switching.
+
+OPTIONS
+-------
+<branch>::
+	Branch to switch to.
+
+<new_branch>::
+	Name for the new branch.
+
+<start_point>::
+	The name of a commit at which to switch to before creating a
+	new branch or detach from.
++
+You can use the `"@{-N}"` syntax to refer to the N-th last
+branch/commit switched to "git switch" or "git checkout"
+operation. You may also specify `-` which is synonymous to `"@{-1}`.
++
+As a special case, you may use `"A...B"` as a shortcut for the merge
+base of `A` and `B` if there is exactly one merge base. You can leave
+out at most one of `A` and `B`, in which case it defaults to `HEAD`.
+
+-c <new_branch>::
+--create <new_branch>::
+	Create a new branch named <new_branch> and start it at
+	<start_point>; see linkgit:git-branch[1] for details.
+
+-C <new_branch>::
+--force-create <new_branch>::
+	Creates the branch <new_branch> and start it at <start_point>;
+	if it already exists, then reset it to <start_point>. This is
+	equivalent to running "git branch" with "-f"; see
+	linkgit:git-branch[1] for details.
+
+-d::
+--detach::
+	Switch to a commit for inspection and discardable
+	experiments. See the "DETACHED HEAD" section in
+	linkgit:git-checkout[1] for details.
+
+-g::
+--guess::
+	If <branch> is not found but there does exist a tracking
+	branch in exactly one remote (call it <remote>) with a
+	matching name, treat as equivalent to
++
+------------
+$ git switch -c <branch> --track <remote>/<branch>
+------------
++
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to e.g. `checkout.defaultRemote=origin`
+to always checkout remote branches from there if `<branch>` is
+ambiguous but exists on the 'origin' remote. See also
+`checkout.defaultRemote` in linkgit:git-config[1].
+
+-q::
+--quiet::
+	Quiet, suppress feedback messages.
+
+-f::
+--force::
+	Proceed even if the index or the working tree differs from
+	HEAD.  This is used to throw away local changes.
+
+--[no-]progress::
+	Progress status is reported on the standard error stream
+	by default when it is attached to a terminal, unless `--quiet`
+	is specified. This flag enables progress reporting even if not
+	attached to a terminal, regardless of `--quiet`.
+
+-t::
+--track::
+	When creating a new branch, set up "upstream" configuration.
+	`-c` is implied. See "--track" in linkgit:git-branch[1] for
+	details.
++
+If no `-c` option is given, the name of the new branch will be derived
+from the remote-tracking branch, by looking at the local part of the
+refspec configured for the corresponding remote, and then stripping
+the initial part up to the "*".  This would tell us to use "hack" as
+the local branch when branching off of "origin/hack" (or
+"remotes/origin/hack", or even "refs/remotes/origin/hack").  If the
+given name has no slash, or the above guessing results in an empty
+name, the guessing is aborted.  You can explicitly give a name with
+`-c` in such a case.
+
+--no-track::
+	Do not set up "upstream" configuration, even if the
+	branch.autoSetupMerge configuration variable is true.
+
+-m::
+--merge::
+	If you have local modifications to one or more files that are
+	different between the current branch and the branch to which
+	you are switching, the command refuses to switch branches in
+	order to preserve your modifications in context.  However,
+	with this option, a three-way merge between the current
+	branch, your working tree contents, and the new branch is
+	done, and you will be on the new branch.
++
+When a merge conflict happens, the index entries for conflicting
+paths are left unmerged, and you need to resolve the conflicts
+and mark the resolved paths with `git add` (or `git rm` if the merge
+should result in deletion of the path).
+
+--conflict=<style>::
+	The same as --merge option above, but changes the way the
+	conflicting hunks are presented, overriding the
+	merge.conflictStyle configuration variable.  Possible values are
+	"merge" (default) and "diff3" (in addition to what is shown by
+	"merge" style, shows the original contents).
+
+--orphan <new_branch>::
+	Create a new 'orphan' branch, named <new_branch>, started from
+	<start_point> and switch to it. See explanation of the same
+	option in linkgit:git-checkout[1] for details.
+
+--ignore-other-worktrees::
+	`git switch` refuses when the wanted ref is already
+	checked out by another worktree. This option makes it check
+	the ref out anyway. In other words, the ref can be held by
+	more than one worktree.
+
+--[no-]recurse-submodules::
+	Using --recurse-submodules will update the content of all initialized
+	submodules according to the commit recorded in the superproject. If
+	local modifications in a submodule would be overwritten the checkout
+	will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
+	is used, the work trees of submodules will not be updated.
+	Just like linkgit:git-submodule[1], this will detach the
+	submodules HEAD.
+
+EXAMPLES
+--------
+
+The following command switches to the "master" branch:
+
+------------
+$ git switch master
+------------
+
+After working in the wrong branch, switching to the correct branch
+would be done using:
+
+------------
+$ git switch mytopic
+------------
+
+However, your "wrong" branch and correct "mytopic" branch may differ
+in files that you have modified locally, in which case the above
+switch would fail like this:
+
+------------
+$ git switch mytopic
+error: You have local changes to 'frotz'; not switching branches.
+------------
+
+You can give the `-m` flag to the command, which would try a three-way
+merge:
+
+------------
+$ git switch -m mytopic
+Auto-merging frotz
+------------
+
+After this three-way merge, the local modifications are _not_
+registered in your index file, so `git diff` would show you what
+changes you made since the tip of the new branch.
+
+To switch back to the previous branch before we switched to mytopic
+(i.e. "master" branch):
+
+------------
+$ git switch -
+------------
+
+You can grow a new branch from any commit. For example, switch to
+"HEAD~3" and create branch "fixup":
+
+------------
+$ git switch -c fixup HEAD~3
+Switched to a new branch 'fixup'
+------------
+
+If you want to start a new branch from a remote branch of the same
+name, use `-g`:
+
+------------
+$ git switch -g new-topic
+Branch 'new-topic' set up to track remote branch 'new-topic' from 'origin'
+Switched to a new branch 'new-topic'
+------------
+
+To check out commit "HEAD~3" for temporary inspection or experiment
+without creating a new branch:
+
+------------
+$ git switch --detach HEAD~3
+HEAD is now at 9fc9555312 Merge branch 'cc/shared-index-permbits'
+------------
+
+If it turns out whatever you have done is worth keeping, you can
+always create a new name for it (without switching away):
+
+------------
+$ git switch -c good-surprises
+------------
+
+SEE ALSO
+--------
+linkgit:git-checkout[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index b8392fc330..5731caa4c6 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -112,7 +112,8 @@ Checking-out and checking-in
 
 These attributes affect how the contents stored in the
 repository are copied to the working tree files when commands
-such as 'git checkout' and 'git merge' run.  They also affect how
+such as 'git switch', 'git checkout'  and 'git merge' run.
+They also affect how
 Git stores the contents you prepare in the working tree in the
 repository upon 'git add' and 'git commit'.
 
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 959044347e..e257a7810a 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -161,12 +161,13 @@ rebased, and is not set when rebasing the current branch.
 post-checkout
 ~~~~~~~~~~~~~
 
-This hook is invoked when a linkgit:git-checkout[1] is run after having updated the
+This hook is invoked when a linkgit:git-checkout[1] or
+linkgit:git-switch[1] is run after having updated the
 worktree.  The hook is given three parameters: the ref of the previous HEAD,
 the ref of the new HEAD (which may or may not have changed), and a flag
 indicating whether the checkout was a branch checkout (changing branches,
 flag=1) or a file checkout (retrieving a file from the index, flag=0).
-This hook cannot affect the outcome of `git checkout`.
+This hook cannot affect the outcome of `git switch` or `git checkout`.
 
 It is also run after linkgit:git-clone[1], unless the `--no-checkout` (`-n`) option is
 used. The first parameter given to the hook is the null-ref, the second the
@@ -402,7 +403,8 @@ exit with a zero status.
 For example, the hook can simply run `git read-tree -u -m HEAD "$1"`
 in order to emulate `git fetch` that is run in the reverse direction
 with `git push`, as the two-tree form of `git read-tree -u -m` is
-essentially the same as `git checkout` that switches branches while
+essentially the same as `git switch` or `git checkout`
+that switches branches while
 keeping the local changes in the working tree that do not interfere
 with the difference between the branches.
 
diff --git a/Makefile b/Makefile
index 6e8d017e8e..0e5b29390c 100644
--- a/Makefile
+++ b/Makefile
@@ -789,6 +789,7 @@ BUILT_INS += git-merge-subtree$X
 BUILT_INS += git-show$X
 BUILT_INS += git-stage$X
 BUILT_INS += git-status$X
+BUILT_INS += git-switch$X
 BUILT_INS += git-whatchanged$X
 
 # what 'all' will build and 'install' will install in gitexecdir,
diff --git a/builtin.h b/builtin.h
index 6538932e99..c64e44450e 100644
--- a/builtin.h
+++ b/builtin.h
@@ -227,6 +227,7 @@ extern int cmd_show_index(int argc, const char **argv, const char *prefix);
 extern int cmd_status(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
+extern int cmd_switch(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 8ea1349cce..2ac53b4302 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -33,6 +33,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;
@@ -1396,33 +1401,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;
@@ -1555,3 +1552,42 @@ 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 = NULL;
+	int ret;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.no_dwim_new_local_branch = 0;
+
+	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);
+
+	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;
+	int ret;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.no_dwim_new_local_branch = 0;
+
+	options = parse_options_dup(options);
+	options = add_common_options(&opts, options);
+	options = add_switch_branch_options(&opts, options);
+
+	ret = checkout_main(argc, argv, prefix, &opts,
+			    options, switch_branch_usage);
+	FREE_AND_NULL(options);
+	return ret;
+}
diff --git a/command-list.txt b/command-list.txt
index 3a9af104b5..13317f47d4 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -171,6 +171,7 @@ git-status                              mainporcelain           info
 git-stripspace                          purehelpers
 git-submodule                           mainporcelain
 git-svn                                 foreignscminterface
+git-switch                              mainporcelain           history
 git-symbolic-ref                        plumbingmanipulators
 git-tag                                 mainporcelain           history
 git-unpack-file                         plumbinginterrogators
diff --git a/git.c b/git.c
index 0ce0e13f0f..31b8e8d6a1 100644
--- a/git.c
+++ b/git.c
@@ -558,6 +558,7 @@ static struct cmd_struct commands[] = {
 	{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
 	{ "stripspace", cmd_stripspace },
 	{ "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
+	{ "switch", cmd_switch, RUN_SETUP | NEED_WORK_TREE },
 	{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
 	{ "tag", cmd_tag, RUN_SETUP | DELAY_PAGER_CONFIG },
 	{ "unpack-file", cmd_unpack_file, RUN_SETUP | NO_PARSEOPT },
-- 
2.20.1.682.gd5861c6d90
Eric Sunshine· Jan 31, 2019, 00:50 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH 08/19] checkout: split part of it to new command switch

On Wed, Jan 30, 2019 at 4:49 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 14 quoted lines
> "git checkout" doing too many things is a source of confusion for many
> users (and it even bites old timers sometimes). To remedy that, the
> command will be split into two new ones: switch and
> something-to-checkout-paths. The good old "git checkout" command is
> still here and will be until all (or most of users) are sick of it.
> [...]
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
> diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
> @@ -0,0 +1,236 @@
> +SYNOPSIS
> +'git switch' [<options>] [--guess] <branch>
> +'git switch' [<options>] --detach [<start_point>>]
> +'git switch' [<options>] [[-c|-C|--orphan] <new_branch>] [<start_point>]

What does the third form mean when all optional arguments (that is, _all_ arguments) are omitted?

Nit: I realize you inherited this from git-checkout documentation, but
can you use '-' rather than '_', as in <start-point>, <new-branch>, to
be consistent with other documentation, such as git-branch.
Show 15 quoted lines
> +EXAMPLES
> +To check out commit "HEAD~3" for temporary inspection or experiment
> +without creating a new branch:
> +
> +------------
> +$ git switch --detach HEAD~3
> +HEAD is now at 9fc9555312 Merge branch 'cc/shared-index-permbits'
> +------------
> +
> +If it turns out whatever you have done is worth keeping, you can
> +always create a new name for it (without switching away):
> +
> +------------
> +$ git switch -c good-surprises
> +------------

I had to go re-read git-branch documentation to understand why you recommended "git switch -c <new-branch>" (or even why this mode exists) over plain "git branch <new-branch>". I wonder if the difference between the two deserves a bit more discussion here and above in the description of the -c/-C options.

Duy Nguyen· Jan 31, 2019, 05:29 UTC · re: Eric Sunshine · lore

Re: [PATCH 08/19] checkout: split part of it to new command switch

On Thu, Jan 31, 2019 at 7:50 AM Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 19 quoted lines
>
> On Wed, Jan 30, 2019 at 4:49 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> > "git checkout" doing too many things is a source of confusion for many
> > users (and it even bites old timers sometimes). To remedy that, the
> > command will be split into two new ones: switch and
> > something-to-checkout-paths. The good old "git checkout" command is
> > still here and will be until all (or most of users) are sick of it.
> > [...]
> > Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> > ---
> > diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
> > @@ -0,0 +1,236 @@
> > +SYNOPSIS
> > +'git switch' [<options>] [--guess] <branch>
> > +'git switch' [<options>] --detach [<start_point>>]
> > +'git switch' [<options>] [[-c|-C|--orphan] <new_branch>] [<start_point>]
>
> What does the third form mean when all optional arguments (that is,
> _all_ arguments) are omitted?
"git switch" is smart (or too dumb to be clever):

$ git switch fatal: nothing to do

> Nit: I realize you inherited this from git-checkout documentation, but
> can you use '-' rather than '_', as in <start-point>, <new-branch>, to
> be consistent with other documentation, such as git-branch.
A quick grep shows <some-thing> seems to win over <some_thing>. Yeah.
Show 21 quoted lines
> > +EXAMPLES
> > +To check out commit "HEAD~3" for temporary inspection or experiment
> > +without creating a new branch:
> > +
> > +------------
> > +$ git switch --detach HEAD~3
> > +HEAD is now at 9fc9555312 Merge branch 'cc/shared-index-permbits'
> > +------------
> > +
> > +If it turns out whatever you have done is worth keeping, you can
> > +always create a new name for it (without switching away):
> > +
> > +------------
> > +$ git switch -c good-surprises
> > +------------
>
> I had to go re-read git-branch documentation to understand why you
> recommended "git switch -c <new-branch>" (or even why this mode
> exists) over plain "git branch <new-branch>". I wonder if the
> difference between the two deserves a bit more discussion here and
> above in the description of the -c/-C options.
There is this bit in git-branch, will that help?

-- 8< -- NOTES -----

If you are creating a branch that you want to checkout immediately, it is easier to use the "git switch" command with its `-c` option to create a branch and check it out with a single command. -- 8< --

Otherwise any suggestions are welcome, I guess I could rephrase it a bit like
-c <new_branch>::
--create <new_branch>::
        Create a new branch named <new_branch> starting at
        <start_point> before switching to the branch. This is similar
        to creating a new branch with linkgit:git-branch[1] except
        that `git branch` does not switch branches.
-- 
Duy
Eric Sunshine· Jan 31, 2019, 06:24 UTC · re: Duy Nguyen · lore

Re: [PATCH 08/19] checkout: split part of it to new command switch

On Thu, Jan 31, 2019 at 12:29 AM Duy Nguyen <pclouds@gmail.com> wrote:
Show 13 quoted lines
> On Thu, Jan 31, 2019 at 7:50 AM Eric Sunshine <sunshine@sunshineco.com> wrote:
> > On Wed, Jan 30, 2019 at 4:49 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> > > +'git switch' [<options>] [--guess] <branch>
> > > +'git switch' [<options>] --detach [<start_point>>]
> > > +'git switch' [<options>] [[-c|-C|--orphan] <new_branch>] [<start_point>]
> >
> > What does the third form mean when all optional arguments (that is,
> > _all_ arguments) are omitted?
>
> "git switch" is smart (or too dumb to be clever):
>
> $ git switch
> fatal: nothing to do

But does it need to be this way? Does it make a good user-experience? I, personally, find it confusing to see that it can accept no arguments. An alternative, perhaps more consistent with UX elsewhere:

    $ git switch
    fatal: missing branch argument
or something.
Show 19 quoted lines
> > > +If it turns out whatever you have done is worth keeping, you can
> > > +always create a new name for it (without switching away):
> > > +------------
> > > +$ git switch -c good-surprises
> > > +------------
> >
> > I had to go re-read git-branch documentation to understand why you
> > recommended "git switch -c <new-branch>" (or even why this mode
> > exists) over plain "git branch <new-branch>". I wonder if the
> > difference between the two deserves a bit more discussion here and
> > above in the description of the -c/-C options.
>
> There is this bit in git-branch, will that help?
>
> -- 8< --
> If you are creating a branch that you want to checkout immediately, it is
> easier to use the "git switch" command with its `-c` option to create
> a branch and check it out with a single command.
> -- 8< --

That's pretty reasonable when reading git-branch documentation but doesn't help when reading git-switch documentation.

(Also, shouldn't the bit about "want to checkout" be changed to "want to switch to"?)

Show 8 quoted lines
> Otherwise any suggestions are welcome, I guess I could rephrase it a bit like
>
> -c <new_branch>::
> --create <new_branch>::
>         Create a new branch named <new_branch> starting at
>         <start_point> before switching to the branch. This is similar
>         to creating a new branch with linkgit:git-branch[1] except
>         that `git branch` does not switch branches.
This is much better.

I find, however, that the top-level git-switch "DESCRIPTION" section, which talks about "switching branches" doesn't actually ever explain what it means to "switch" to a branch. Even adding a simple sentence stating that "switching to a branch means that a newly-created commit will be a direct child of the current head of the branch, and that the branch will be updated to point at the new commit" would help cement the meaning of branch switching in the reader's mind (rather than assuming the reader understands that implicitly).

Eric Sunshine· Jan 31, 2019, 07:20 UTC · re: Eric Sunshine · lore

Re: [PATCH 08/19] checkout: split part of it to new command switch

On Thu, Jan 31, 2019 at 1:24 AM Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 23 quoted lines
> On Thu, Jan 31, 2019 at 12:29 AM Duy Nguyen <pclouds@gmail.com> wrote:
> > On Thu, Jan 31, 2019 at 7:50 AM Eric Sunshine <sunshine@sunshineco.com> wrote:
> > > On Wed, Jan 30, 2019 at 4:49 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> > > > +'git switch' [<options>] [--guess] <branch>
> > > > +'git switch' [<options>] --detach [<start_point>>]
> > > > +'git switch' [<options>] [[-c|-C|--orphan] <new_branch>] [<start_point>]
> > >
> > > What does the third form mean when all optional arguments (that is,
> > > _all_ arguments) are omitted?
> >
> > "git switch" is smart (or too dumb to be clever):
> >
> > $ git switch
> > fatal: nothing to do
>
> But does it need to be this way? Does it make a good user-experience?
> I, personally, find it confusing to see that it can accept no
> arguments. An alternative, perhaps more consistent with UX elsewhere:
>
>     $ git switch
>     fatal: missing branch argument
>
> or something.

Let me clarify by saying that I don't understand why the third form is documented as validly accepting no arguments given that a no-argument invocation is an error. That is, I would expect the third form of the synopsis to say:

    'git switch' [<options>] (-c|-C|--orphan) <new_branch> [<start_point>]
Duy Nguyen· Jan 31, 2019, 07:43 UTC · re: Eric Sunshine · lore

Re: [PATCH 08/19] checkout: split part of it to new command switch

On Thu, Jan 31, 2019 at 1:25 PM Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 8 quoted lines
> I find, however, that the top-level git-switch "DESCRIPTION" section,
> which talks about "switching branches" doesn't actually ever explain
> what it means to "switch" to a branch. Even adding a simple sentence
> stating that "switching to a branch means that a newly-created commit
> will be a direct child of the current head of the branch, and that the
> branch will be updated to point at the new commit" would help cement
> the meaning of branch switching in the reader's mind (rather than
> assuming the reader understands that implicitly).

Thanks. How about this? I skipped the "update branch to point to the new commit" because that sounds like something you should learn from git-commit and hopefully the word "commit" would be enough to recall that knowledge (or direct the user to git-commit.txt). I notice git-commit.txt does not say anything about branch update business though. Maybe some more updates there...

DESCRIPTION ----------- Switch to a specified branch. The working three and the index are also updated to match the branch. All new commits will be added to the tip of this branch.

Optionally a new branch could be created with either `-c` or `-C`, or detach the working tree from any branch with `--detach`, along with switching.

-- 
Duy
Eric Sunshine· Jan 31, 2019, 07:51 UTC · re: Duy Nguyen · lore

Re: [PATCH 08/19] checkout: split part of it to new command switch

On Thu, Jan 31, 2019 at 2:44 AM Duy Nguyen <pclouds@gmail.com> wrote:
Show 12 quoted lines
> On Thu, Jan 31, 2019 at 1:25 PM Eric Sunshine <sunshine@sunshineco.com> wrote:
> > I find, however, that the top-level git-switch "DESCRIPTION" section,
> > which talks about "switching branches" doesn't actually ever explain
> > what it means to "switch" to a branch. Even adding a simple sentence
> > stating that "switching to a branch means that a newly-created commit
> > will be a direct child of the current head of the branch, and that the
> > branch will be updated to point at the new commit" would help cement
> > the meaning of branch switching in the reader's mind (rather than
> > assuming the reader understands that implicitly).
>
> Thanks. How about this? I skipped the "update branch to point to the
> new commit" ...

I came to the same conclusion (about dropping this bit) upon re-reading what I wrote.

Show 5 quoted lines
> DESCRIPTION
> -----------
> Switch to a specified branch. The working three and the index are also
> updated to match the branch. All new commits will be added to the tip
> of this branch.
Yes, this is good. Simple and direct. A couple minor fixes:

s/three/tree/ s/also//

Nguyễn Thái Ngọc Duy· Jan 31, 2019, 09:21 UTC · re: Duy Nguyen · lore

[PATCH] git-commit.txt: better description what it does

The description of git-commit jumps right into the commit content, which is important, but it fails to mention how the commit is "added" to the repository. Update the first paragraph saying a bit more about branch update to fill this gap.

While at there, add a couple linkgit references when the command is first mentioned.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 On Thu, Jan 31, 2019 at 02:43:41PM +0700, Duy Nguyen wrote:
 > I notice git-commit.txt does not say anything about branch update
 > business though. Maybe some more updates there...
 Maybe something like this. Although I feel I'm quite close to some
 rabbit hole.
Documentation/git-commit.txt | 29 ++++++++++++++++++++++-------
 1 file changed, 22 insertions(+), 7 deletions(-)
Show changes to Documentation/git-commit.txt +22 −7
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index f970a43422..8148ba10b3 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -17,16 +17,19 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Stores the current contents of the index in a new commit along
-with a log message from the user describing the changes.
+Creates a new commit containing the current contents of the index with
+a log message from the user describing the changes. The commit is the
+direct child of the tip of the current branch. The branch is updated
+to point to the new commit (unless no branch is associated with the
+working tree, see "NOTES" section).
 
-The content to be added can be specified in several ways:
+The content to be committed can be specified in several ways:
 
-1. by using 'git add' to incrementally "add" changes to the
-   index before using the 'commit' command (Note: even modified
-   files must be "added");
+1. by using linkgit:git-add[1] to incrementally "add" changes to the
+   index before using the 'commit' command (Note: even modified files
+   must be "added");
 
-2. by using 'git rm' to remove files from the working tree
+2. by using linkgit:git-rm[1] to remove files from the working tree
    and the index, again before using the 'commit' command;
 
 3. by listing files as arguments to the 'commit' command
@@ -349,6 +352,18 @@ changes to tracked files.
 :git-commit: 1
 include::date-formats.txt[]
 
+NOTES
+-----
+If a branch is associated with the working tree, 'HEAD' points to this
+branch. When a new commit is created, the branch is updated to point
+to the new commit. As a result, resolving 'HEAD' still gives the new
+commit.
+
+If no branch is associated with the working tree (i.e. "detached HEAD"
+as described in linkgit:git-checkout[1]), 'HEAD' records the object
+name of the previous commit directly. When a new commit is created, it
+will be updated to point to the new commit.
+
 EXAMPLES
 --------
 When recording your own work, the contents of modified files in
-- 
2.20.1.682.gd5861c6d90
Eric Sunshine· Jan 31, 2019, 10:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH] git-commit.txt: better description what it does

On Thu, Jan 31, 2019 at 4:21 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 19 quoted lines
> The description of git-commit jumps right into the commit content, which
> is important, but it fails to mention how the commit is "added" to the
> repository. Update the first paragraph saying a bit more about branch
> update to fill this gap.
>
> While at there, add a couple linkgit references when the command is
> first mentioned.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
> diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
> @@ -17,16 +17,19 @@ SYNOPSIS
>  DESCRIPTION
>  -----------
> +Creates a new commit containing the current contents of the index with
> +a log message from the user describing the changes. The commit is the
> +direct child of the tip of the current branch. The branch is updated
> +to point to the new commit (unless no branch is associated with the
> +working tree, see "NOTES" section).
Okay. The information about the branch being updated makes sense.
Show 6 quoted lines
> +NOTES
> +-----
> +If a branch is associated with the working tree, 'HEAD' points to this
> +branch. When a new commit is created, the branch is updated to point
> +to the new commit. As a result, resolving 'HEAD' still gives the new
> +commit.

I'm not sure I understand the purpose of the final sentence about HEAD "still resolving" when you were, just before that, talking about the branch.

> +If no branch is associated with the working tree (i.e. "detached HEAD"
> +as described in linkgit:git-checkout[1]), 'HEAD' records the object
> +name of the previous commit directly. When a new commit is created, it
> +will be updated to point to the new commit.

I'm having a hard time figuring out what these two paragraphs together want to say. I _think_ they want to say that HEAD is updated automatically to point at the latest commit, and that if a branch points at HEAD, then the branch is is updated along with HEAD, otherwise if no branch, then it's a "detached HEAD".

Duy Nguyen· Jan 31, 2019, 11:04 UTC · re: Eric Sunshine · lore

Re: [PATCH] git-commit.txt: better description what it does

On Thu, Jan 31, 2019 at 5:49 PM Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 45 quoted lines
>
> On Thu, Jan 31, 2019 at 4:21 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> > The description of git-commit jumps right into the commit content, which
> > is important, but it fails to mention how the commit is "added" to the
> > repository. Update the first paragraph saying a bit more about branch
> > update to fill this gap.
> >
> > While at there, add a couple linkgit references when the command is
> > first mentioned.
> >
> > Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> > ---
> > diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
> > @@ -17,16 +17,19 @@ SYNOPSIS
> >  DESCRIPTION
> >  -----------
> > +Creates a new commit containing the current contents of the index with
> > +a log message from the user describing the changes. The commit is the
> > +direct child of the tip of the current branch. The branch is updated
> > +to point to the new commit (unless no branch is associated with the
> > +working tree, see "NOTES" section).
>
> Okay. The information about the branch being updated makes sense.
>
> > +NOTES
> > +-----
> > +If a branch is associated with the working tree, 'HEAD' points to this
> > +branch. When a new commit is created, the branch is updated to point
> > +to the new commit. As a result, resolving 'HEAD' still gives the new
> > +commit.
>
> I'm not sure I understand the purpose of the final sentence about HEAD
> "still resolving" when you were, just before that, talking about the
> branch.
>
> > +If no branch is associated with the working tree (i.e. "detached HEAD"
> > +as described in linkgit:git-checkout[1]), 'HEAD' records the object
> > +name of the previous commit directly. When a new commit is created, it
> > +will be updated to point to the new commit.
>
> I'm having a hard time figuring out what these two paragraphs together
> want to say. I _think_ they want to say that HEAD is updated
> automatically to point at the latest commit, and that if a branch
> points at HEAD, then the branch is is updated along with HEAD,
> otherwise if no branch, then it's a "detached HEAD".

I felt some more explanation was needed when I mentioned about updating current branch in the description, but then what happens when there's no branch (aka detached HEAD)? There is still some updates. If you consider HEAD a branch, then it's ok, but otherwise something is missing. But perhaps this is the kind of details that could be left out?

-- 
Duy
Eric Sunshine· Feb 1, 2019, 00:21 UTC · re: Duy Nguyen · lore

Re: [PATCH] git-commit.txt: better description what it does

On Thu, Jan 31, 2019 at 6:04 AM Duy Nguyen <pclouds@gmail.com> wrote:
Show 26 quoted lines
> On Thu, Jan 31, 2019 at 5:49 PM Eric Sunshine <sunshine@sunshineco.com> wrote:
> > On Thu, Jan 31, 2019 at 4:21 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> > > +NOTES
> > > +-----
> > > +If a branch is associated with the working tree, 'HEAD' points to this
> > > +branch. When a new commit is created, the branch is updated to point
> > > +to the new commit. As a result, resolving 'HEAD' still gives the new
> > > +commit.
> > > +
> > > +If no branch is associated with the working tree (i.e. "detached HEAD"
> > > +as described in linkgit:git-checkout[1]), 'HEAD' records the object
> > > +name of the previous commit directly. When a new commit is created, it
> > > +will be updated to point to the new commit.
> >
> > I'm having a hard time figuring out what these two paragraphs together
> > want to say. I _think_ they want to say that HEAD is updated
> > automatically to point at the latest commit, and that if a branch
> > points at HEAD, then the branch is is updated along with HEAD,
> > otherwise if no branch, then it's a "detached HEAD".
>
> I felt some more explanation was needed when I mentioned about
> updating current branch in the description, but then what happens when
> there's no branch (aka detached HEAD)? There is still some updates. If
> you consider HEAD a branch, then it's ok, but otherwise something is
> missing. But perhaps this is the kind of details that could be left
> out?

It could be left out or incorporated directly into the DESCRIPTION section with a link to the much more thorough discussion of detached HEAD in git-checkout documentation. Here's my attempt at rewriting DESCRIPTION (and dropping NOTES altogether):

    Create a new commit containing the current contents of the index
    and the given log message describing the changes. The new commit
    is a direct child of HEAD, usually the tip of the current branch,
    and the branch is updated to point to it (unless no branch is
    associated with the working tree, in which case HEAD is "detached"
    as described in linkgit:git-checkout[1]).
(I also streamlined the above a bit from what you had composed.)
Nguyễn Thái Ngọc Duy· Feb 1, 2019, 10:09 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2] git-commit.txt: better description what it does

The description of git-commit jumps right into the commit content, which is important, but it fails to mention how the commit is "added" to the repository. Update the first paragraph saying a bit more about branch update to fill this gap.

While at there, add a couple linkgit references when the command is first mentioned.

Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 NOTES section is gone. First paragraph updated.
 Documentation/git-commit.txt | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
Show changes to Documentation/git-commit.txt +11 −7
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index f970a43422..a85c2c2a4c 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -17,16 +17,20 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Stores the current contents of the index in a new commit along
-with a log message from the user describing the changes.
+Create a new commit containing the current contents of the index and
+the given log message describing the changes. The new commit is a
+direct child of HEAD, usually the tip of the current branch, and the
+branch is updated to point to it (unless no branch is associated with
+the working tree, in which case HEAD is "detached" as described in
+linkgit:git-checkout[1]).
 
-The content to be added can be specified in several ways:
+The content to be committed can be specified in several ways:
 
-1. by using 'git add' to incrementally "add" changes to the
-   index before using the 'commit' command (Note: even modified
-   files must be "added");
+1. by using linkgit:git-add[1] to incrementally "add" changes to the
+   index before using the 'commit' command (Note: even modified files
+   must be "added");
 
-2. by using 'git rm' to remove files from the working tree
+2. by using linkgit:git-rm[1] to remove files from the working tree
    and the index, again before using the 'commit' command;
 
 3. by listing files as arguments to the 'commit' command
-- 
2.20.1.682.gd5861c6d90
Junio C Hamano· Feb 4, 2019, 17:59 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v2] git-commit.txt: better description what it does

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
Show 12 quoted lines
> The description of git-commit jumps right into the commit content, which
> is important, but it fails to mention how the commit is "added" to the
> repository. Update the first paragraph saying a bit more about branch
> update to fill this gap.
>
> While at there, add a couple linkgit references when the command is
> first mentioned.
>
> Helped-by: Eric Sunshine <sunshine@sunshineco.com>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  NOTES section is gone. First paragraph updated.

I haven't been following the discussion in the previous round, but this looks like a sensible change. Thanks, both.

Show 36 quoted lines
>
>  Documentation/git-commit.txt | 18 +++++++++++-------
>  1 file changed, 11 insertions(+), 7 deletions(-)
>
> diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
> index f970a43422..a85c2c2a4c 100644
> --- a/Documentation/git-commit.txt
> +++ b/Documentation/git-commit.txt
> @@ -17,16 +17,20 @@ SYNOPSIS
>  
>  DESCRIPTION
>  -----------
> -Stores the current contents of the index in a new commit along
> -with a log message from the user describing the changes.
> +Create a new commit containing the current contents of the index and
> +the given log message describing the changes. The new commit is a
> +direct child of HEAD, usually the tip of the current branch, and the
> +branch is updated to point to it (unless no branch is associated with
> +the working tree, in which case HEAD is "detached" as described in
> +linkgit:git-checkout[1]).
>  
> -The content to be added can be specified in several ways:
> +The content to be committed can be specified in several ways:
>  
> -1. by using 'git add' to incrementally "add" changes to the
> -   index before using the 'commit' command (Note: even modified
> -   files must be "added");
> +1. by using linkgit:git-add[1] to incrementally "add" changes to the
> +   index before using the 'commit' command (Note: even modified files
> +   must be "added");
>  
> -2. by using 'git rm' to remove files from the working tree
> +2. by using linkgit:git-rm[1] to remove files from the working tree
>     and the index, again before using the 'commit' command;
>  
>  3. by listing files as arguments to the 'commit' command
SZEDER Gábor· Jan 31, 2019, 13:06 UTC · re: Duy Nguyen · lore

Re: [PATCH 08/19] checkout: split part of it to new command switch

On Thu, Jan 31, 2019 at 12:29:01PM +0700, Duy Nguyen wrote:
Show 41 quoted lines
> > > +EXAMPLES
> > > +To check out commit "HEAD~3" for temporary inspection or experiment
> > > +without creating a new branch:
> > > +
> > > +------------
> > > +$ git switch --detach HEAD~3
> > > +HEAD is now at 9fc9555312 Merge branch 'cc/shared-index-permbits'
> > > +------------
> > > +
> > > +If it turns out whatever you have done is worth keeping, you can
> > > +always create a new name for it (without switching away):
> > > +
> > > +------------
> > > +$ git switch -c good-surprises
> > > +------------
> >
> > I had to go re-read git-branch documentation to understand why you
> > recommended "git switch -c <new-branch>" (or even why this mode
> > exists) over plain "git branch <new-branch>". I wonder if the
> > difference between the two deserves a bit more discussion here and
> > above in the description of the -c/-C options.
> 
> There is this bit in git-branch, will that help?
> 
> -- 8< --
> NOTES
> -----
> 
> If you are creating a branch that you want to checkout immediately, it is
> easier to use the "git switch" command with its `-c` option to create
> a branch and check it out with a single command.
> -- 8< --
> 
> Otherwise any suggestions are welcome, I guess I could rephrase it a bit like
> 
> -c <new_branch>::
> --create <new_branch>::
>         Create a new branch named <new_branch> starting at
>         <start_point> before switching to the branch. This is similar
>         to creating a new branch with linkgit:git-branch[1] except
>         that `git branch` does not switch branches.

I think the second sentence, in particular the "except..." part, in this description goes backwards by explaining how 'git branch' differs from this. I think the most to-the-point explanation instead of that second sentence would be something like this:

  This is a shorthand for 'git branch <new-branch> [<start-point>] &&
  git switch <new-branch>'.
SZEDER Gábor· Jan 31, 2019, 13:09 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH 08/19] checkout: split part of it to new command switch

On Wed, Jan 30, 2019 at 04:48:20PM +0700, Nguyễn Thái Ngọc Duy wrote:
> diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
> new file mode 100644
> index 0000000000..953c9747b2
> +--orphan <new_branch>::
> +	Create a new 'orphan' branch, named <new_branch>, started from
> +	<start_point> and switch to it. See explanation of the same
> +	option in linkgit:git-checkout[1] for details.

Copy-paste error? An orphan branch, by definition, doesn't have a starting point.

Duy Nguyen· Feb 8, 2019, 08:46 UTC · re: SZEDER Gábor · lore

Re: [PATCH 08/19] checkout: split part of it to new command switch

On Thu, Jan 31, 2019 at 8:09 PM SZEDER Gábor <szeder.dev@gmail.com> wrote:
Show 13 quoted lines
>
> On Wed, Jan 30, 2019 at 04:48:20PM +0700, Nguyễn Thái Ngọc Duy wrote:
> > diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
> > new file mode 100644
> > index 0000000000..953c9747b2
>
> > +--orphan <new_branch>::
> > +     Create a new 'orphan' branch, named <new_branch>, started from
> > +     <start_point> and switch to it. See explanation of the same
> > +     option in linkgit:git-checkout[1] for details.
>
> Copy-paste error?  An orphan branch, by definition, doesn't have a
> starting point.

It does not have any parents, true. The "started from <start-point>" is not super clear on this, but git-checkout.txt describes more and says that it's essentially "git checkout <start_point>" then make an orphan branch based on HEAD^{tree}.

-- 
Duy
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 09/19] switch: better names for -b and -B

The shortcut of these options do not make much sense when used with switch. And their descriptions are also tied to checkout out. Move -b/-B to cmd_checkout() and new -c/-C with the same functionality in cmd_switch_branch()

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 32 +++++++++++++++++++++-----------
 1 file changed, 21 insertions(+), 11 deletions(-)
Show changes to builtin/checkout.c +21 −11
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2ac53b4302..2251883a88 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1353,14 +1353,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"),
@@ -1556,15 +1552,22 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
 	struct checkout_opts opts;
-	struct option *options = NULL;
+	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_END()
+	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
 
-	options = parse_options_dup(options);
+	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
-	options = add_switch_branch_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,
@@ -1577,14 +1580,21 @@ 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 new branch")),
+		OPT_END()
+	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
 
-	options = parse_options_dup(options);
+	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-	options = add_switch_branch_options(&opts, options);
+	options = add_common_switch_branch_options(&opts, options);
 
 	ret = checkout_main(argc, argv, prefix, &opts,
 			    options, switch_branch_usage);
-- 
2.20.1.682.gd5861c6d90
Eric Sunshine· Jan 31, 2019, 07:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH 09/19] switch: better names for -b and -B

On Wed, Jan 30, 2019 at 4:49 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 17 quoted lines
> The shortcut of these options do not make much sense when used with
> switch. And their descriptions are also tied to checkout
> out. Move -b/-B to cmd_checkout() and new -c/-C with the same
> functionality in cmd_switch_branch()
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> @@ -1353,14 +1353,10 @@ static struct option *add_common_options(struct checkout_opts *opts,
> @@ -1556,15 +1552,22 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
>  int cmd_checkout(int argc, const char **argv, const char *prefix)
>  {
> +       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")),
Note wording for -B and compare with...
Show 6 quoted lines
> @@ -1577,14 +1580,21 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
> +       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 new branch")),

wording for -C which talks about "new branch" rather than just "branch" of -B. (Dropping "new" from the latter makes it read a bit better, in my opinion, and would make it consistent with -B wording.)

> +               OPT_END()
> +       };
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 10/19] switch: remove -l

This option is ancient. Nowadays reflog is enabled by default and automatically created for new branches. Keep it in git-checkout only.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/checkout.c +1 −1
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2251883a88..db9a2692db 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1357,7 +1357,6 @@ static struct option *add_common_switch_branch_options(
 	struct checkout_opts *opts, struct option *prevopts)
 {
 	struct option options[] = {
-		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),
@@ -1558,6 +1557,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			   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;
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 11/19] switch: stop accepting pathspec

This command is about switching branch (or creating a new one) and should not accept pathspec. This helps simplify ambiguation handling. The other two ("git checkout" and "git restore") of course do accept pathspec as before.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)
Show changes to builtin/checkout.c +15 −5
diff --git a/builtin/checkout.c b/builtin/checkout.c
index db9a2692db..7906e07352 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -52,6 +52,7 @@ struct checkout_opts {
 	int count_checkout_paths;
 	int overlay_mode;
 	int no_dwim_new_local_branch;
+	int accept_pathspec;
 
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
@@ -1162,10 +1163,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;
 		}
@@ -1199,11 +1206,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) {
@@ -1248,7 +1256,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--;
@@ -1564,6 +1572,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
+	opts.accept_pathspec = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1591,6 +1600,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
+	opts.accept_pathspec = 0;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 12/19] switch: reject "do nothing" case

"git checkout" can be executed without any arguments. What it does is not exactly great: it switches from HEAD to HEAD and shows worktree modification as a side effect.

Make switch reject this case. Just use "git status" if you want that side effect. For switch, you have to either

- really switch a branch
- (explicitly) detach from the current branch
- create a new branch
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 9 +++++++++
 1 file changed, 9 insertions(+)
Show changes to builtin/checkout.c +9 −0
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 7906e07352..9a58fccd71 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -53,6 +53,7 @@ struct checkout_opts {
 	int overlay_mode;
 	int no_dwim_new_local_branch;
 	int accept_pathspec;
+	int switch_branch_doing_nothing_is_ok;
 
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
@@ -1319,6 +1320,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(_("nothing to do"));
+
 	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
 	    !opts->ignore_other_worktrees) {
 		int flag;
@@ -1572,6 +1579,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	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);
@@ -1601,6 +1609,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	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);
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 13/19] switch: only allow explicit detached HEAD

"git checkout <commit>" will checkout the commit in question and detach HEAD from the current branch. It is naturally a right thing to do once you get git references. But detached HEAD is a scary concept to new users because we show a lot of warnings and stuff, and it could be hard to get out of (until you know better).

To keep switch a bit more friendly to new users, we only allow entering detached HEAD mode when --detach is given. "git switch" must take a branch (unless you create a new branch, then of course switch can take any commit-ish)

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 11 +++++++++++
 1 file changed, 11 insertions(+)
Show changes to builtin/checkout.c +11 −0
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 9a58fccd71..69df3a1547 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -44,6 +44,7 @@ struct checkout_opts {
 	int merge;
 	int force;
 	int force_detach;
+	int implicit_detach;
 	int writeout_stage;
 	int overwrite_ignore;
 	int ignore_skipworktree;
@@ -1326,6 +1327,14 @@ static int checkout_branch(struct checkout_opts *opts,
 	    !opts->force_detach)
 		die(_("nothing to do"));
 
+	if (!opts->implicit_detach &&
+	    !opts->force_detach &&
+	    !opts->new_branch &&
+	    !opts->new_branch_force &&
+	    new_branch_info->name &&
+	    !new_branch_info->path)
+		die(_("a branch is expected, got %s"), new_branch_info->name);
+
 	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
 	    !opts->ignore_other_worktrees) {
 		int flag;
@@ -1581,6 +1590,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.no_dwim_new_local_branch = 0;
 	opts.switch_branch_doing_nothing_is_ok = 1;
 	opts.accept_pathspec = 1;
+	opts.implicit_detach = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1610,6 +1620,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	opts.no_dwim_new_local_branch = 0;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
+	opts.implicit_detach = 0;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 14/19] switch: add short option for --detach

"git checkout" automatically detaches branches and --detach is not that useful (--no-detach is more likely). But for "switch", you may want to use it more often once you're used to detached HEAD. This of course adds -d to git-checkout but it does not harm (yet?) to do it.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/checkout.c +1 −1
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 69df3a1547..49c7ac211e 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1381,7 +1381,7 @@ static struct option *add_common_switch_branch_options(
 	struct checkout_opts *opts, struct option *prevopts)
 {
 	struct option options[] = {
-		OPT_BOOL(0, "detach", &opts->force_detach, N_("detach HEAD at named commit")),
+		OPT_BOOL('d', "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),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 15/19] switch: no implicit dwim, use --guess to dwim

Similar to automatic detach, this behavior could be confusing because it can sometimes create a new branch without a user asking it to, especially when the user is still not aware about this feature.

In the future, perhaps we could have a config key to disable these safety nets and let 'switch' do automatic detach or dwim again. But that will be opt-in after the user knows what is what. For now give a short option if you want to use it often.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt | 37 ++++++++++++++++++++--------------
 builtin/checkout.c             | 16 +++++++--------
 2 files changed, 30 insertions(+), 23 deletions(-)
Show changes to 2 files +30 −22

Documentation/git-checkout.txt, builtin/checkout.c

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 31c6cbef19..bcb7822c27 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -31,22 +31,13 @@ branch.
 	<branch>.
 +
 If <branch> is not found but there does exist a tracking branch in
-exactly one remote (call it <remote>) with a matching name, treat as
-equivalent to
+exactly one remote (call it <remote>) with a matching name and
+--no-guess is not specified, treat as equivalent to
 +
 ------------
 $ git checkout -b <branch> --track <remote>/<branch>
 ------------
 +
-If the branch exists in multiple remotes and one of them is named by
-the `checkout.defaultRemote` configuration variable, we'll use that
-one for the purposes of disambiguation, even if the `<branch>` isn't
-unique across all remotes. Set it to
-e.g. `checkout.defaultRemote=origin` to always checkout remote
-branches from there if `<branch>` is ambiguous but exists on the
-'origin' remote. See also `checkout.defaultRemote` in
-linkgit:git-config[1].
-+
 You could omit <branch>, in which case the command degenerates to
 "check out the current branch", which is a glorified no-op with
 rather expensive side-effects to show only the tracking information,
@@ -182,6 +173,26 @@ explicitly give a name with `-b` in such a case.
 	Do not set up "upstream" configuration, even if the
 	branch.autoSetupMerge configuration variable is true.
 
+--[no-]guess::
+	If <branch> is not found but there does exist a tracking
+	branch in exactly one remote (call it <remote>) with a
+	matching name, treat as equivalent to
++
+------------
+$ git checkout -b <branch> --track <remote>/<branch>
+------------
++
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to
+e.g. `checkout.defaultRemote=origin` to always checkout remote
+branches from there if `<branch>` is ambiguous but exists on the
+'origin' remote. See also `checkout.defaultRemote` in
+linkgit:git-config[1].
++
+Use `--no-guess` to disable this.
+
 -l::
 	Create the new branch's reflog; see linkgit:git-branch[1] for
 	details.
@@ -284,10 +295,6 @@ Note that this option uses the no overlay mode by default (see also
 	Just like linkgit:git-submodule[1], this will detach the
 	submodules HEAD.
 
---no-guess::
-	Do not attempt to create a branch if a remote tracking branch
-	of the same name exists.
-
 --[no-]overlay::
 	In the default overlay mode, `git checkout` never
 	removes files from the index or the working tree.  When
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 49c7ac211e..cdd07f6aba 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -52,7 +52,7 @@ struct checkout_opts {
 	int show_progress;
 	int count_checkout_paths;
 	int overlay_mode;
-	int no_dwim_new_local_branch;
+	int dwim_new_local_branch;
 	int accept_pathspec;
 	int switch_branch_doing_nothing_is_ok;
 
@@ -1388,8 +1388,6 @@ static struct option *add_common_switch_branch_options(
 		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
-			 N_("second guess 'git checkout <no-such-branch>'")),
 		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
 		OPT_END()
@@ -1426,7 +1424,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 {
 	struct branch_info new_branch_info;
 	int dwim_remotes_matched = 0;
-	int dwim_new_local_branch;
 
 	memset(&new_branch_info, 0, sizeof(new_branch_info));
 	opts->overwrite_ignore = 1;
@@ -1441,7 +1438,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 	argc = parse_options(argc, argv, prefix, options, usagestr,
 			     PARSE_OPT_KEEP_DASHDASH);
 
-	dwim_new_local_branch = !opts->no_dwim_new_local_branch;
 	if (opts->show_progress < 0) {
 		if (opts->quiet)
 			opts->show_progress = 0;
@@ -1501,7 +1497,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 		struct object_id rev;
 		int dwim_ok =
 			!opts->patch_mode &&
-			dwim_new_local_branch &&
+			opts->dwim_new_local_branch &&
 			opts->track == BRANCH_TRACK_UNSPECIFIED &&
 			!opts->new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
@@ -1582,12 +1578,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		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, "guess", &opts.dwim_new_local_branch,
+			 N_("second guess 'git checkout <no-such-branch>' (default)")),
 		OPT_END()
 	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
-	opts.no_dwim_new_local_branch = 0;
+	opts.dwim_new_local_branch = 1;
 	opts.switch_branch_doing_nothing_is_ok = 1;
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
@@ -1612,12 +1610,14 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 			   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 new branch")),
+		OPT_BOOL('g', "guess", &opts.dwim_new_local_branch,
+			 N_("second guess 'git switch <no-such-branch>'")),
 		OPT_END()
 	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
-	opts.no_dwim_new_local_branch = 0;
+	opts.dwim_new_local_branch = 0;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
 	opts.implicit_detach = 0;
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 16/19] switch: no worktree status unless real branch switch happens

When we switch from one branch to another, it makes sense to show a summary of local changes since there could be conflicts, or some files left modified.... When switch is used solely for creating a new branch (and "switch" to the same commit) or detaching, we don't really need to show anything.

"git checkout" does it anyway for historical reasons. But we can start with a clean slate with switch and don't have to.

This essentially reverts fa655d8411 (checkout: optimize "git checkout -b <new_branch>" - 2018-08-16) and make it default for switch, but also for -B and --detach. Users of big repos are encouraged to move to switch.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/config/checkout.txt |   8 --
 builtin/checkout.c                | 127 +++---------------------------
 t/t1090-sparse-checkout-scope.sh  |  14 ----
 3 files changed, 9 insertions(+), 140 deletions(-)
Show changes to 3 files +9 −140

Documentation/config/checkout.txt, builtin/checkout.c, t/t1090-sparse-checkout-scope.sh

diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt
index fc211eca58..2fe24049fe 100644
--- a/Documentation/config/checkout.txt
+++ b/Documentation/config/checkout.txt
@@ -17,14 +17,6 @@ and by linkgit:git-worktree[1] when 'git worktree add' refers to a
 remote branch. This setting might be used for other checkout-like
 commands or functionality in the future.
 
-checkout.optimizeNewBranch::
-	Optimizes the performance of "git checkout -b <new_branch>" when
-	using sparse-checkout.  When set to true, git will not update the
-	repo based on the current sparse-checkout settings.  This means it
-	will not update the skip-worktree bit in the index nor add/remove
-	files in the working directory to reflect the current sparse checkout
-	settings nor will it show the local changes.
-
 checkout.overlayMode::
 	In the default overlay mode, `git checkout` never
 	removes files from the index or the working tree.  When
diff --git a/builtin/checkout.c b/builtin/checkout.c
index cdd07f6aba..8b4aabd6db 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -25,8 +25,6 @@
 #include "submodule.h"
 #include "advice.h"
 
-static int checkout_optimize_new_branch;
-
 static const char * const checkout_usage[] = {
 	N_("git checkout [<options>] <branch>"),
 	N_("git checkout [<options>] [<branch>] -- <file>..."),
@@ -55,11 +53,7 @@ struct checkout_opts {
 	int dwim_new_local_branch;
 	int accept_pathspec;
 	int switch_branch_doing_nothing_is_ok;
-
-	/*
-	 * If new checkout options are added, skip_merge_working_tree
-	 * should be updated accordingly.
-	 */
+	int only_merge_on_switching_branches;
 
 	const char *new_branch;
 	const char *new_branch_force;
@@ -556,104 +550,6 @@ static void setup_branch_path(struct branch_info *branch)
 	branch->path = strbuf_detach(&buf, NULL);
 }
 
-/*
- * Skip merging the trees, updating the index and working directory if and
- * only if we are creating a new branch via "git checkout -b <new_branch>."
- */
-static int skip_merge_working_tree(const struct checkout_opts *opts,
-	const struct branch_info *old_branch_info,
-	const struct branch_info *new_branch_info)
-{
-	/*
-	 * Do the merge if sparse checkout is on and the user has not opted in
-	 * to the optimized behavior
-	 */
-	if (core_apply_sparse_checkout && !checkout_optimize_new_branch)
-		return 0;
-
-	/*
-	 * We must do the merge if we are actually moving to a new commit.
-	 */
-	if (!old_branch_info->commit || !new_branch_info->commit ||
-		!oideq(&old_branch_info->commit->object.oid,
-		       &new_branch_info->commit->object.oid))
-		return 0;
-
-	/*
-	 * opts->patch_mode cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * opts->quiet only impacts output so doesn't require a merge
-	 */
-
-	/*
-	 * Honor the explicit request for a three-way merge or to throw away
-	 * local changes
-	 */
-	if (opts->merge || opts->force)
-		return 0;
-
-	/*
-	 * --detach is documented as "updating the index and the files in the
-	 * working tree" but this optimization skips those steps so fall through
-	 * to the regular code path.
-	 */
-	if (opts->force_detach)
-		return 0;
-
-	/*
-	 * opts->writeout_stage cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * Honor the explicit ignore requests
-	 */
-	if (!opts->overwrite_ignore || opts->ignore_skipworktree ||
-		opts->ignore_other_worktrees)
-		return 0;
-
-	/*
-	 * opts->show_progress only impacts output so doesn't require a merge
-	 */
-
-	/*
-	 * opts->overlay_mode cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * If we aren't creating a new branch any changes or updates will
-	 * happen in the existing branch.  Since that could only be updating
-	 * the index and working directory, we don't want to skip those steps
-	 * or we've defeated any purpose in running the command.
-	 */
-	if (!opts->new_branch)
-		return 0;
-
-	/*
-	 * new_branch_force is defined to "create/reset and checkout a branch"
-	 * so needs to go through the merge to do the reset
-	 */
-	if (opts->new_branch_force)
-		return 0;
-
-	/*
-	 * A new orphaned branch requrires the index and the working tree to be
-	 * adjusted to <start_point>
-	 */
-	if (opts->new_orphan_branch)
-		return 0;
-
-	/*
-	 * Remaining variables are not checkout options but used to track state
-	 */
-
-	return 1;
-}
-
 static int merge_working_tree(const struct checkout_opts *opts,
 			      struct branch_info *old_branch_info,
 			      struct branch_info *new_branch_info,
@@ -1011,6 +907,8 @@ static int switch_branches(const struct checkout_opts *opts,
 	void *path_to_free;
 	struct object_id rev;
 	int flag, writeout_error = 0;
+	int do_merge = 1;
+
 	memset(&old_branch_info, 0, sizeof(old_branch_info));
 	old_branch_info.path = path_to_free = resolve_refdup("HEAD", 0, &rev, &flag);
 	if (old_branch_info.path)
@@ -1027,16 +925,12 @@ static int switch_branches(const struct checkout_opts *opts,
 		if (!new_branch_info->commit)
 			die(_("You are on a branch yet to be born"));
 		parse_commit_or_die(new_branch_info->commit);
+
+		if (opts->only_merge_on_switching_branches)
+			do_merge = 0;
 	}
 
-	/* optimize the "checkout -b <new_branch> path */
-	if (skip_merge_working_tree(opts, &old_branch_info, new_branch_info)) {
-		if (!checkout_optimize_new_branch && !opts->quiet) {
-			if (read_cache_preload(NULL) < 0)
-				return error(_("index file corrupt"));
-			show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
-		}
-	} else {
+	if (do_merge) {
 		ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
 		if (ret) {
 			free(path_to_free);
@@ -1058,11 +952,6 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
 {
 	struct checkout_opts *opts = cb;
 
-	if (!strcmp(var, "checkout.optimizenewbranch")) {
-		checkout_optimize_new_branch = git_config_bool(var, value);
-		return 0;
-	}
-
 	if (!strcmp(var, "checkout.overlaymode")) {
 		opts->overlay_mode = git_config_bool(var, value);
 		return 0;
@@ -1587,6 +1476,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	memset(&opts, 0, sizeof(opts));
 	opts.dwim_new_local_branch = 1;
 	opts.switch_branch_doing_nothing_is_ok = 1;
+	opts.only_merge_on_switching_branches = 0;
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
 
@@ -1620,6 +1510,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	opts.dwim_new_local_branch = 0;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
+	opts.only_merge_on_switching_branches = 1;
 	opts.implicit_detach = 0;
 
 	options = parse_options_dup(switch_options);
diff --git a/t/t1090-sparse-checkout-scope.sh b/t/t1090-sparse-checkout-scope.sh
index 090b7fc3d3..40cc004326 100755
--- a/t/t1090-sparse-checkout-scope.sh
+++ b/t/t1090-sparse-checkout-scope.sh
@@ -31,20 +31,6 @@ test_expect_success 'perform sparse checkout of master' '
 	test_path_is_file c
 '
 
-test_expect_success 'checkout -b checkout.optimizeNewBranch interaction' '
-	cp .git/info/sparse-checkout .git/info/sparse-checkout.bak &&
-	test_when_finished "
-		mv -f .git/info/sparse-checkout.bak .git/info/sparse-checkout
-		git checkout master
-	" &&
-	echo "/b" >>.git/info/sparse-checkout &&
-	test "$(git ls-files -t b)" = "S b" &&
-	git -c checkout.optimizeNewBranch=true checkout -b fast &&
-	test "$(git ls-files -t b)" = "S b" &&
-	git checkout -b slow &&
-	test "$(git ls-files -t b)" = "H b"
-'
-
 test_expect_success 'merge feature branch into sparse checkout of master' '
 	git merge feature &&
 	test_path_is_file a &&
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 17/19] t: add tests for switch

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 t/t2060-switch.sh | 91 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 91 insertions(+)
 create mode 100755 t/t2060-switch.sh
Show changes to t/t2060-switch.sh +91 −0
diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
new file mode 100755
index 0000000000..252f78c79e
--- /dev/null
+++ b/t/t2060-switch.sh
@@ -0,0 +1,91 @@
+#!/bin/sh
+
+test_description='switch basic functionality'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit first &&
+	git branch first-branch &&
+	test_commit second &&
+	test_commit third &&
+	git remote add origin nohost:/nopath &&
+	git update-ref refs/remotes/origin/foo first-branch
+'
+
+test_expect_success 'switch branch no arguments' '
+	test_must_fail git switch
+'
+
+test_expect_success 'switch branch' '
+	git switch first-branch &&
+	test_path_is_missing second.t
+'
+
+test_expect_success 'switch to a commit' '
+	test_must_fail git switch master^{commit}
+'
+
+test_expect_success 'switch and detach' '
+	test_when_finished git switch master &&
+	git switch --detach master^{commit} &&
+	test_must_fail git symbolic-ref HEAD
+'
+
+test_expect_success 'switch and detach current branch' '
+	test_when_finished git switch master &&
+	git switch master &&
+	git switch --detach &&
+	test_must_fail git symbolic-ref HEAD
+'
+
+test_expect_success 'switch and create branch' '
+	test_when_finished git switch master &&
+	git switch -c temp master^ &&
+	git rev-parse master^ >expected &&
+	git rev-parse refs/heads/temp >actual &&
+	test_cmp expected actual &&
+	echo refs/heads/temp >expected-branch &&
+	git symbolic-ref HEAD >actual-branch &&
+	test_cmp expected-branch actual-branch
+'
+
+test_expect_success 'force create branch from HEAD' '
+	test_when_finished git switch master &&
+	git switch --detach master &&
+	git switch -C temp &&
+	git rev-parse master >expected &&
+	git rev-parse refs/heads/temp >actual &&
+	test_cmp expected actual &&
+	echo refs/heads/temp >expected-branch &&
+	git symbolic-ref HEAD >actual-branch &&
+	test_cmp expected-branch actual-branch
+'
+
+test_expect_success 'new orphan branch' '
+	test_when_finished git switch master &&
+	git switch --orphan new-orphan master^ &&
+	test_commit orphan &&
+	git cat-file commit refs/heads/new-orphan >commit &&
+	! grep ^parent commit
+'
+
+test_expect_success 'switching ignores file of same branch name' '
+	test_when_finished git switch master &&
+	: >first-branch &&
+	git switch first-branch &&
+	echo refs/heads/first-branch >expected &&
+	git symbolic-ref HEAD >actual &&
+	test_commit expected actual
+'
+
+test_expect_success 'guess and create branch ' '
+	test_when_finished git switch master &&
+	test_must_fail git switch foo &&
+	git switch --guess foo &&
+	echo refs/heads/foo >expected &&
+	git symbolic-ref HEAD >actual &&
+	test_cmp expected actual
+'
+
+test_done
-- 
2.20.1.682.gd5861c6d90
Eric Sunshine· Jan 31, 2019, 07:18 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH 17/19] t: add tests for switch

On Wed, Jan 30, 2019 at 4:50 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 10 quoted lines
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
> diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
> @@ -0,0 +1,91 @@
> +test_expect_success 'switch and create branch' '
> +       test_when_finished git switch master &&
> +       git switch -c temp master^ &&
> +       git rev-parse master^ >expected &&
> +       git rev-parse refs/heads/temp >actual &&
> +       test_cmp expected actual &&
The above three lines could be replaced with test_cmp_rev()?
Show 12 quoted lines
> +       echo refs/heads/temp >expected-branch &&
> +       git symbolic-ref HEAD >actual-branch &&
> +       test_cmp expected-branch actual-branch
> +'
> +
> +test_expect_success 'force create branch from HEAD' '
> +       test_when_finished git switch master &&
> +       git switch --detach master &&
> +       git switch -C temp &&
> +       git rev-parse master >expected &&
> +       git rev-parse refs/heads/temp >actual &&
> +       test_cmp expected actual &&
Ditto: test_cmp_rev()
> +       echo refs/heads/temp >expected-branch &&
> +       git symbolic-ref HEAD >actual-branch &&
> +       test_cmp expected-branch actual-branch
> +'
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 18/19] completion: support switch

Completion support for --guess could be made better. If no --detach is given, we should only provide a list of refs/heads/* and dwim ones, not the entire ref space. But I still can't penetrate that __git_refs() function yet.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 contrib/completion/git-completion.bash | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)
Show changes to contrib/completion/git-completion.bash +26 −0
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 499e56f83d..891abb72d7 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -2126,6 +2126,32 @@ _git_status ()
 	__git_complete_index_file "$complete_opt"
 }
 
+_git_switch ()
+{
+	case "$cur" in
+	--conflict=*)
+		__gitcomp "diff3 merge" "" "${cur##--conflict=}"
+		;;
+	--*)
+		__gitcomp_builtin switch
+		;;
+	*)
+		# check if ---guess was specified to enable DWIM mode
+		local track_opt= only_local_ref=n
+		if [ -n "$(__git_find_on_cmdline "-g --guess")" ]; then
+			track_opt='--track'
+		elif [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then
+			only_local_ref=y
+		fi
+		if [ $only_local_ref = y ]; then
+			__gitcomp_direct "$(__git_heads "" "$cur" " ")"
+		else
+			__git_complete_refs $track_opt
+		fi
+		;;
+	esac
+}
+
 __git_config_get_set_variables ()
 {
 	local prevword word config_file= c=$cword
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Jan 30, 2019, 09:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 19/19] doc: promote "git switch"

The new command "git switch" is added to avoid the confusion of one-command-do-all "git checkout" for new users. They are also helpful to avoid ambiguation context.

For these reasons, promote it everywhere possible. This includes documentation, suggestions/advice from other commands...

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-branch.txt           |  8 ++--
 Documentation/git-check-ref-format.txt |  3 +-
 Documentation/git-format-patch.txt     |  2 +-
 Documentation/git-merge-base.txt       |  2 +-
 Documentation/git-rebase.txt           |  2 +-
 Documentation/git-remote.txt           |  2 +-
 Documentation/git-rerere.txt           | 10 ++---
 Documentation/git-reset.txt            | 20 +++++-----
 Documentation/git-stash.txt            |  9 +++--
 Documentation/gitcore-tutorial.txt     | 18 ++++-----
 Documentation/giteveryday.txt          | 24 ++++++------
 Documentation/gittutorial.txt          |  4 +-
 Documentation/revisions.txt            |  2 +-
 Documentation/user-manual.txt          | 52 +++++++++++++-------------
 advice.c                               | 11 ++++--
 builtin/clone.c                        |  2 +-
 sha1-name.c                            |  2 +-
 t/t2020-checkout-detach.sh             |  4 +-
 18 files changed, 91 insertions(+), 86 deletions(-)
Show changes to 18 files +91 −86

Documentation/git-branch.txt, Documentation/git-check-ref-format.txt, Documentation/git-format-patch.txt, Documentation/git-merge-base.txt, Documentation/git-rebase.txt, Documentation/git-remote.txt, Documentation/git-rerere.txt, Documentation/git-reset.txt, Documentation/git-stash.txt, Documentation/gitcore-tutorial.txt, Documentation/giteveryday.txt, Documentation/gittutorial.txt, Documentation/revisions.txt, Documentation/user-manual.txt, advice.c, builtin/clone.c, sha1-name.c, t/t2020-checkout-detach.sh

diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index bf5316ffa9..94c1f24414 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -48,7 +48,7 @@ The command's second form creates a new branch head named <branchname>
 which points to the current `HEAD`, or <start-point> if given.
 
 Note that this will create the new branch, but it will not switch the
-working tree to it; use "git checkout <newbranch>" to switch to the
+working tree to it; use "git switch <newbranch>" to switch to the
 new branch.
 
 When a local branch is started off a remote-tracking branch, Git sets up the
@@ -194,7 +194,7 @@ This option is only applicable in non-verbose mode.
 +
 This behavior is the default when the start point is a remote-tracking branch.
 Set the branch.autoSetupMerge configuration variable to `false` if you
-want `git checkout` and `git branch` to always behave as if `--no-track`
+want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
 were given. Set it to `always` if you want this behavior when the
 start-point is either a local or remote-tracking branch.
 
@@ -293,7 +293,7 @@ Start development from a known tag::
 $ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6
 $ cd my2.6
 $ git branch my2.6.14 v2.6.14   <1>
-$ git checkout my2.6.14
+$ git switch my2.6.14
 ------------
 +
 <1> This step and the next one could be combined into a single step with
@@ -319,7 +319,7 @@ NOTES
 -----
 
 If you are creating a branch that you want to checkout immediately, it is
-easier to use the git checkout command with its `-b` option to create
+easier to use the "git switch" command with its `-c` option to create
 a branch and check it out with a single command.
 
 The options `--contains`, `--no-contains`, `--merged` and `--no-merged`
diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt
index d9de992585..ee6a4144fb 100644
--- a/Documentation/git-check-ref-format.txt
+++ b/Documentation/git-check-ref-format.txt
@@ -88,7 +88,8 @@ but it is explicitly forbidden at the beginning of a branch name).
 When run with `--branch` option in a repository, the input is first
 expanded for the ``previous checkout syntax''
 `@{-n}`.  For example, `@{-1}` is a way to refer the last thing that
-was checked out using "git checkout" operation. This option should be
+was checked out using "git switch" or "git checkout" operation.
+This option should be
 used by porcelains to accept this syntax anywhere a branch name is
 expected, so they can act as if you typed the branch name. As an
 exception note that, the ``previous checkout operation'' might result
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 27304428a1..3d42853529 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -421,7 +421,7 @@ One way to test if your MUA is set up correctly is:
 * Apply it:
 
     $ git fetch <project> master:test-apply
-    $ git checkout test-apply
+    $ git switch test-apply
     $ git reset --hard
     $ git am a.patch
 
diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt
index 9f07f4f6ed..261d5c1164 100644
--- a/Documentation/git-merge-base.txt
+++ b/Documentation/git-merge-base.txt
@@ -149,7 +149,7 @@ instead.
 Discussion on fork-point mode
 -----------------------------
 
-After working on the `topic` branch created with `git checkout -b
+After working on the `topic` branch created with `git switch -c
 topic origin/master`, the history of remote-tracking branch
 `origin/master` may have been rewound and rebuilt, leading to a
 history of this shape:
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 4dd5853d6e..420777c10b 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -17,7 +17,7 @@ SYNOPSIS
 DESCRIPTION
 -----------
 If <branch> is specified, 'git rebase' will perform an automatic
-`git checkout <branch>` before doing anything else.  Otherwise
+`git switch <branch>` before doing anything else.  Otherwise
 it remains on the current branch.
 
 If <upstream> is not specified, the upstream configured in
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index 0cad37fb81..9659abbf8e 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -230,7 +230,7 @@ $ git branch -r
   staging/master
   staging/staging-linus
   staging/staging-next
-$ git checkout -b staging staging/master
+$ git switch -c staging staging/master
 ...
 ------------
 
diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt
index df310d2a58..fe4434ad9d 100644
--- a/Documentation/git-rerere.txt
+++ b/Documentation/git-rerere.txt
@@ -91,7 +91,7 @@ For such a test, you need to merge master and topic somehow.
 One way to do it is to pull master into the topic branch:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 
               o---*---o---+ topic
@@ -113,10 +113,10 @@ the upstream might have been advanced since the test merge `+`,
 in which case the final commit graph would look like this:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 	$ ... work on both topic and master branches
-	$ git checkout master
+	$ git switch master
 	$ git merge topic
 
               o---*---o---+---o---o topic
@@ -136,11 +136,11 @@ merges, you could blow away the test merge, and keep building on
 top of the tip before the test merge:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 	$ git reset --hard HEAD^ ;# rewind the test merge
 	$ ... work on both topic and master branches
-	$ git checkout master
+	$ git switch master
 	$ git merge topic
 
               o---*---o-------o---o topic
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 9f69ae8b69..020ba838c9 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -149,9 +149,9 @@ See also the `--amend` option to linkgit:git-commit[1].
 Undo a commit, making it a topic branch::
 +
 ------------
-$ git branch topic/wip     <1>
-$ git reset --hard HEAD~3  <2>
-$ git checkout topic/wip   <3>
+$ git branch topic/wip          <1>
+$ git reset --hard HEAD~3       <2>
+$ git switch topic/wip          <3>
 ------------
 +
 <1> You have made some commits, but realize they were premature
@@ -232,13 +232,13 @@ working tree are not in any shape to be committed yet, but you
 need to get to the other branch for a quick bugfix.
 +
 ------------
-$ git checkout feature ;# you were working in "feature" branch and
-$ work work work       ;# got interrupted
+$ git switch feature  ;# you were working in "feature" branch and
+$ work work work      ;# got interrupted
 $ git commit -a -m "snapshot WIP"                 <1>
-$ git checkout master
+$ git switch master
 $ fix fix fix
 $ git commit ;# commit with real log
-$ git checkout feature
+$ git switch feature
 $ git reset --soft HEAD^ ;# go back to WIP state  <2>
 $ git reset                                       <3>
 ------------
@@ -279,18 +279,18 @@ reset it while keeping the changes in your working tree.
 +
 ------------
 $ git tag start
-$ git checkout -b branch1
+$ git switch -c branch1
 $ edit
 $ git commit ...                            <1>
 $ edit
-$ git checkout -b branch2                   <2>
+$ git switch -c branch2                     <2>
 $ git reset --keep start                    <3>
 ------------
 +
 <1> This commits your first edits in `branch1`.
 <2> In the ideal world, you could have realized that the earlier
     commit did not belong to the new topic when you created and switched
-    to `branch2` (i.e. `git checkout -b branch2 start`), but nobody is
+    to `branch2` (i.e. `git switch -c branch2 start`), but nobody is
     perfect.
 <3> But you can use `reset --keep` to remove the unwanted commit after
     you switched to `branch2`.
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 7ef8c47911..ebb6282db3 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -235,12 +235,12 @@ return to your original branch to make the emergency fix, like this:
 +
 ----------------------------------------------------------------
 # ... hack hack hack ...
-$ git checkout -b my_wip
+$ git switch -c my_wip
 $ git commit -a -m "WIP"
-$ git checkout master
+$ git switch master
 $ edit emergency fix
 $ git commit -a -m "Fix in a hurry"
-$ git checkout my_wip
+$ git switch my_wip
 $ git reset --soft HEAD^
 # ... continue hacking ...
 ----------------------------------------------------------------
@@ -293,7 +293,8 @@ SEE ALSO
 linkgit:git-checkout[1],
 linkgit:git-commit[1],
 linkgit:git-reflog[1],
-linkgit:git-reset[1]
+linkgit:git-reset[1],
+linkgit:git-switch[1]
 
 GIT
 ---
diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt
index e29a9effcc..6bd0f192cc 100644
--- a/Documentation/gitcore-tutorial.txt
+++ b/Documentation/gitcore-tutorial.txt
@@ -741,7 +741,7 @@ used earlier, and create a branch in it. You do that by simply just
 saying that you want to check out a new branch:
 
 ------------
-$ git checkout -b mybranch
+$ git switch -c mybranch
 ------------
 
 will create a new branch based at the current `HEAD` position, and switch
@@ -755,7 +755,7 @@ just telling 'git checkout' what the base of the checkout would be.
 In other words, if you have an earlier tag or branch, you'd just do
 
 ------------
-$ git checkout -b mybranch earlier-commit
+$ git switch -c mybranch earlier-commit
 ------------
 
 and it would create the new branch `mybranch` at the earlier commit,
@@ -765,7 +765,7 @@ and check out the state at that time.
 You can always just jump back to your original `master` branch by doing
 
 ------------
-$ git checkout master
+$ git switch master
 ------------
 
 (or any other branch-name, for that matter) and if you forget which
@@ -794,7 +794,7 @@ $ git branch <branchname> [startingpoint]
 
 which will simply _create_ the branch, but will not do anything further.
 You can then later -- once you decide that you want to actually develop
-on that branch -- switch to that branch with a regular 'git checkout'
+on that branch -- switch to that branch with a regular 'git switch'
 with the branchname as the argument.
 
 
@@ -808,7 +808,7 @@ being the same as the original `master` branch, let's make sure we're in
 that branch, and do some work there.
 
 ------------------------------------------------
-$ git checkout mybranch
+$ git switch mybranch
 $ echo "Work, work, work" >>hello
 $ git commit -m "Some work." -i hello
 ------------------------------------------------
@@ -825,7 +825,7 @@ does some work in the original branch, and simulate that by going back
 to the master branch, and editing the same file differently there:
 
 ------------
-$ git checkout master
+$ git switch master
 ------------
 
 Here, take a moment to look at the contents of `hello`, and notice how they
@@ -958,7 +958,7 @@ to the `master` branch. Let's go back to `mybranch`, and run
 'git merge' to get the "upstream changes" back to your branch.
 
 ------------
-$ git checkout mybranch
+$ git switch mybranch
 $ git merge -m "Merge upstream changes." master
 ------------
 
@@ -1133,9 +1133,9 @@ Remember, before running 'git merge', our `master` head was at
 work." commit.
 
 ------------
-$ git checkout mybranch
+$ git switch mybranch
 $ git reset --hard master^2
-$ git checkout master
+$ git switch master
 $ git reset --hard master^
 ------------
 
diff --git a/Documentation/giteveryday.txt b/Documentation/giteveryday.txt
index 9f2528fc8c..38015026bb 100644
--- a/Documentation/giteveryday.txt
+++ b/Documentation/giteveryday.txt
@@ -41,7 +41,7 @@ following commands.
 
   * linkgit:git-log[1] to see what happened.
 
-  * linkgit:git-checkout[1] and linkgit:git-branch[1] to switch
+  * linkgit:git-switch[1] and linkgit:git-branch[1] to switch
     branches.
 
   * linkgit:git-add[1] to manage the index file.
@@ -80,7 +80,7 @@ $ git tag v2.43 <2>
 Create a topic branch and develop.::
 +
 ------------
-$ git checkout -b alsa-audio <1>
+$ git switch -c alsa-audio <1>
 $ edit/compile/test
 $ git checkout -- curses/ux_audio_oss.c <2>
 $ git add curses/ux_audio_alsa.c <3>
@@ -90,7 +90,7 @@ $ git commit -a -s <5>
 $ edit/compile/test
 $ git diff HEAD^ <6>
 $ git commit -a --amend <7>
-$ git checkout master <8>
+$ git switch master <8>
 $ git merge alsa-audio <9>
 $ git log --since='3 days ago' <10>
 $ git log v2.43.. curses/ <11>
@@ -148,11 +148,11 @@ Clone the upstream and work on it.  Feed changes to upstream.::
 ------------
 $ git clone git://git.kernel.org/pub/scm/.../torvalds/linux-2.6 my2.6
 $ cd my2.6
-$ git checkout -b mine master <1>
+$ git switch -c mine master <1>
 $ edit/compile/test; git commit -a -s <2>
 $ git format-patch master <3>
 $ git send-email --to="person <email@example.com>" 00*.patch <4>
-$ git checkout master <5>
+$ git switch master <5>
 $ git pull <6>
 $ git log -p ORIG_HEAD.. arch/i386 include/asm-i386 <7>
 $ git ls-remote --heads http://git.kernel.org/.../jgarzik/libata-dev.git <8>
@@ -194,7 +194,7 @@ satellite$ edit/compile/test/commit
 satellite$ git push origin <4>
 
 mothership$ cd frotz
-mothership$ git checkout master
+mothership$ git switch master
 mothership$ git merge satellite/master <5>
 ------------
 +
@@ -216,7 +216,7 @@ machine into the master branch.
 Branch off of a specific tag.::
 +
 ------------
-$ git checkout -b private2.6.14 v2.6.14 <1>
+$ git switch -c private2.6.14 v2.6.14 <1>
 $ edit/compile/test; git commit -a
 $ git checkout master
 $ git cherry-pick v2.6.14..private2.6.14 <2>
@@ -274,14 +274,14 @@ $ mailx <3>
 & s 2 3 4 5 ./+to-apply
 & s 7 8 ./+hold-linus
 & q
-$ git checkout -b topic/one master
+$ git switch -c topic/one master
 $ git am -3 -i -s ./+to-apply <4>
 $ compile/test
-$ git checkout -b hold/linus && git am -3 -i -s ./+hold-linus <5>
-$ git checkout topic/one && git rebase master <6>
-$ git checkout pu && git reset --hard next <7>
+$ git switch -c hold/linus && git am -3 -i -s ./+hold-linus <5>
+$ git switch topic/one && git rebase master <6>
+$ git switch pu && git reset --hard next <7>
 $ git merge topic/one topic/two && git merge hold/linus <8>
-$ git checkout maint
+$ git switch maint
 $ git cherry-pick master~4 <9>
 $ compile/test
 $ git tag -s -m "GIT 0.99.9x" v0.99.9x <10>
diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt
index 242de31cb6..e6ad6b5f8d 100644
--- a/Documentation/gittutorial.txt
+++ b/Documentation/gittutorial.txt
@@ -207,7 +207,7 @@ automatically.  The asterisk marks the branch you are currently on;
 type
 
 ------------------------------------------------
-$ git checkout experimental
+$ git switch experimental
 ------------------------------------------------
 
 to switch to the experimental branch.  Now edit a file, commit the
@@ -216,7 +216,7 @@ change, and switch back to the master branch:
 ------------------------------------------------
 (edit file)
 $ git commit -a
-$ git checkout master
+$ git switch master
 ------------------------------------------------
 
 Check that the change you made is no longer visible, since it was
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
index 72daa20e76..a1c7a65da6 100644
--- a/Documentation/revisions.txt
+++ b/Documentation/revisions.txt
@@ -115,7 +115,7 @@ Here's an example to make it more clear:
 ------------------------------
 $ git config push.default current
 $ git config remote.pushdefault myfork
-$ git checkout -b mybranch origin/master
+$ git switch -c mybranch origin/master
 
 $ git rev-parse --symbolic-full-name @{upstream}
 refs/remotes/origin/master
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index eff7890274..766e64d0e1 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -122,10 +122,10 @@ Tags are expected to always point at the same version of a project,
 while heads are expected to advance as development progresses.
 
 Create a new branch head pointing to one of these versions and check it
-out using linkgit:git-checkout[1]:
+out using linkgit:git-switch[1]:
 
 ------------------------------------------------
-$ git checkout -b new v2.6.13
+$ git switch -c new v2.6.13
 ------------------------------------------------
 
 The working directory then reflects the contents that the project had
@@ -282,10 +282,10 @@ a summary of the commands:
 	this command will fail with a warning.
 `git branch -D <branch>`::
 	delete the branch `<branch>` irrespective of its merged status.
-`git checkout <branch>`::
+`git switch <branch>`::
 	make the current branch `<branch>`, updating the working
 	directory to reflect the version referenced by `<branch>`.
-`git checkout -b <new> <start-point>`::
+`git switch -c <new> <start-point>`::
 	create a new branch `<new>` referencing `<start-point>`, and
 	check it out.
 
@@ -302,22 +302,22 @@ ref: refs/heads/master
 Examining an old version without creating a new branch
 ------------------------------------------------------
 
-The `git checkout` command normally expects a branch head, but will also
+The `git switch` command normally expects a branch head, but will also
 accept an arbitrary commit; for example, you can check out the commit
 referenced by a tag:
 
 ------------------------------------------------
-$ git checkout v2.6.17
+$ git switch --detach v2.6.17
 Note: checking out 'v2.6.17'.
 
 You are in 'detached HEAD' state. You can look around, make experimental
 changes and commit them, and you can discard any commits you make in this
-state without impacting any branches by performing another checkout.
+state without impacting any branches by performing another switch.
 
 If you want to create a new branch to retain commits you create, you may
-do so (now or later) by using -b with the checkout command again. Example:
+do so (now or later) by using -c with the switch command again. Example:
 
-  git checkout -b new_branch_name
+  git switch -c new_branch_name
 
 HEAD is now at 427abfa Linux v2.6.17
 ------------------------------------------------
@@ -373,7 +373,7 @@ You might want to build on one of these remote-tracking branches
 on a branch of your own, just as you would for a tag:
 
 ------------------------------------------------
-$ git checkout -b my-todo-copy origin/todo
+$ git switch -c my-todo-copy origin/todo
 ------------------------------------------------
 
 You can also check out `origin/todo` directly to examine it or
@@ -2211,8 +2211,8 @@ $ git branch --track release origin/master
 These can be easily kept up to date using linkgit:git-pull[1].
 
 -------------------------------------------------
-$ git checkout test && git pull
-$ git checkout release && git pull
+$ git switch test && git pull
+$ git switch release && git pull
 -------------------------------------------------
 
 Important note!  If you have any local changes in these branches, then
@@ -2264,7 +2264,7 @@ tested changes
 2) help future bug hunters that use `git bisect` to find problems
 
 -------------------------------------------------
-$ git checkout -b speed-up-spinlocks v2.6.35
+$ git switch -c speed-up-spinlocks v2.6.35
 -------------------------------------------------
 
 Now you apply the patch(es), run some tests, and commit the change(s).  If
@@ -2279,7 +2279,7 @@ When you are happy with the state of this change, you can merge it into the
 "test" branch in preparation to make it public:
 
 -------------------------------------------------
-$ git checkout test && git merge speed-up-spinlocks
+$ git switch test && git merge speed-up-spinlocks
 -------------------------------------------------
 
 It is unlikely that you would have any conflicts here ... but you might if you
@@ -2291,7 +2291,7 @@ see the value of keeping each patch (or patch series) in its own branch.  It
 means that the patches can be moved into the `release` tree in any order.
 
 -------------------------------------------------
-$ git checkout release && git merge speed-up-spinlocks
+$ git switch release && git merge speed-up-spinlocks
 -------------------------------------------------
 
 After a while, you will have a number of branches, and despite the
@@ -2512,7 +2512,7 @@ Suppose that you create a branch `mywork` on a remote-tracking branch
 `origin`, and create some commits on top of it:
 
 -------------------------------------------------
-$ git checkout -b mywork origin
+$ git switch -c mywork origin
 $ vi file.txt
 $ git commit
 $ vi otherfile.txt
@@ -2552,7 +2552,7 @@ commits without any merges, you may instead choose to use
 linkgit:git-rebase[1]:
 
 -------------------------------------------------
-$ git checkout mywork
+$ git switch mywork
 $ git rebase origin
 -------------------------------------------------
 
@@ -3668,13 +3668,13 @@ change within the submodule, and then update the superproject to reference the
 new commit:
 
 -------------------------------------------------
-$ git checkout master
+$ git switch master
 -------------------------------------------------
 
 or
 
 -------------------------------------------------
-$ git checkout -b fix-up
+$ git switch -c fix-up
 -------------------------------------------------
 
 then
@@ -4194,7 +4194,7 @@ start.
 A good place to start is with the contents of the initial commit, with:
 
 ----------------------------------------------------
-$ git checkout e83c5163
+$ git switch --detach e83c5163
 ----------------------------------------------------
 
 The initial revision lays the foundation for almost everything Git has
@@ -4437,10 +4437,10 @@ Managing branches
 -----------------
 
 -----------------------------------------------
-$ git branch	     # list all local branches in this repo
-$ git checkout test  # switch working directory to branch "test"
-$ git branch new     # create branch "new" starting at current HEAD
-$ git branch -d new  # delete branch "new"
+$ git branch			# list all local branches in this repo
+$ git switch test	        # switch working directory to branch "test"
+$ git branch new		# create branch "new" starting at current HEAD
+$ git branch -d new		# delete branch "new"
 -----------------------------------------------
 
 Instead of basing a new branch on current HEAD (the default), use:
@@ -4456,7 +4456,7 @@ $ git branch new test~10 # ten commits before tip of branch "test"
 Create and switch to a new branch at the same time:
 
 -----------------------------------------------
-$ git checkout -b new v2.6.15
+$ git switch -c new v2.6.15
 -----------------------------------------------
 
 Update and examine branches from the repository you cloned from:
@@ -4467,7 +4467,7 @@ $ git branch -r		# list
   origin/master
   origin/next
   ...
-$ git checkout -b masterwork origin/master
+$ git switch -c masterwork origin/master
 -----------------------------------------------
 
 Fetch a branch from a different repository, and give it a new
diff --git a/advice.c b/advice.c
index 567209aa79..835219b51b 100644
--- a/advice.c
+++ b/advice.c
@@ -191,13 +191,16 @@ void NORETURN die_conclude_merge(void)
 void detach_advice(const char *new_name)
 {
 	const char *fmt =
-	_("Note: checking out '%s'.\n\n"
+	_("Note: checking out '%s'.\n"
+	"\n"
 	"You are in 'detached HEAD' state. You can look around, make experimental\n"
 	"changes and commit them, and you can discard any commits you make in this\n"
-	"state without impacting any branches by performing another checkout.\n\n"
+	"state without impacting any branches by performing another checkout.\n"
+	"\n"
 	"If you want to create a new branch to retain commits you create, you may\n"
-	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
-	"  git checkout -b <new-branch-name>\n\n");
+	"do so (now or later) by using -b with the checkout command again. Example:\n"
+	"\n"
+	"  git switch -c <new-branch-name>\n\n");
 
 	fprintf(stderr, fmt, new_name);
 }
diff --git a/builtin/clone.c b/builtin/clone.c
index 7c7f98c72c..b06f0ed735 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -491,7 +491,7 @@ static enum {
 static const char junk_leave_repo_msg[] =
 N_("Clone succeeded, but checkout failed.\n"
    "You can inspect what was checked out with 'git status'\n"
-   "and retry the checkout with 'git checkout -f HEAD'\n");
+   "and retry the checkout with 'git switch -f HEAD'\n");
 
 static void remove_junk(void)
 {
diff --git a/sha1-name.c b/sha1-name.c
index a656481c6a..e5f0832995 100644
--- a/sha1-name.c
+++ b/sha1-name.c
@@ -745,7 +745,7 @@ static int get_oid_basic(const char *str, int len, struct object_id *oid,
 	"because it will be ignored when you just specify 40-hex. These refs\n"
 	"may be created by mistake. For example,\n"
 	"\n"
-	"  git checkout -b $br $(git rev-parse ...)\n"
+	"  git switch -c $br $(git rev-parse ...)\n"
 	"\n"
 	"where \"$br\" is somehow empty and a 40-hex ref is created. Please\n"
 	"examine these refs and maybe delete them. Turn this message off by\n"
diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
index 1fa670625c..50f7d6c15c 100755
--- a/t/t2020-checkout-detach.sh
+++ b/t/t2020-checkout-detach.sh
@@ -204,7 +204,7 @@ test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not as
 	If you want to create a new branch to retain commits you create, you may
 	do so (now or later) by using -b with the checkout command again. Example:
 
-	  git checkout -b <new-branch-name>
+	  git switch -c <new-branch-name>
 
 	HEAD is now at \$commit three
 	EOF
@@ -280,7 +280,7 @@ test_expect_success 'describe_detached_head does print SHA-1 ellipsis when asked
 	If you want to create a new branch to retain commits you create, you may
 	do so (now or later) by using -b with the checkout command again. Example:
 
-	  git checkout -b <new-branch-name>
+	  git switch -c <new-branch-name>
 
 	HEAD is now at \$commit... three
 	EOF
-- 
2.20.1.682.gd5861c6d90
Eric Sunshine· Jan 31, 2019, 07:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH 19/19] doc: promote "git switch"

On Wed, Jan 30, 2019 at 4:50 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 12 quoted lines
> The new command "git switch" is added to avoid the confusion of
> one-command-do-all "git checkout" for new users. They are also helpful
> to avoid ambiguation context.
>
> For these reasons, promote it everywhere possible. This includes
> documentation, suggestions/advice from other commands...
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
> diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
> @@ -319,7 +319,7 @@ NOTES
>  If you are creating a branch that you want to checkout immediately, it is
s/checkout/switch to/
Show 12 quoted lines
> -easier to use the git checkout command with its `-b` option to create
> +easier to use the "git switch" command with its `-c` option to create
>  a branch and check it out with a single command.
> diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
> @@ -302,22 +302,22 @@ ref: refs/heads/master
>  Examining an old version without creating a new branch
>  ------------------------------------------------------
>
> -The `git checkout` command normally expects a branch head, but will also
> +The `git switch` command normally expects a branch head, but will also
>  accept an arbitrary commit; for example, you can check out the commit
>  referenced by a tag:
This may need to mention --detach explicitly. Perhaps:
    ...normally expects a branch head, but will also accept
    an arbitrary commit when invoked with --detach;...
Show 7 quoted lines
> diff --git a/advice.c b/advice.c
> @@ -191,13 +191,16 @@ void NORETURN die_conclude_merge(void)
>  void detach_advice(const char *new_name)
>  {
>         const char *fmt =
> -       _("Note: checking out '%s'.\n\n"
> +       _("Note: checking out '%s'.\n"
Do you also want to adjust this to avoid saying "checking out"?
Show 5 quoted lines
> +       "\n"
>         "You are in 'detached HEAD' state. You can look around, make experimental\n"
>         "changes and commit them, and you can discard any commits you make in this\n"
> -       "state without impacting any branches by performing another checkout.\n\n"
> +       "state without impacting any branches by performing another checkout.\n"
Ditto: "checkout"?
Show 5 quoted lines
> +       "\n"
>         "If you want to create a new branch to retain commits you create, you may\n"
> -       "do so (now or later) by using -b with the checkout command again. Example:\n\n"
> -       "  git checkout -b <new-branch-name>\n\n");
> +       "do so (now or later) by using -b with the checkout command again. Example:\n"

s/-b/-c/ s/checkout/switch/

Show 9 quoted lines
> +       "\n"
> +       "  git switch -c <new-branch-name>\n\n");
> diff --git a/builtin/clone.c b/builtin/clone.c
> @@ -491,7 +491,7 @@ static enum {
>  static const char junk_leave_repo_msg[] =
>  N_("Clone succeeded, but checkout failed.\n"
>     "You can inspect what was checked out with 'git status'\n"
> -   "and retry the checkout with 'git checkout -f HEAD'\n");
> +   "and retry the checkout with 'git switch -f HEAD'\n");

Just wondering if these uses of "checkout" and "checked out" need adjustment. I don't have any good suggestions, though.

> diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
> @@ -204,7 +204,7 @@ test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not as
>         If you want to create a new branch to retain commits you create, you may
>         do so (now or later) by using -b with the checkout command again. Example:

s/-b/-c/ s/checkout/switch/

Show 5 quoted lines
> -         git checkout -b <new-branch-name>
> +         git switch -c <new-branch-name>
> @@ -280,7 +280,7 @@ test_expect_success 'describe_detached_head does print SHA-1 ellipsis when asked
>         If you want to create a new branch to retain commits you create, you may
>         do so (now or later) by using -b with the checkout command again. Example:

s/-b/-c/ s/checkout/switch/

> -         git checkout -b <new-branch-name>
> +         git switch -c <new-branch-name>
Duy Nguyen· Jan 31, 2019, 11:28 UTC · re: Eric Sunshine · lore

Re: [PATCH 19/19] doc: promote "git switch"

On Thu, Jan 31, 2019 at 2:39 PM Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 10 quoted lines
> > diff --git a/builtin/clone.c b/builtin/clone.c
> > @@ -491,7 +491,7 @@ static enum {
> >  static const char junk_leave_repo_msg[] =
> >  N_("Clone succeeded, but checkout failed.\n"
> >     "You can inspect what was checked out with 'git status'\n"
> > -   "and retry the checkout with 'git checkout -f HEAD'\n");
> > +   "and retry the checkout with 'git switch -f HEAD'\n");
>
> Just wondering if these uses of "checkout" and "checked out" need
> adjustment. I don't have any good suggestions, though.

I think I'm going to leave this message alone for now. Technically "git switch" can replace "git checkout" here but I feel this is more about restoring files on worktree than switching branches. So when "git restore" comes, I'll change it to say "git restore --from=HEAD :/" or something like that instead.

-- 
Duy
SZEDER Gábor· Jan 31, 2019, 13:37 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH 00/19] Add new command "switch"

On Wed, Jan 30, 2019 at 04:48:12PM +0700, Nguyễn Thái Ngọc Duy wrote:
> The
> only one left is adding options similar to "git reset" (but with
> better names this time than --soft and --hard, hopefully).

Oh no, what for?! As the commit message or patch 19/19 'doc: promote "git switch"' states:

  The new command "git switch" is added to avoid the confusion of
  one-command-do-all "git checkout" for new users.

Adding '--soft|--hard' from 'git reset' would go in the opposite direction.

Duy Nguyen· Jan 31, 2019, 14:51 UTC · re: SZEDER Gábor · lore

Re: [PATCH 00/19] Add new command "switch"

On Thu, Jan 31, 2019 at 8:37 PM SZEDER Gábor <szeder.dev@gmail.com> wrote:
Show 14 quoted lines
>
> On Wed, Jan 30, 2019 at 04:48:12PM +0700, Nguyễn Thái Ngọc Duy wrote:
> > The
> > only one left is adding options similar to "git reset" (but with
> > better names this time than --soft and --hard, hopefully).
>
> Oh no, what for?!  As the commit message or patch 19/19 'doc: promote
> "git switch"' states:
>
>   The new command "git switch" is added to avoid the confusion of
>   one-command-do-all "git checkout" for new users.
>
> Adding '--soft|--hard' from 'git reset' would go in the opposite
> direction.

If it's about the confusion, I think we can avoid it. If it's about the one-command-do-all, I think it still fits in the main topic of git-switch, which is about switching. But in git-reset case it's switching HEAD, not a normal branch.

What I have in mind so far is this, let's see if it's so bad so I can find another direction.

git switch --reset-branch [--keep-index] [--keep-worktree] [<commit>]

This updates worktree and index to <commit> and "rewinds" the current branch to <commit>. If either of those --keep-* is given, that part will be left untouched. So "git reset --hard" is the same as "git switch --reset-branch", --soft "--reset-branch --keep-index --keep-worktree" and --mixed "--reset-branch --keep-worktree".

I've been updating docs with this new format to see what it looks like and I think it expresses the idea behind git-reset much better.

-- 
Duy
Junio C Hamano· Jan 31, 2019, 18:23 UTC · re: Duy Nguyen · lore

Re: [PATCH 00/19] Add new command "switch"

Duy Nguyen <pclouds@gmail.com> writes:
Show 7 quoted lines
>> Adding '--soft|--hard' from 'git reset' would go in the opposite
>> direction.
>
> If it's about the confusion, I think we can avoid it. If it's about
> the one-command-do-all, I think it still fits in the main topic of
> git-switch, which is about switching. But in git-reset case it's
> switching HEAD, not a normal branch.

I do not think "switch branch" should update HEAD for the branch. Let it be handled by "reset" (or "branch -f that-other-branch").

I personally did not have "it is way too overloaded" problem with "checkout", but it turns out that many others found it so. You'll see the same happen for your "switch" if you do not resist temptation to add unrelated things (or things you may find related but you see others find unrelated even in this early discussion).

Duy Nguyen· Feb 2, 2019, 11:06 UTC · re: Junio C Hamano · lore

Re: [PATCH 00/19] Add new command "switch"

On Fri, Feb 1, 2019 at 1:23 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 13 quoted lines
>
> Duy Nguyen <pclouds@gmail.com> writes:
>
> >> Adding '--soft|--hard' from 'git reset' would go in the opposite
> >> direction.
> >
> > If it's about the confusion, I think we can avoid it. If it's about
> > the one-command-do-all, I think it still fits in the main topic of
> > git-switch, which is about switching. But in git-reset case it's
> > switching HEAD, not a normal branch.
>
> I do not think "switch branch" should update HEAD for the branch.
> Let it be handled by "reset" (or "branch -f that-other-branch").

It can already, it's simply a shortcut for "git switch --force-create <current-branch> -f <commit>" (or "git checkout -fB <current-branch> <commit>"), at least for --hard. --mixed and --soft is just a different variant of "-f".

Show 5 quoted lines
> I personally did not have "it is way too overloaded" problem with
> "checkout", but it turns out that many others found it so.  You'll
> see the same happen for your "switch" if you do not resist
> temptation to add unrelated things (or things you may find related
> but you see others find unrelated even in this early discussion).

The thing about git-reset is it's also overloaded with updating things other than HEAD. All the pathspec form is the same as checkout/restore (except checkout updates both index/worktree while reset is more about index; but restore can cover all index/worktree combination). But yeah maybe just leave it for now. While trying to document the new option in switch to replace reset, the verb "rewind" (the tip of the current branch) seems fitting well. I might revisit this topic with git-rewind or something.

-- 
Duy
Junio C Hamano· Jan 31, 2019, 18:13 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH 00/19] Add new command "switch"

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
> This is the first half of the previous "switch-branch and
> restore-files" RFC series [1]. I only focus on switch-branch for now
> because it's already getting long and I still have some work to do on
> restore-files.

Exciting ;-) This will probably become the best UI improvement in the past few years.

Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 00/19] Add new command "switch"

This series adds a new command "switch" for switching branches, an alternative for "git checkout".

v2 fixes the comments I got from v1. I think the range-diff below summarizes it better than I do. No major changes though.

Range-diff dựa trên v1:
 1:  6d550b443c !  1:  2283204090 checkout: split part of it to new command switch
    @@ -148,24 +148,33 @@
     +--------
     +[verse]
     +'git switch' [<options>] [--guess] <branch>
    -+'git switch' [<options>] --detach [<start_point>>]
    -+'git switch' [<options>] [[-c|-C|--orphan] <new_branch>] [<start_point>]
    ++'git switch' [<options>] --detach [<start-point>]
    ++'git switch' [<options>] (-c|-C|--orphan) <new-branch> [<start-point>]
     +
     +DESCRIPTION
     +-----------
    -+Switch to a specified branch and update files in the working tree to
    -+match it. Optionally a new branch could be created with either `-c` or
    -+`-C`, or detach from any branch with `--detach`, along with switching.
    ++Switch to a specified branch. The working tree and the index are
    ++updated to match the branch. All new commits will be added to the tip
    ++of this branch.
    ++
    ++Optionally a new branch could be created with either `-c` or `-C`, or
    ++detach the working tree from any branch with `--detach`, along with
    ++switching.
    ++
    ++Switching branches does not require a clean index and working tree
    ++(i.e. no differences compared to 'HEAD'). The operation is aborted
    ++however if the switch leads to loss of local changes, unless told
    ++otherwise.
     +
     +OPTIONS
     +-------
     +<branch>::
     +	Branch to switch to.
     +
    -+<new_branch>::
    ++<new-branch>::
     +	Name for the new branch.
     +
    -+<start_point>::
    ++<start-point>::
     +	The name of a commit at which to switch to before creating a
     +	new branch or detach from.
     ++
    @@ -177,17 +186,27 @@
     +base of `A` and `B` if there is exactly one merge base. You can leave
     +out at most one of `A` and `B`, in which case it defaults to `HEAD`.
     +
    -+-c <new_branch>::
    -+--create <new_branch>::
    -+	Create a new branch named <new_branch> and start it at
    -+	<start_point>; see linkgit:git-branch[1] for details.
    ++-c <new-branch>::
    ++--create <new-branch>::
    ++	Create a new branch named <new-branch> starting at
    ++	<start-point> before switching to the branch. This is a
    ++	convenient shortcut for:
    +++
    ++------------
    ++$ git branch <new-branch>
    ++$ git switch <new-branch>
    ++------------
     +
    -+-C <new_branch>::
    -+--force-create <new_branch>::
    -+	Creates the branch <new_branch> and start it at <start_point>;
    -+	if it already exists, then reset it to <start_point>. This is
    -+	equivalent to running "git branch" with "-f"; see
    -+	linkgit:git-branch[1] for details.
    ++-C <new-branch>::
    ++--force-create <new-branch>::
    ++	Similar to `--create` except that if <new-branch> already
    ++	exists, it will be reset to <start-point>. This is a
    ++	convenient shortcut for:
    +++
    ++------------
    ++$ git branch -f <new-branch>
    ++$ git switch <new-branch>
    ++------------
     +
     +-d::
     +--detach::
    @@ -220,7 +239,9 @@
     +-f::
     +--force::
     +	Proceed even if the index or the working tree differs from
    -+	HEAD.  This is used to throw away local changes.
    ++	HEAD. Both the index and working tree are restored to match
    ++	the switching target. This is used to throw away local
    ++	changes.
     +
     +--[no-]progress::
     +	Progress status is reported on the standard error stream
    @@ -270,9 +291,9 @@
     +	"merge" (default) and "diff3" (in addition to what is shown by
     +	"merge" style, shows the original contents).
     +
    -+--orphan <new_branch>::
    -+	Create a new 'orphan' branch, named <new_branch>, started from
    -+	<start_point> and switch to it. See explanation of the same
    ++--orphan <new-branch>::
    ++	Create a new 'orphan' branch, named <new-branch>, started from
    ++	<start-point> and switch to it. See explanation of the same
     +	option in linkgit:git-checkout[1] for details.
     +
     +--ignore-other-worktrees::
 2:  e2f94df471 !  2:  deceb221dc switch: better names for -b and -B
    @@ -61,7 +61,7 @@
     +		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 new branch")),
    ++			   N_("create/reset and switch to a branch")),
     +		OPT_END()
     +	};
      	int ret;
 3:  b3d431a97f =  3:  08f28e9726 switch: remove -l
 4:  26738818fa =  4:  9ee4852c8b switch: stop accepting pathspec
 5:  79ebe7a68c !  5:  0a7ea4b33b switch: reject "do nothing" case
    @@ -32,7 +32,7 @@
     +	    !new_branch_info->name &&
     +	    !opts->new_branch &&
     +	    !opts->force_detach)
    -+		die(_("nothing to do"));
    ++		die(_("missing branch or commit argument"));
     +
      	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
      	    !opts->ignore_other_worktrees) {
 6:  bc36ac8510 !  6:  eedf82c72b switch: only allow explicit detached HEAD
    @@ -26,7 +26,7 @@
      	int ignore_skipworktree;
     @@
      	    !opts->force_detach)
    - 		die(_("nothing to do"));
    + 		die(_("missing branch or commit argument"));
      
     +	if (!opts->implicit_detach &&
     +	    !opts->force_detach &&
 7:  1a761ce8e5 =  7:  9314ff16c9 switch: add short option for --detach
 8:  738c560eb9 !  8:  9d8e461df2 switch: no implicit dwim, use --guess to dwim
    @@ -143,7 +143,7 @@
     @@
      			   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 new branch")),
    + 			   N_("create/reset and switch to a branch")),
     +		OPT_BOOL('g', "guess", &opts.dwim_new_local_branch,
     +			 N_("second guess 'git switch <no-such-branch>'")),
      		OPT_END()
 9:  456e9340f1 =  9:  749665c457 switch: no worktree status unless real branch switch happens
10:  cdd132a7e2 ! 10:  e4e0b397eb t: add tests for switch
    @@ -51,9 +51,7 @@
     +test_expect_success 'switch and create branch' '
     +	test_when_finished git switch master &&
     +	git switch -c temp master^ &&
    -+	git rev-parse master^ >expected &&
    -+	git rev-parse refs/heads/temp >actual &&
    -+	test_cmp expected actual &&
    ++	test_cmp_rev master^ refs/heads/temp &&
     +	echo refs/heads/temp >expected-branch &&
     +	git symbolic-ref HEAD >actual-branch &&
     +	test_cmp expected-branch actual-branch
    @@ -63,9 +61,7 @@
     +	test_when_finished git switch master &&
     +	git switch --detach master &&
     +	git switch -C temp &&
    -+	git rev-parse master >expected &&
    -+	git rev-parse refs/heads/temp >actual &&
    -+	test_cmp expected actual &&
    ++	test_cmp_rev master refs/heads/temp &&
     +	echo refs/heads/temp >expected-branch &&
     +	git symbolic-ref HEAD >actual-branch &&
     +	test_cmp expected-branch actual-branch
11:  96a97fcab4 = 11:  b9a7662271 completion: support switch
12:  8d984bc684 ! 12:  7449a0c4cc doc: promote "git switch"
    @@ -40,14 +40,18 @@
      +
      <1> This step and the next one could be combined into a single step with
     @@
    + NOTES
      -----
      
    - If you are creating a branch that you want to checkout immediately, it is
    +-If you are creating a branch that you want to checkout immediately, it is
     -easier to use the git checkout command with its `-b` option to create
    -+easier to use the "git switch" command with its `-c` option to create
    - a branch and check it out with a single command.
    +-a branch and check it out with a single command.
    ++If you are creating a branch that you want to switch to immediately,
    ++it is easier to use the "git switch" command with its `-c` option to
    ++do the same thing with a single command.
      
      The options `--contains`, `--no-contains`, `--merged` and `--no-merged`
    + serve four related but different purposes:
     
      diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt
      --- a/Documentation/git-check-ref-format.txt
    @@ -473,9 +477,11 @@
      ------------------------------------------------------
      
     -The `git checkout` command normally expects a branch head, but will also
    +-accept an arbitrary commit; for example, you can check out the commit
    +-referenced by a tag:
     +The `git switch` command normally expects a branch head, but will also
    - accept an arbitrary commit; for example, you can check out the commit
    - referenced by a tag:
    ++accept an arbitrary commit when invoked with --detach; for example,
    ++you can check out the commit referenced by a tag:
      
      ------------------------------------------------
     -$ git checkout v2.6.17
    @@ -628,36 +634,23 @@
      {
      	const char *fmt =
     -	_("Note: checking out '%s'.\n\n"
    -+	_("Note: checking out '%s'.\n"
    ++	_("Note: switching to '%s'.\n"
     +	"\n"
      	"You are in 'detached HEAD' state. You can look around, make experimental\n"
      	"changes and commit them, and you can discard any commits you make in this\n"
     -	"state without impacting any branches by performing another checkout.\n\n"
    -+	"state without impacting any branches by performing another checkout.\n"
    ++	"state without impacting any branches by switching back to a branch.\n"
     +	"\n"
      	"If you want to create a new branch to retain commits you create, you may\n"
     -	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
     -	"  git checkout -b <new-branch-name>\n\n");
    -+	"do so (now or later) by using -b with the checkout command again. Example:\n"
    ++	"do so (now or later) by using -c with the switch command. Example:\n"
     +	"\n"
     +	"  git switch -c <new-branch-name>\n\n");
      
      	fprintf(stderr, fmt, new_name);
      }
     
    - diff --git a/builtin/clone.c b/builtin/clone.c
    - --- a/builtin/clone.c
    - +++ b/builtin/clone.c
    -@@
    - static const char junk_leave_repo_msg[] =
    - N_("Clone succeeded, but checkout failed.\n"
    -    "You can inspect what was checked out with 'git status'\n"
    --   "and retry the checkout with 'git checkout -f HEAD'\n");
    -+   "and retry the checkout with 'git switch -f HEAD'\n");
    - 
    - static void remove_junk(void)
    - {
    -
      diff --git a/sha1-name.c b/sha1-name.c
      --- a/sha1-name.c
      +++ b/sha1-name.c
    @@ -675,8 +668,20 @@
      --- a/t/t2020-checkout-detach.sh
      +++ b/t/t2020-checkout-detach.sh
     @@
    + 
    + 	# The first detach operation is more chatty than the following ones.
    + 	cat >1st_detach <<-EOF &&
    +-	Note: checking out 'HEAD^'.
    ++	Note: switching to 'HEAD^'.
    + 
    + 	You are in 'detached HEAD' state. You can look around, make experimental
    + 	changes and commit them, and you can discard any commits you make in this
    +-	state without impacting any branches by performing another checkout.
    ++	state without impacting any branches by switching back to a branch.
    + 
      	If you want to create a new branch to retain commits you create, you may
    - 	do so (now or later) by using -b with the checkout command again. Example:
    +-	do so (now or later) by using -b with the checkout command again. Example:
    ++	do so (now or later) by using -c with the switch command. Example:
      
     -	  git checkout -b <new-branch-name>
     +	  git switch -c <new-branch-name>
    @@ -684,8 +689,20 @@
      	HEAD is now at \$commit three
      	EOF
     @@
    + 
    + 	# The first detach operation is more chatty than the following ones.
    + 	cat >1st_detach <<-EOF &&
    +-	Note: checking out 'HEAD^'.
    ++	Note: switching to 'HEAD^'.
    + 
    + 	You are in 'detached HEAD' state. You can look around, make experimental
    + 	changes and commit them, and you can discard any commits you make in this
    +-	state without impacting any branches by performing another checkout.
    ++	state without impacting any branches by switching back to a branch.
    + 
      	If you want to create a new branch to retain commits you create, you may
    - 	do so (now or later) by using -b with the checkout command again. Example:
    +-	do so (now or later) by using -b with the checkout command again. Example:
    ++	do so (now or later) by using -c with the switch command. Example:
      
     -	  git checkout -b <new-branch-name>
     +	  git switch -c <new-branch-name>
13:  91e5442069 <  -:  ---------- checkout: split part of it to new command restore
14:  6bc8f87c0a <  -:  ---------- restore: take tree-ish from --from option instead
15:  095a1cd3e3 <  -:  ---------- restore: make pathspec mandatory
16:  6eb5612187 <  -:  ---------- restore: disable overlay mode by default
17:  cc79522047 <  -:  ---------- t: add tests for restore
18:  ed5d7097b6 <  -:  ---------- completion: support restore
19:  625a4fac68 <  -:  ---------- doc: promote "git restore"
Nguyễn Thái Ngọc Duy (19):
  git-checkout.txt: fix one syntax line
  doc: document --overwrite-ignore
  t: rename t2014-switch.sh to t2014-checkout-switch.sh
  checkout: factor out some code in parse_branchname_arg()
  checkout: make "opts" in cmd_checkout() a pointer
  checkout: move 'confict_style' and 'dwim_..' to checkout_opts
  checkout: split options[] array in three pieces
  checkout: split part of it to new command switch
  switch: better names for -b and -B
  switch: remove -l
  switch: stop accepting pathspec
  switch: reject "do nothing" case
  switch: only allow explicit detached HEAD
  switch: add short option for --detach
  switch: no implicit dwim, use --guess to dwim
  switch: no worktree status unless real branch switch happens
  t: add tests for switch
  completion: support switch
  doc: promote "git switch"
 .gitignore                                    |   1 +
 Documentation/config/advice.txt               |  13 +-
 Documentation/config/branch.txt               |   4 +-
 Documentation/config/checkout.txt             |  17 +-
 Documentation/config/diff.txt                 |   3 +-
 Documentation/git-branch.txt                  |  12 +-
 Documentation/git-check-ref-format.txt        |   3 +-
 Documentation/git-checkout.txt                |  48 +-
 Documentation/git-format-patch.txt            |   2 +-
 Documentation/git-merge-base.txt              |   2 +-
 Documentation/git-merge.txt                   |   4 +
 Documentation/git-rebase.txt                  |   2 +-
 Documentation/git-remote.txt                  |   2 +-
 Documentation/git-rerere.txt                  |  10 +-
 Documentation/git-reset.txt                   |  20 +-
 Documentation/git-stash.txt                   |   9 +-
 Documentation/git-switch.txt                  | 257 ++++++++++
 Documentation/gitattributes.txt               |   3 +-
 Documentation/gitcore-tutorial.txt            |  18 +-
 Documentation/giteveryday.txt                 |  24 +-
 Documentation/githooks.txt                    |   8 +-
 Documentation/gittutorial.txt                 |   4 +-
 Documentation/revisions.txt                   |   2 +-
 Documentation/user-manual.txt                 |  56 +--
 Makefile                                      |   1 +
 advice.c                                      |  11 +-
 builtin.h                                     |   1 +
 builtin/checkout.c                            | 464 +++++++++---------
 command-list.txt                              |   1 +
 contrib/completion/git-completion.bash        |  26 +
 git.c                                         |   1 +
 parse-options-cb.c                            |  17 +
 parse-options.h                               |   3 +-
 sha1-name.c                                   |   2 +-
 t/t1090-sparse-checkout-scope.sh              |  14 -
 ...014-switch.sh => t2014-checkout-switch.sh} |   0
 t/t2020-checkout-detach.sh                    |  16 +-
 t/t2060-switch.sh                             |  87 ++++
 38 files changed, 796 insertions(+), 372 deletions(-)
 create mode 100644 Documentation/git-switch.txt
 rename t/{t2014-switch.sh => t2014-checkout-switch.sh} (100%)
 create mode 100755 t/t2060-switch.sh
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 01/19] git-checkout.txt: fix one syntax line

<branch> can be omitted in this syntax, and it's actually documented a few paragraphs down:

  You could omit <branch>, in which case the command degenerates to
  "check out the current branch", which is a glorified no-op with
  rather expensive side-effects to show only the tracking information,
  if exists, for the current branch.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to Documentation/git-checkout.txt +1 −1
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index ce7d38b8b7..bc7fbdb8d2 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -23,7 +23,7 @@ or the specified tree.  If no paths are given, 'git checkout' will
 also update `HEAD` to set the specified branch as the current
 branch.
 
-'git checkout' <branch>::
+'git checkout' [<branch>]::
 	To prepare for working on <branch>, switch to it by updating
 	the index and the files in the working tree, and by pointing
 	HEAD at the branch. Local modifications to the files in the
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 02/19] doc: document --overwrite-ignore

I added this option in git-checkout and git-merge in c1d7036b6b (checkout,merge: disallow overwriting ignored files with --no-overwrite-ignore - 2011-11-27) but did not remember to update documentation. This completes that commit.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt | 5 +++++
 Documentation/git-merge.txt    | 4 ++++
 2 files changed, 9 insertions(+)
Show changes to 2 files +9 −0

Documentation/git-checkout.txt, Documentation/git-merge.txt

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index bc7fbdb8d2..9ac01f7967 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -270,6 +270,11 @@ Note that this option uses the no overlay mode by default (see also
 	out anyway. In other words, the ref can be held by more than one
 	worktree.
 
+--[no-]overwrite-ignore::
+	Silently overwrite ignored files when switching branches. This
+	is the default behavior. Use --no-overwrite-ignore to abort
+	the operation when the new branch contains ignored files.
+
 --[no-]recurse-submodules::
 	Using --recurse-submodules will update the content of all initialized
 	submodules according to the commit recorded in the superproject. If
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index 4cc86469f3..0480e9d0f0 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -87,6 +87,10 @@ will be appended to the specified message.
 	Allow the rerere mechanism to update the index with the
 	result of auto-conflict resolution if possible.
 
+--[no-]overwrite-ignore::
+	Silently overwrite ignored files from the merge result. This
+	is the default behavior. Use --no-overwrite-ignore to abort.
+
 --abort::
 	Abort the current conflict resolution process, and
 	try to reconstruct the pre-merge state.
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 03/19] t: rename t2014-switch.sh to t2014-checkout-switch.sh

The old name does not really say that this is about 'checkout -b'. See 49d833dc07 (Revert "checkout branch: prime cache-tree fully" - 2009-05-12) for more information

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 t/{t2014-switch.sh => t2014-checkout-switch.sh} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename t/{t2014-switch.sh => t2014-checkout-switch.sh} (100%)
Show changes to t/t2014-switch.sh +0 −0
diff --git a/t/t2014-switch.sh b/t/t2014-checkout-switch.sh
similarity index 100%
rename from t/t2014-switch.sh
rename to t/t2014-checkout-switch.sh
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 04/19] checkout: factor out some code in parse_branchname_arg()

This is in preparation for the new command restore, which also needs to parse opts->source_tree but does not need all the disambiguation logic.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 51 ++++++++++++++++++++++++++++------------------
 1 file changed, 31 insertions(+), 20 deletions(-)
Show changes to builtin/checkout.c +31 −20
diff --git a/builtin/checkout.c b/builtin/checkout.c
index f63d864a91..12b7e08d4e 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1068,6 +1068,34 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
 	return git_xmerge_config(var, value, NULL);
 }
 
+static void setup_new_branch_info_and_source_tree(
+	struct branch_info *new_branch_info,
+	struct checkout_opts *opts,
+	struct object_id *rev,
+	const char *arg)
+{
+	struct tree **source_tree = &opts->source_tree;
+	struct object_id branch_rev;
+
+	new_branch_info->name = arg;
+	setup_branch_path(new_branch_info);
+
+	if (!check_refname_format(new_branch_info->path, 0) &&
+	    !read_ref(new_branch_info->path, &branch_rev))
+		oidcpy(rev, &branch_rev);
+	else
+		new_branch_info->path = NULL; /* not an existing branch */
+
+	new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
+	if (!new_branch_info->commit) {
+		/* not a commit */
+		*source_tree = parse_tree_indirect(rev);
+	} else {
+		parse_commit_or_die(new_branch_info->commit);
+		*source_tree = get_commit_tree(new_branch_info->commit);
+	}
+}
+
 static int parse_branchname_arg(int argc, const char **argv,
 				int dwim_new_local_branch_ok,
 				struct branch_info *new_branch_info,
@@ -1075,10 +1103,8 @@ static int parse_branchname_arg(int argc, const char **argv,
 				struct object_id *rev,
 				int *dwim_remotes_matched)
 {
-	struct tree **source_tree = &opts->source_tree;
 	const char **new_branch = &opts->new_branch;
 	int argcount = 0;
-	struct object_id branch_rev;
 	const char *arg;
 	int dash_dash_pos;
 	int has_dash_dash = 0;
@@ -1200,26 +1226,11 @@ static int parse_branchname_arg(int argc, const char **argv,
 	argv++;
 	argc--;
 
-	new_branch_info->name = arg;
-	setup_branch_path(new_branch_info);
-
-	if (!check_refname_format(new_branch_info->path, 0) &&
-	    !read_ref(new_branch_info->path, &branch_rev))
-		oidcpy(rev, &branch_rev);
-	else
-		new_branch_info->path = NULL; /* not an existing branch */
+	setup_new_branch_info_and_source_tree(new_branch_info, opts, rev, arg);
 
-	new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
-	if (!new_branch_info->commit) {
-		/* not a commit */
-		*source_tree = parse_tree_indirect(rev);
-	} else {
-		parse_commit_or_die(new_branch_info->commit);
-		*source_tree = get_commit_tree(new_branch_info->commit);
-	}
-
-	if (!*source_tree)                   /* case (1): want a tree */
+	if (!opts->source_tree)                   /* case (1): want a tree */
 		die(_("reference is not a tree: %s"), arg);
+
 	if (!has_dash_dash) {	/* case (3).(d) -> (1) */
 		/*
 		 * Do not complain the most common case
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 05/19] checkout: make "opts" in cmd_checkout() a pointer

"opts" will soon be moved out of cmd_checkout(). To keep changes in that patch smaller, convert "opts" to a pointer and keep the real thing behind "real_opts".

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 115 +++++++++++++++++++++++----------------------
 1 file changed, 58 insertions(+), 57 deletions(-)
Show changes to builtin/checkout.c +58 −57
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 12b7e08d4e..66f99e788d 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1326,82 +1326,83 @@ static int checkout_branch(struct checkout_opts *opts,
 
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
-	struct checkout_opts opts;
+	struct checkout_opts real_opts;
+	struct checkout_opts *opts = &real_opts;
 	struct branch_info new_branch_info;
 	char *conflict_style = NULL;
 	int dwim_new_local_branch, no_dwim_new_local_branch = 0;
 	int dwim_remotes_matched = 0;
 	struct option options[] = {
-		OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
-		OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
+		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
+		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"),
+		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"),
+		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),
-		OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
-		OPT_SET_INT_F('2', "ours", &opts.writeout_stage,
+		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
+		OPT_SET_INT_F('2', "ours", &opts->writeout_stage,
 			      N_("checkout our version for unmerged files"),
 			      2, PARSE_OPT_NONEG),
-		OPT_SET_INT_F('3', "theirs", &opts.writeout_stage,
+		OPT_SET_INT_F('3', "theirs", &opts->writeout_stage,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
-		OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)"),
+		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")),
-		OPT_BOOL_F(0, "overwrite-ignore", &opts.overwrite_ignore,
+		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
+		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
 		OPT_STRING(0, "conflict", &conflict_style, N_("style"),
 			   N_("conflict style (merge or diff3)")),
-		OPT_BOOL('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
-		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree,
+		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_BOOL(0, "no-guess", &no_dwim_new_local_branch,
 			 N_("do not second guess 'git checkout <no-such-branch>'")),
-		OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
+		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
 		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
 			    "checkout", "control recursive updating of submodules",
 			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
-		OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
-		OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")),
+		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
+		OPT_BOOL(0, "overlay", &opts->overlay_mode, N_("use overlay mode (default)")),
 		OPT_END(),
 	};
 
-	memset(&opts, 0, sizeof(opts));
+	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->overwrite_ignore = 1;
+	opts->prefix = prefix;
+	opts->show_progress = -1;
+	opts->overlay_mode = -1;
 
-	git_config(git_checkout_config, &opts);
+	git_config(git_checkout_config, opts);
 
-	opts.track = BRANCH_TRACK_UNSPECIFIED;
+	opts->track = BRANCH_TRACK_UNSPECIFIED;
 
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
 	dwim_new_local_branch = !no_dwim_new_local_branch;
-	if (opts.show_progress < 0) {
-		if (opts.quiet)
-			opts.show_progress = 0;
+	if (opts->show_progress < 0) {
+		if (opts->quiet)
+			opts->show_progress = 0;
 		else
-			opts.show_progress = isatty(2);
+			opts->show_progress = isatty(2);
 	}
 
 	if (conflict_style) {
-		opts.merge = 1; /* implied */
+		opts->merge = 1; /* implied */
 		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
 	}
 
-	if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)
+	if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
 		die(_("-b, -B and --orphan are mutually exclusive"));
 
-	if (opts.overlay_mode == 1 && opts.patch_mode)
+	if (opts->overlay_mode == 1 && opts->patch_mode)
 		die(_("-p and --overlay are mutually exclusive"));
 
 	/*
@@ -1409,14 +1410,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	 * and new_branch_force and new_orphan_branch will tell us which one of
 	 * -b/-B/--orphan is being used.
 	 */
-	if (opts.new_branch_force)
-		opts.new_branch = opts.new_branch_force;
+	if (opts->new_branch_force)
+		opts->new_branch = opts->new_branch_force;
 
-	if (opts.new_orphan_branch)
-		opts.new_branch = opts.new_orphan_branch;
+	if (opts->new_orphan_branch)
+		opts->new_branch = opts->new_orphan_branch;
 
 	/* --track without -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];
 		if (!argc || !strcmp(argv0, "--"))
 			die(_("--track needs a branch name"));
@@ -1425,7 +1426,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		argv0 = strchr(argv0, '/');
 		if (!argv0 || !argv0[1])
 			die(_("missing branch name; try -b"));
-		opts.new_branch = argv0 + 1;
+		opts->new_branch = argv0 + 1;
 	}
 
 	/*
@@ -1444,56 +1445,56 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	if (argc) {
 		struct object_id rev;
 		int dwim_ok =
-			!opts.patch_mode &&
+			!opts->patch_mode &&
 			dwim_new_local_branch &&
-			opts.track == BRANCH_TRACK_UNSPECIFIED &&
-			!opts.new_branch;
+			opts->track == BRANCH_TRACK_UNSPECIFIED &&
+			!opts->new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
-					     &new_branch_info, &opts, &rev,
+					     &new_branch_info, opts, &rev,
 					     &dwim_remotes_matched);
 		argv += n;
 		argc -= n;
 	}
 
 	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)
+		if (opts->new_branch && argc == 1)
 			die(_("'%s' is not a commit and a branch '%s' cannot be created from it"),
-				argv[0], opts.new_branch);
+				argv[0], opts->new_branch);
 
-		if (opts.force_detach)
+		if (opts->force_detach)
 			die(_("git checkout: --detach does not take a path argument '%s'"),
 			    argv[0]);
 
-		if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
+		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.new_branch) {
+	if (opts->new_branch) {
 		struct strbuf buf = STRBUF_INIT;
 
-		if (opts.new_branch_force)
-			opts.branch_exists = validate_branchname(opts.new_branch, &buf);
+		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);
+			opts->branch_exists =
+				validate_new_branchname(opts->new_branch, &buf, 0);
 		strbuf_release(&buf);
 	}
 
 	UNLEAK(opts);
-	if (opts.patch_mode || opts.pathspec.nr) {
-		int ret = checkout_paths(&opts, new_branch_info.name);
+	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"
@@ -1512,6 +1513,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			       dwim_remotes_matched);
 		return ret;
 	} else {
-		return checkout_branch(&opts, &new_branch_info);
+		return checkout_branch(opts, &new_branch_info);
 	}
 }
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 06/19] checkout: move 'confict_style' and 'dwim_..' to checkout_opts

These local variables are referenced by struct option[]. This struct will soon be broken down, moved away and we can't rely on local variables anymore. Move these two to struct checkout_opts in preparation for that.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)
Show changes to builtin/checkout.c +10 −7
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 66f99e788d..5914dd3619 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -46,6 +46,8 @@ struct checkout_opts {
 	int show_progress;
 	int count_checkout_paths;
 	int overlay_mode;
+	int no_dwim_new_local_branch;
+
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
 	 * should be updated accordingly.
@@ -57,6 +59,7 @@ struct checkout_opts {
 	int new_branch_log;
 	enum branch_track track;
 	struct diff_options diff_options;
+	char *conflict_style;
 
 	int branch_exists;
 	const char *prefix;
@@ -1329,8 +1332,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	struct checkout_opts real_opts;
 	struct checkout_opts *opts = &real_opts;
 	struct branch_info new_branch_info;
-	char *conflict_style = NULL;
-	int dwim_new_local_branch, no_dwim_new_local_branch = 0;
+	int dwim_new_local_branch;
 	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
@@ -1355,12 +1357,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_STRING(0, "conflict", &conflict_style, N_("style"),
+		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
 			   N_("conflict style (merge or diff3)")),
 		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_BOOL(0, "no-guess", &no_dwim_new_local_branch,
+		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
 			 N_("do not second guess 'git checkout <no-such-branch>'")),
 		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
@@ -1378,6 +1380,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts->prefix = prefix;
 	opts->show_progress = -1;
 	opts->overlay_mode = -1;
+	opts->no_dwim_new_local_branch = 0;
 
 	git_config(git_checkout_config, opts);
 
@@ -1386,7 +1389,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
-	dwim_new_local_branch = !no_dwim_new_local_branch;
+	dwim_new_local_branch = !opts->no_dwim_new_local_branch;
 	if (opts->show_progress < 0) {
 		if (opts->quiet)
 			opts->show_progress = 0;
@@ -1394,9 +1397,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			opts->show_progress = isatty(2);
 	}
 
-	if (conflict_style) {
+	if (opts->conflict_style) {
 		opts->merge = 1; /* implied */
-		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
+		git_xmerge_config("merge.conflictstyle", opts->conflict_style, NULL);
 	}
 
 	if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
-- 
2.20.1.682.gd5861c6d90
Junio C Hamano· Feb 15, 2019, 22:38 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v2 06/19] checkout: move 'confict_style' and 'dwim_..' to checkout_opts

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
Show 7 quoted lines
> These local variables are referenced by struct option[]. This struct
> will soon be broken down, moved away and we can't rely on local
> variables anymore. Move these two to struct checkout_opts in
> preparation for that.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---

Up to this point all patches made sense to me. Thanks for working on this.

Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 07/19] checkout: split options[] array in three pieces

This is a preparation step for introducing new commands that do parts of what checkout does. There will be two new commands, one is about switching branches, detaching HEAD... one about checking out paths. These share the a subset of command line options. The rest of command line options are separate.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 82 +++++++++++++++++++++++++++++++++-------------
 parse-options-cb.c | 17 ++++++++++
 parse-options.h    |  3 +-
 3 files changed, 78 insertions(+), 24 deletions(-)
Show changes to 3 files +78 −24

builtin/checkout.c, parse-options-cb.c, parse-options.h

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 5914dd3619..8ea1349cce 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1327,15 +1327,31 @@ static int checkout_branch(struct checkout_opts *opts,
 	return switch_branches(opts, new_branch_info);
 }
 
-int cmd_checkout(int argc, const char **argv, const char *prefix)
+static struct option *add_common_options(struct checkout_opts *opts,
+					 struct option *prevopts)
 {
-	struct checkout_opts real_opts;
-	struct checkout_opts *opts = &real_opts;
-	struct branch_info new_branch_info;
-	int dwim_new_local_branch;
-	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
+		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
+			    "checkout", "control recursive updating of submodules",
+			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
+		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
+		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
+			   PARSE_OPT_NOCOMPLETE),
+		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
+		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
+			   N_("conflict style (merge or diff3)")),
+		OPT_END()
+	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+static struct option *add_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"),
@@ -1345,34 +1361,49 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
 			BRANCH_TRACK_EXPLICIT),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
+		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
+			   N_("update ignored files (default)"),
+			   PARSE_OPT_NOCOMPLETE),
+		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
+			 N_("second guess 'git checkout <no-such-branch>'")),
+		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
+			 N_("do not check if another worktree is holding the given ref")),
+		OPT_END()
+	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+static struct option *add_checkout_path_options(struct checkout_opts *opts,
+						struct option *prevopts)
+{
+	struct option options[] = {
 		OPT_SET_INT_F('2', "ours", &opts->writeout_stage,
 			      N_("checkout our version for unmerged files"),
 			      2, PARSE_OPT_NONEG),
 		OPT_SET_INT_F('3', "theirs", &opts->writeout_stage,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
-		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
-			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
-		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
-			   N_("update ignored files (default)"),
-			   PARSE_OPT_NOCOMPLETE),
-		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
-			   N_("conflict style (merge or diff3)")),
 		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_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
-			 N_("do not second guess 'git checkout <no-such-branch>'")),
-		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
-			 N_("do not check if another worktree is holding the given ref")),
-		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
-			    "checkout", "control recursive updating of submodules",
-			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
-		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
 		OPT_BOOL(0, "overlay", &opts->overlay_mode, N_("use overlay mode (default)")),
-		OPT_END(),
+		OPT_END()
 	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+int cmd_checkout(int argc, const char **argv, const char *prefix)
+{
+	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));
@@ -1386,6 +1417,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	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,
 			     PARSE_OPT_KEEP_DASHDASH);
 
diff --git a/parse-options-cb.c b/parse-options-cb.c
index e2f3eaed07..76eb5c211b 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -122,6 +122,23 @@ int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
 	return 0;
 }
 
+struct option *parse_options_dup(const struct option *o)
+{
+	struct option *opts;
+	int nr = 0;
+
+	while (o && o->type != OPTION_END) {
+		nr++;
+		o++;
+	}
+
+	ALLOC_ARRAY(opts, nr + 1);
+	memcpy(opts, o - nr, sizeof(*o) * nr);
+	memset(opts + nr, 0, sizeof(*opts));
+	opts[nr].type = OPTION_END;
+	return opts;
+}
+
 struct option *parse_options_concat(struct option *a, struct option *b)
 {
 	struct option *ret;
diff --git a/parse-options.h b/parse-options.h
index 14fe32428e..6846a5b1e0 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -237,7 +237,8 @@ extern int parse_options_step(struct parse_opt_ctx_t *ctx,
 
 extern int parse_options_end(struct parse_opt_ctx_t *ctx);
 
-extern struct option *parse_options_concat(struct option *a, struct option *b);
+struct option *parse_options_dup(const struct option *a);
+struct option *parse_options_concat(struct option *a, struct option *b);
 
 /*----- some often used options -----*/
 extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 08/19] checkout: split part of it to new command switch

"git checkout" doing too many things is a source of confusion for many users (and it even bites old timers sometimes). To remedy that, the command will be split into two new ones: switch and something-to-checkout-paths. The good old "git checkout" command is still here and will be until all (or most of users) are sick of it.

See the new man page for the final design of switch. The actual implementation though is still pretty much the same as "git checkout" and not completely aligned with the man page. Following patches will adjust their behavior to match the man page.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 .gitignore                        |   1 +
 Documentation/config/advice.txt   |  13 +-
 Documentation/config/branch.txt   |   4 +-
 Documentation/config/checkout.txt |   9 +-
 Documentation/config/diff.txt     |   3 +-
 Documentation/git-checkout.txt    |   4 +
 Documentation/git-switch.txt      | 257 ++++++++++++++++++++++++++++++
 Documentation/gitattributes.txt   |   3 +-
 Documentation/githooks.txt        |   8 +-
 Makefile                          |   1 +
 builtin.h                         |   1 +
 builtin/checkout.c                |  60 +++++--
 command-list.txt                  |   1 +
 git.c                             |   1 +
 14 files changed, 339 insertions(+), 27 deletions(-)
 create mode 100644 Documentation/git-switch.txt
Show changes to 14 files +339 −27

.gitignore, Documentation/config/advice.txt, Documentation/config/branch.txt, Documentation/config/checkout.txt, Documentation/config/diff.txt, Documentation/git-checkout.txt, Documentation/git-switch.txt, Documentation/gitattributes.txt, Documentation/githooks.txt, Makefile, builtin.h, builtin/checkout.c, command-list.txt, git.c

diff --git a/.gitignore b/.gitignore
index 0d77ea5894..8e2b89047f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -167,6 +167,7 @@
 /git-submodule
 /git-submodule--helper
 /git-svn
+/git-switch
 /git-symbolic-ref
 /git-tag
 /git-unpack-file
diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
index 88620429ea..239d479506 100644
--- a/Documentation/config/advice.txt
+++ b/Documentation/config/advice.txt
@@ -42,7 +42,8 @@ advice.*::
 		state in the output of linkgit:git-status[1], in
 		the template shown when writing commit messages in
 		linkgit:git-commit[1], and in the help message shown
-		by linkgit:git-checkout[1] when switching branch.
+		by linkgit:git-switch[1] or
+		linkgit:git-checkout[1] when switching branch.
 	statusUoption::
 		Advise to consider using the `-u` option to linkgit:git-status[1]
 		when the command takes more than 2 seconds to enumerate untracked
@@ -62,12 +63,14 @@ advice.*::
 		your information is guessed from the system username and
 		domain name.
 	detachedHead::
-		Advice shown when you used linkgit:git-checkout[1] to
-		move to the detach HEAD state, to instruct how to create
-		a local branch after the fact.
+		Advice shown when you used
+		linkgit:git-switch[1] or linkgit:git-checkout[1]
+		to move to the detach HEAD state, to instruct how to
+		create a local branch after the fact.
 	checkoutAmbiguousRemoteBranchName::
 		Advice shown when the argument to
-		linkgit:git-checkout[1] ambiguously resolves to a
+		linkgit:git-checkout[1] and linkgit:git-switch[1]
+		ambiguously resolves to a
 		remote tracking branch on more than one remote in
 		situations where an unambiguous argument would have
 		otherwise caused a remote-tracking branch to be
diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index 019d60ede2..8050466159 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -1,5 +1,5 @@
 branch.autoSetupMerge::
-	Tells 'git branch' and 'git checkout' to set up new branches
+	Tells 'git branch', 'git switch' and 'git checkout' to set up new branches
 	so that linkgit:git-pull[1] will appropriately merge from the
 	starting point branch. Note that even if this option is not set,
 	this behavior can be chosen per-branch using the `--track`
@@ -11,7 +11,7 @@ branch.autoSetupMerge::
 	branch. This option defaults to true.
 
 branch.autoSetupRebase::
-	When a new branch is created with 'git branch' or 'git checkout'
+	When a new branch is created with 'git branch', 'git switch' or 'git checkout'
 	that tracks another branch, this variable tells Git to set
 	up pull to rebase instead of merge (see "branch.<name>.rebase").
 	When `never`, rebase is never automatically set to true.
diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt
index 73380a8d86..fc211eca58 100644
--- a/Documentation/config/checkout.txt
+++ b/Documentation/config/checkout.txt
@@ -1,5 +1,6 @@
 checkout.defaultRemote::
-	When you run 'git checkout <something>' and only have one
+	When you run 'git checkout <something>'
+	or 'git switch <something>' and only have one
 	remote, it may implicitly fall back on checking out and
 	tracking e.g. 'origin/<something>'. This stops working as soon
 	as you have more than one remote with a '<something>'
@@ -8,8 +9,10 @@ checkout.defaultRemote::
 	disambiguation. The typical use-case is to set this to
 	`origin`.
 +
-Currently this is used by linkgit:git-checkout[1] when 'git checkout
-<something>' will checkout the '<something>' branch on another remote,
+Currently this is used by linkgit:git-switch[1] and
+linkgit:git-checkout[1] when 'git checkout <something>'
+or 'git switch <something>'
+will checkout the '<something>' branch on another remote,
 and by linkgit:git-worktree[1] when 'git worktree add' refers to a
 remote branch. This setting might be used for other checkout-like
 commands or functionality in the future.
diff --git a/Documentation/config/diff.txt b/Documentation/config/diff.txt
index e48bb987d7..b3b304ee12 100644
--- a/Documentation/config/diff.txt
+++ b/Documentation/config/diff.txt
@@ -78,7 +78,8 @@ diff.external::
 diff.ignoreSubmodules::
 	Sets the default value of --ignore-submodules. Note that this
 	affects only 'git diff' Porcelain, and not lower level 'diff'
-	commands such as 'git diff-files'. 'git checkout' also honors
+	commands such as 'git diff-files'. 'git checkout'
+	and 'git switch' also honor
 	this setting when reporting uncommitted changes. Setting it to
 	'all' disables the submodule summary normally shown by 'git commit'
 	and 'git status' when `status.submoduleSummary` is set unless it is
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 9ac01f7967..31c6cbef19 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -556,6 +556,10 @@ $ edit frotz
 $ git add frotz
 ------------
 
+SEE ALSO
+--------
+linkgit:git-switch[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
new file mode 100644
index 0000000000..1a4d156e3c
--- /dev/null
+++ b/Documentation/git-switch.txt
@@ -0,0 +1,257 @@
+git-switch(1)
+=============
+
+NAME
+----
+git-switch - Switch branches
+
+SYNOPSIS
+--------
+[verse]
+'git switch' [<options>] [--guess] <branch>
+'git switch' [<options>] --detach [<start-point>]
+'git switch' [<options>] (-c|-C|--orphan) <new-branch> [<start-point>]
+
+DESCRIPTION
+-----------
+Switch to a specified branch. The working tree and the index are
+updated to match the branch. All new commits will be added to the tip
+of this branch.
+
+Optionally a new branch could be created with either `-c` or `-C`, or
+detach the working tree from any branch with `--detach`, along with
+switching.
+
+Switching branches does not require a clean index and working tree
+(i.e. no differences compared to 'HEAD'). The operation is aborted
+however if the switch leads to loss of local changes, unless told
+otherwise.
+
+OPTIONS
+-------
+<branch>::
+	Branch to switch to.
+
+<new-branch>::
+	Name for the new branch.
+
+<start-point>::
+	The name of a commit at which to switch to before creating a
+	new branch or detach from.
++
+You can use the `"@{-N}"` syntax to refer to the N-th last
+branch/commit switched to "git switch" or "git checkout"
+operation. You may also specify `-` which is synonymous to `"@{-1}`.
++
+As a special case, you may use `"A...B"` as a shortcut for the merge
+base of `A` and `B` if there is exactly one merge base. You can leave
+out at most one of `A` and `B`, in which case it defaults to `HEAD`.
+
+-c <new-branch>::
+--create <new-branch>::
+	Create a new branch named <new-branch> starting at
+	<start-point> before switching to the branch. This is a
+	convenient shortcut for:
++
+------------
+$ git branch <new-branch>
+$ git switch <new-branch>
+------------
+
+-C <new-branch>::
+--force-create <new-branch>::
+	Similar to `--create` except that if <new-branch> already
+	exists, it will be reset to <start-point>. This is a
+	convenient shortcut for:
++
+------------
+$ git branch -f <new-branch>
+$ git switch <new-branch>
+------------
+
+-d::
+--detach::
+	Switch to a commit for inspection and discardable
+	experiments. See the "DETACHED HEAD" section in
+	linkgit:git-checkout[1] for details.
+
+-g::
+--guess::
+	If <branch> is not found but there does exist a tracking
+	branch in exactly one remote (call it <remote>) with a
+	matching name, treat as equivalent to
++
+------------
+$ git switch -c <branch> --track <remote>/<branch>
+------------
++
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to e.g. `checkout.defaultRemote=origin`
+to always checkout remote branches from there if `<branch>` is
+ambiguous but exists on the 'origin' remote. See also
+`checkout.defaultRemote` in linkgit:git-config[1].
+
+-q::
+--quiet::
+	Quiet, suppress feedback messages.
+
+-f::
+--force::
+	Proceed even if the index or the working tree differs from
+	HEAD. Both the index and working tree are restored to match
+	the switching target. This is used to throw away local
+	changes.
+
+--[no-]progress::
+	Progress status is reported on the standard error stream
+	by default when it is attached to a terminal, unless `--quiet`
+	is specified. This flag enables progress reporting even if not
+	attached to a terminal, regardless of `--quiet`.
+
+-t::
+--track::
+	When creating a new branch, set up "upstream" configuration.
+	`-c` is implied. See "--track" in linkgit:git-branch[1] for
+	details.
++
+If no `-c` option is given, the name of the new branch will be derived
+from the remote-tracking branch, by looking at the local part of the
+refspec configured for the corresponding remote, and then stripping
+the initial part up to the "*".  This would tell us to use "hack" as
+the local branch when branching off of "origin/hack" (or
+"remotes/origin/hack", or even "refs/remotes/origin/hack").  If the
+given name has no slash, or the above guessing results in an empty
+name, the guessing is aborted.  You can explicitly give a name with
+`-c` in such a case.
+
+--no-track::
+	Do not set up "upstream" configuration, even if the
+	branch.autoSetupMerge configuration variable is true.
+
+-m::
+--merge::
+	If you have local modifications to one or more files that are
+	different between the current branch and the branch to which
+	you are switching, the command refuses to switch branches in
+	order to preserve your modifications in context.  However,
+	with this option, a three-way merge between the current
+	branch, your working tree contents, and the new branch is
+	done, and you will be on the new branch.
++
+When a merge conflict happens, the index entries for conflicting
+paths are left unmerged, and you need to resolve the conflicts
+and mark the resolved paths with `git add` (or `git rm` if the merge
+should result in deletion of the path).
+
+--conflict=<style>::
+	The same as --merge option above, but changes the way the
+	conflicting hunks are presented, overriding the
+	merge.conflictStyle configuration variable.  Possible values are
+	"merge" (default) and "diff3" (in addition to what is shown by
+	"merge" style, shows the original contents).
+
+--orphan <new-branch>::
+	Create a new 'orphan' branch, named <new-branch>, started from
+	<start-point> and switch to it. See explanation of the same
+	option in linkgit:git-checkout[1] for details.
+
+--ignore-other-worktrees::
+	`git switch` refuses when the wanted ref is already
+	checked out by another worktree. This option makes it check
+	the ref out anyway. In other words, the ref can be held by
+	more than one worktree.
+
+--[no-]recurse-submodules::
+	Using --recurse-submodules will update the content of all initialized
+	submodules according to the commit recorded in the superproject. If
+	local modifications in a submodule would be overwritten the checkout
+	will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
+	is used, the work trees of submodules will not be updated.
+	Just like linkgit:git-submodule[1], this will detach the
+	submodules HEAD.
+
+EXAMPLES
+--------
+
+The following command switches to the "master" branch:
+
+------------
+$ git switch master
+------------
+
+After working in the wrong branch, switching to the correct branch
+would be done using:
+
+------------
+$ git switch mytopic
+------------
+
+However, your "wrong" branch and correct "mytopic" branch may differ
+in files that you have modified locally, in which case the above
+switch would fail like this:
+
+------------
+$ git switch mytopic
+error: You have local changes to 'frotz'; not switching branches.
+------------
+
+You can give the `-m` flag to the command, which would try a three-way
+merge:
+
+------------
+$ git switch -m mytopic
+Auto-merging frotz
+------------
+
+After this three-way merge, the local modifications are _not_
+registered in your index file, so `git diff` would show you what
+changes you made since the tip of the new branch.
+
+To switch back to the previous branch before we switched to mytopic
+(i.e. "master" branch):
+
+------------
+$ git switch -
+------------
+
+You can grow a new branch from any commit. For example, switch to
+"HEAD~3" and create branch "fixup":
+
+------------
+$ git switch -c fixup HEAD~3
+Switched to a new branch 'fixup'
+------------
+
+If you want to start a new branch from a remote branch of the same
+name, use `-g`:
+
+------------
+$ git switch -g new-topic
+Branch 'new-topic' set up to track remote branch 'new-topic' from 'origin'
+Switched to a new branch 'new-topic'
+------------
+
+To check out commit "HEAD~3" for temporary inspection or experiment
+without creating a new branch:
+
+------------
+$ git switch --detach HEAD~3
+HEAD is now at 9fc9555312 Merge branch 'cc/shared-index-permbits'
+------------
+
+If it turns out whatever you have done is worth keeping, you can
+always create a new name for it (without switching away):
+
+------------
+$ git switch -c good-surprises
+------------
+
+SEE ALSO
+--------
+linkgit:git-checkout[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index b8392fc330..5731caa4c6 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -112,7 +112,8 @@ Checking-out and checking-in
 
 These attributes affect how the contents stored in the
 repository are copied to the working tree files when commands
-such as 'git checkout' and 'git merge' run.  They also affect how
+such as 'git switch', 'git checkout'  and 'git merge' run.
+They also affect how
 Git stores the contents you prepare in the working tree in the
 repository upon 'git add' and 'git commit'.
 
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 959044347e..e257a7810a 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -161,12 +161,13 @@ rebased, and is not set when rebasing the current branch.
 post-checkout
 ~~~~~~~~~~~~~
 
-This hook is invoked when a linkgit:git-checkout[1] is run after having updated the
+This hook is invoked when a linkgit:git-checkout[1] or
+linkgit:git-switch[1] is run after having updated the
 worktree.  The hook is given three parameters: the ref of the previous HEAD,
 the ref of the new HEAD (which may or may not have changed), and a flag
 indicating whether the checkout was a branch checkout (changing branches,
 flag=1) or a file checkout (retrieving a file from the index, flag=0).
-This hook cannot affect the outcome of `git checkout`.
+This hook cannot affect the outcome of `git switch` or `git checkout`.
 
 It is also run after linkgit:git-clone[1], unless the `--no-checkout` (`-n`) option is
 used. The first parameter given to the hook is the null-ref, the second the
@@ -402,7 +403,8 @@ exit with a zero status.
 For example, the hook can simply run `git read-tree -u -m HEAD "$1"`
 in order to emulate `git fetch` that is run in the reverse direction
 with `git push`, as the two-tree form of `git read-tree -u -m` is
-essentially the same as `git checkout` that switches branches while
+essentially the same as `git switch` or `git checkout`
+that switches branches while
 keeping the local changes in the working tree that do not interfere
 with the difference between the branches.
 
diff --git a/Makefile b/Makefile
index 6e8d017e8e..0e5b29390c 100644
--- a/Makefile
+++ b/Makefile
@@ -789,6 +789,7 @@ BUILT_INS += git-merge-subtree$X
 BUILT_INS += git-show$X
 BUILT_INS += git-stage$X
 BUILT_INS += git-status$X
+BUILT_INS += git-switch$X
 BUILT_INS += git-whatchanged$X
 
 # what 'all' will build and 'install' will install in gitexecdir,
diff --git a/builtin.h b/builtin.h
index 6538932e99..c64e44450e 100644
--- a/builtin.h
+++ b/builtin.h
@@ -227,6 +227,7 @@ extern int cmd_show_index(int argc, const char **argv, const char *prefix);
 extern int cmd_status(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
+extern int cmd_switch(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 8ea1349cce..2ac53b4302 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -33,6 +33,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;
@@ -1396,33 +1401,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;
@@ -1555,3 +1552,42 @@ 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 = NULL;
+	int ret;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.no_dwim_new_local_branch = 0;
+
+	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);
+
+	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;
+	int ret;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.no_dwim_new_local_branch = 0;
+
+	options = parse_options_dup(options);
+	options = add_common_options(&opts, options);
+	options = add_switch_branch_options(&opts, options);
+
+	ret = checkout_main(argc, argv, prefix, &opts,
+			    options, switch_branch_usage);
+	FREE_AND_NULL(options);
+	return ret;
+}
diff --git a/command-list.txt b/command-list.txt
index 3a9af104b5..13317f47d4 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -171,6 +171,7 @@ git-status                              mainporcelain           info
 git-stripspace                          purehelpers
 git-submodule                           mainporcelain
 git-svn                                 foreignscminterface
+git-switch                              mainporcelain           history
 git-symbolic-ref                        plumbingmanipulators
 git-tag                                 mainporcelain           history
 git-unpack-file                         plumbinginterrogators
diff --git a/git.c b/git.c
index 0ce0e13f0f..31b8e8d6a1 100644
--- a/git.c
+++ b/git.c
@@ -558,6 +558,7 @@ static struct cmd_struct commands[] = {
 	{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
 	{ "stripspace", cmd_stripspace },
 	{ "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
+	{ "switch", cmd_switch, RUN_SETUP | NEED_WORK_TREE },
 	{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
 	{ "tag", cmd_tag, RUN_SETUP | DELAY_PAGER_CONFIG },
 	{ "unpack-file", cmd_unpack_file, RUN_SETUP | NO_PARSEOPT },
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 09/19] switch: better names for -b and -B

The shortcut of these options do not make much sense when used with switch. And their descriptions are also tied to checkout out. Move -b/-B to cmd_checkout() and new -c/-C with the same functionality in cmd_switch_branch()

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 32 +++++++++++++++++++++-----------
 1 file changed, 21 insertions(+), 11 deletions(-)
Show changes to builtin/checkout.c +21 −11
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2ac53b4302..f6de232922 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1353,14 +1353,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"),
@@ -1556,15 +1552,22 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
 	struct checkout_opts opts;
-	struct option *options = NULL;
+	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_END()
+	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
 
-	options = parse_options_dup(options);
+	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
-	options = add_switch_branch_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,
@@ -1577,14 +1580,21 @@ 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_END()
+	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
 
-	options = parse_options_dup(options);
+	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-	options = add_switch_branch_options(&opts, options);
+	options = add_common_switch_branch_options(&opts, options);
 
 	ret = checkout_main(argc, argv, prefix, &opts,
 			    options, switch_branch_usage);
-- 
2.20.1.682.gd5861c6d90
Eric Sunshine· Feb 11, 2019, 04:02 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v2 09/19] switch: better names for -b and -B

On Fri, Feb 8, 2019 at 4:05 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> The shortcut of these options do not make much sense when used with
> switch. And their descriptions are also tied to checkout
> out. [...]
"checkout out"?
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 10/19] switch: remove -l

This option is ancient. Nowadays reflog is enabled by default and automatically created for new branches. Keep it in git-checkout only.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/checkout.c +1 −1
diff --git a/builtin/checkout.c b/builtin/checkout.c
index f6de232922..f7d8fa7caa 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1357,7 +1357,6 @@ static struct option *add_common_switch_branch_options(
 	struct checkout_opts *opts, struct option *prevopts)
 {
 	struct option options[] = {
-		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),
@@ -1558,6 +1557,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			   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;
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 11/19] switch: stop accepting pathspec

This command is about switching branch (or creating a new one) and should not accept pathspec. This helps simplify ambiguation handling. The other two ("git checkout" and "git restore") of course do accept pathspec as before.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)
Show changes to builtin/checkout.c +15 −5
diff --git a/builtin/checkout.c b/builtin/checkout.c
index f7d8fa7caa..4822d5c5d7 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -52,6 +52,7 @@ struct checkout_opts {
 	int count_checkout_paths;
 	int overlay_mode;
 	int no_dwim_new_local_branch;
+	int accept_pathspec;
 
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
@@ -1162,10 +1163,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;
 		}
@@ -1199,11 +1206,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) {
@@ -1248,7 +1256,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--;
@@ -1564,6 +1572,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
+	opts.accept_pathspec = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1591,6 +1600,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
+	opts.accept_pathspec = 0;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 12/19] switch: reject "do nothing" case

"git checkout" can be executed without any arguments. What it does is not exactly great: it switches from HEAD to HEAD and shows worktree modification as a side effect.

Make switch reject this case. Just use "git status" if you want that side effect. For switch, you have to either

- really switch a branch
- (explicitly) detach from the current branch
- create a new branch
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 9 +++++++++
 1 file changed, 9 insertions(+)
Show changes to builtin/checkout.c +9 −0
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 4822d5c5d7..86e524f7c1 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -53,6 +53,7 @@ struct checkout_opts {
 	int overlay_mode;
 	int no_dwim_new_local_branch;
 	int accept_pathspec;
+	int switch_branch_doing_nothing_is_ok;
 
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
@@ -1319,6 +1320,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;
@@ -1572,6 +1579,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	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);
@@ -1601,6 +1609,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	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);
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 13/19] switch: only allow explicit detached HEAD

"git checkout <commit>" will checkout the commit in question and detach HEAD from the current branch. It is naturally a right thing to do once you get git references. But detached HEAD is a scary concept to new users because we show a lot of warnings and stuff, and it could be hard to get out of (until you know better).

To keep switch a bit more friendly to new users, we only allow entering detached HEAD mode when --detach is given. "git switch" must take a branch (unless you create a new branch, then of course switch can take any commit-ish)

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 11 +++++++++++
 1 file changed, 11 insertions(+)
Show changes to builtin/checkout.c +11 −0
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 86e524f7c1..d6c968f5f3 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -44,6 +44,7 @@ struct checkout_opts {
 	int merge;
 	int force;
 	int force_detach;
+	int implicit_detach;
 	int writeout_stage;
 	int overwrite_ignore;
 	int ignore_skipworktree;
@@ -1326,6 +1327,14 @@ static int checkout_branch(struct checkout_opts *opts,
 	    !opts->force_detach)
 		die(_("missing branch or commit argument"));
 
+	if (!opts->implicit_detach &&
+	    !opts->force_detach &&
+	    !opts->new_branch &&
+	    !opts->new_branch_force &&
+	    new_branch_info->name &&
+	    !new_branch_info->path)
+		die(_("a branch is expected, got %s"), new_branch_info->name);
+
 	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
 	    !opts->ignore_other_worktrees) {
 		int flag;
@@ -1581,6 +1590,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.no_dwim_new_local_branch = 0;
 	opts.switch_branch_doing_nothing_is_ok = 1;
 	opts.accept_pathspec = 1;
+	opts.implicit_detach = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1610,6 +1620,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	opts.no_dwim_new_local_branch = 0;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
+	opts.implicit_detach = 0;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 14/19] switch: add short option for --detach

"git checkout" automatically detaches branches and --detach is not that useful (--no-detach is more likely). But for "switch", you may want to use it more often once you're used to detached HEAD. This of course adds -d to git-checkout but it does not harm (yet?) to do it.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/checkout.c +1 −1
diff --git a/builtin/checkout.c b/builtin/checkout.c
index d6c968f5f3..a5284391a9 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1381,7 +1381,7 @@ static struct option *add_common_switch_branch_options(
 	struct checkout_opts *opts, struct option *prevopts)
 {
 	struct option options[] = {
-		OPT_BOOL(0, "detach", &opts->force_detach, N_("detach HEAD at named commit")),
+		OPT_BOOL('d', "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),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 15/19] switch: no implicit dwim, use --guess to dwim

Similar to automatic detach, this behavior could be confusing because it can sometimes create a new branch without a user asking it to, especially when the user is still not aware about this feature.

In the future, perhaps we could have a config key to disable these safety nets and let 'switch' do automatic detach or dwim again. But that will be opt-in after the user knows what is what. For now give a short option if you want to use it often.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt | 37 ++++++++++++++++++++--------------
 builtin/checkout.c             | 16 +++++++--------
 2 files changed, 30 insertions(+), 23 deletions(-)
Show changes to 2 files +30 −22

Documentation/git-checkout.txt, builtin/checkout.c

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 31c6cbef19..bcb7822c27 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -31,22 +31,13 @@ branch.
 	<branch>.
 +
 If <branch> is not found but there does exist a tracking branch in
-exactly one remote (call it <remote>) with a matching name, treat as
-equivalent to
+exactly one remote (call it <remote>) with a matching name and
+--no-guess is not specified, treat as equivalent to
 +
 ------------
 $ git checkout -b <branch> --track <remote>/<branch>
 ------------
 +
-If the branch exists in multiple remotes and one of them is named by
-the `checkout.defaultRemote` configuration variable, we'll use that
-one for the purposes of disambiguation, even if the `<branch>` isn't
-unique across all remotes. Set it to
-e.g. `checkout.defaultRemote=origin` to always checkout remote
-branches from there if `<branch>` is ambiguous but exists on the
-'origin' remote. See also `checkout.defaultRemote` in
-linkgit:git-config[1].
-+
 You could omit <branch>, in which case the command degenerates to
 "check out the current branch", which is a glorified no-op with
 rather expensive side-effects to show only the tracking information,
@@ -182,6 +173,26 @@ explicitly give a name with `-b` in such a case.
 	Do not set up "upstream" configuration, even if the
 	branch.autoSetupMerge configuration variable is true.
 
+--[no-]guess::
+	If <branch> is not found but there does exist a tracking
+	branch in exactly one remote (call it <remote>) with a
+	matching name, treat as equivalent to
++
+------------
+$ git checkout -b <branch> --track <remote>/<branch>
+------------
++
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to
+e.g. `checkout.defaultRemote=origin` to always checkout remote
+branches from there if `<branch>` is ambiguous but exists on the
+'origin' remote. See also `checkout.defaultRemote` in
+linkgit:git-config[1].
++
+Use `--no-guess` to disable this.
+
 -l::
 	Create the new branch's reflog; see linkgit:git-branch[1] for
 	details.
@@ -284,10 +295,6 @@ Note that this option uses the no overlay mode by default (see also
 	Just like linkgit:git-submodule[1], this will detach the
 	submodules HEAD.
 
---no-guess::
-	Do not attempt to create a branch if a remote tracking branch
-	of the same name exists.
-
 --[no-]overlay::
 	In the default overlay mode, `git checkout` never
 	removes files from the index or the working tree.  When
diff --git a/builtin/checkout.c b/builtin/checkout.c
index a5284391a9..a49ab35b7d 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -52,7 +52,7 @@ struct checkout_opts {
 	int show_progress;
 	int count_checkout_paths;
 	int overlay_mode;
-	int no_dwim_new_local_branch;
+	int dwim_new_local_branch;
 	int accept_pathspec;
 	int switch_branch_doing_nothing_is_ok;
 
@@ -1388,8 +1388,6 @@ static struct option *add_common_switch_branch_options(
 		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
-			 N_("second guess 'git checkout <no-such-branch>'")),
 		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
 		OPT_END()
@@ -1426,7 +1424,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 {
 	struct branch_info new_branch_info;
 	int dwim_remotes_matched = 0;
-	int dwim_new_local_branch;
 
 	memset(&new_branch_info, 0, sizeof(new_branch_info));
 	opts->overwrite_ignore = 1;
@@ -1441,7 +1438,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 	argc = parse_options(argc, argv, prefix, options, usagestr,
 			     PARSE_OPT_KEEP_DASHDASH);
 
-	dwim_new_local_branch = !opts->no_dwim_new_local_branch;
 	if (opts->show_progress < 0) {
 		if (opts->quiet)
 			opts->show_progress = 0;
@@ -1501,7 +1497,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 		struct object_id rev;
 		int dwim_ok =
 			!opts->patch_mode &&
-			dwim_new_local_branch &&
+			opts->dwim_new_local_branch &&
 			opts->track == BRANCH_TRACK_UNSPECIFIED &&
 			!opts->new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
@@ -1582,12 +1578,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		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, "guess", &opts.dwim_new_local_branch,
+			 N_("second guess 'git checkout <no-such-branch>' (default)")),
 		OPT_END()
 	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
-	opts.no_dwim_new_local_branch = 0;
+	opts.dwim_new_local_branch = 1;
 	opts.switch_branch_doing_nothing_is_ok = 1;
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
@@ -1612,12 +1610,14 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 			   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('g', "guess", &opts.dwim_new_local_branch,
+			 N_("second guess 'git switch <no-such-branch>'")),
 		OPT_END()
 	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
-	opts.no_dwim_new_local_branch = 0;
+	opts.dwim_new_local_branch = 0;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
 	opts.implicit_detach = 0;
-- 
2.20.1.682.gd5861c6d90
Eric Sunshine· Feb 10, 2019, 08:37 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v2 15/19] switch: no implicit dwim, use --guess to dwim

On Fri, Feb 8, 2019 at 4:05 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 11 quoted lines
> Similar to automatic detach, this behavior could be confusing because
> it can sometimes create a new branch without a user asking it to,
> especially when the user is still not aware about this feature.
> [...]
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
> diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
> @@ -31,22 +31,13 @@ branch.
>  If <branch> is not found but there does exist a tracking branch in
> +exactly one remote (call it <remote>) with a matching name and
> +--no-guess is not specified, treat as equivalent to

For consistency with typesetting in the rest of this document, you probably want s/--no-guess/`&`/, though probably not worth a re-roll, though, if this is the only issue with v2.

Show 5 quoted lines
> @@ -182,6 +173,26 @@ explicitly give a name with `-b` in such a case.
> +--[no-]guess::
> +       If <branch> is not found but there does exist a tracking
> +       branch in exactly one remote (call it <remote>) with a
> +       matching name, treat as equivalent to
Most places in this document typeset these as `<branch>` and `<remote>`.
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 16/19] switch: no worktree status unless real branch switch happens

When we switch from one branch to another, it makes sense to show a summary of local changes since there could be conflicts, or some files left modified.... When switch is used solely for creating a new branch (and "switch" to the same commit) or detaching, we don't really need to show anything.

"git checkout" does it anyway for historical reasons. But we can start with a clean slate with switch and don't have to.

This essentially reverts fa655d8411 (checkout: optimize "git checkout -b <new_branch>" - 2018-08-16) and make it default for switch, but also for -B and --detach. Users of big repos are encouraged to move to switch.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/config/checkout.txt |   8 --
 builtin/checkout.c                | 127 +++---------------------------
 t/t1090-sparse-checkout-scope.sh  |  14 ----
 3 files changed, 9 insertions(+), 140 deletions(-)
Show changes to 3 files +9 −140

Documentation/config/checkout.txt, builtin/checkout.c, t/t1090-sparse-checkout-scope.sh

diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt
index fc211eca58..2fe24049fe 100644
--- a/Documentation/config/checkout.txt
+++ b/Documentation/config/checkout.txt
@@ -17,14 +17,6 @@ and by linkgit:git-worktree[1] when 'git worktree add' refers to a
 remote branch. This setting might be used for other checkout-like
 commands or functionality in the future.
 
-checkout.optimizeNewBranch::
-	Optimizes the performance of "git checkout -b <new_branch>" when
-	using sparse-checkout.  When set to true, git will not update the
-	repo based on the current sparse-checkout settings.  This means it
-	will not update the skip-worktree bit in the index nor add/remove
-	files in the working directory to reflect the current sparse checkout
-	settings nor will it show the local changes.
-
 checkout.overlayMode::
 	In the default overlay mode, `git checkout` never
 	removes files from the index or the working tree.  When
diff --git a/builtin/checkout.c b/builtin/checkout.c
index a49ab35b7d..21a3fe3fb8 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -25,8 +25,6 @@
 #include "submodule.h"
 #include "advice.h"
 
-static int checkout_optimize_new_branch;
-
 static const char * const checkout_usage[] = {
 	N_("git checkout [<options>] <branch>"),
 	N_("git checkout [<options>] [<branch>] -- <file>..."),
@@ -55,11 +53,7 @@ struct checkout_opts {
 	int dwim_new_local_branch;
 	int accept_pathspec;
 	int switch_branch_doing_nothing_is_ok;
-
-	/*
-	 * If new checkout options are added, skip_merge_working_tree
-	 * should be updated accordingly.
-	 */
+	int only_merge_on_switching_branches;
 
 	const char *new_branch;
 	const char *new_branch_force;
@@ -556,104 +550,6 @@ static void setup_branch_path(struct branch_info *branch)
 	branch->path = strbuf_detach(&buf, NULL);
 }
 
-/*
- * Skip merging the trees, updating the index and working directory if and
- * only if we are creating a new branch via "git checkout -b <new_branch>."
- */
-static int skip_merge_working_tree(const struct checkout_opts *opts,
-	const struct branch_info *old_branch_info,
-	const struct branch_info *new_branch_info)
-{
-	/*
-	 * Do the merge if sparse checkout is on and the user has not opted in
-	 * to the optimized behavior
-	 */
-	if (core_apply_sparse_checkout && !checkout_optimize_new_branch)
-		return 0;
-
-	/*
-	 * We must do the merge if we are actually moving to a new commit.
-	 */
-	if (!old_branch_info->commit || !new_branch_info->commit ||
-		!oideq(&old_branch_info->commit->object.oid,
-		       &new_branch_info->commit->object.oid))
-		return 0;
-
-	/*
-	 * opts->patch_mode cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * opts->quiet only impacts output so doesn't require a merge
-	 */
-
-	/*
-	 * Honor the explicit request for a three-way merge or to throw away
-	 * local changes
-	 */
-	if (opts->merge || opts->force)
-		return 0;
-
-	/*
-	 * --detach is documented as "updating the index and the files in the
-	 * working tree" but this optimization skips those steps so fall through
-	 * to the regular code path.
-	 */
-	if (opts->force_detach)
-		return 0;
-
-	/*
-	 * opts->writeout_stage cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * Honor the explicit ignore requests
-	 */
-	if (!opts->overwrite_ignore || opts->ignore_skipworktree ||
-		opts->ignore_other_worktrees)
-		return 0;
-
-	/*
-	 * opts->show_progress only impacts output so doesn't require a merge
-	 */
-
-	/*
-	 * opts->overlay_mode cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * If we aren't creating a new branch any changes or updates will
-	 * happen in the existing branch.  Since that could only be updating
-	 * the index and working directory, we don't want to skip those steps
-	 * or we've defeated any purpose in running the command.
-	 */
-	if (!opts->new_branch)
-		return 0;
-
-	/*
-	 * new_branch_force is defined to "create/reset and checkout a branch"
-	 * so needs to go through the merge to do the reset
-	 */
-	if (opts->new_branch_force)
-		return 0;
-
-	/*
-	 * A new orphaned branch requrires the index and the working tree to be
-	 * adjusted to <start_point>
-	 */
-	if (opts->new_orphan_branch)
-		return 0;
-
-	/*
-	 * Remaining variables are not checkout options but used to track state
-	 */
-
-	return 1;
-}
-
 static int merge_working_tree(const struct checkout_opts *opts,
 			      struct branch_info *old_branch_info,
 			      struct branch_info *new_branch_info,
@@ -1011,6 +907,8 @@ static int switch_branches(const struct checkout_opts *opts,
 	void *path_to_free;
 	struct object_id rev;
 	int flag, writeout_error = 0;
+	int do_merge = 1;
+
 	memset(&old_branch_info, 0, sizeof(old_branch_info));
 	old_branch_info.path = path_to_free = resolve_refdup("HEAD", 0, &rev, &flag);
 	if (old_branch_info.path)
@@ -1027,16 +925,12 @@ static int switch_branches(const struct checkout_opts *opts,
 		if (!new_branch_info->commit)
 			die(_("You are on a branch yet to be born"));
 		parse_commit_or_die(new_branch_info->commit);
+
+		if (opts->only_merge_on_switching_branches)
+			do_merge = 0;
 	}
 
-	/* optimize the "checkout -b <new_branch> path */
-	if (skip_merge_working_tree(opts, &old_branch_info, new_branch_info)) {
-		if (!checkout_optimize_new_branch && !opts->quiet) {
-			if (read_cache_preload(NULL) < 0)
-				return error(_("index file corrupt"));
-			show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
-		}
-	} else {
+	if (do_merge) {
 		ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
 		if (ret) {
 			free(path_to_free);
@@ -1058,11 +952,6 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
 {
 	struct checkout_opts *opts = cb;
 
-	if (!strcmp(var, "checkout.optimizenewbranch")) {
-		checkout_optimize_new_branch = git_config_bool(var, value);
-		return 0;
-	}
-
 	if (!strcmp(var, "checkout.overlaymode")) {
 		opts->overlay_mode = git_config_bool(var, value);
 		return 0;
@@ -1587,6 +1476,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	memset(&opts, 0, sizeof(opts));
 	opts.dwim_new_local_branch = 1;
 	opts.switch_branch_doing_nothing_is_ok = 1;
+	opts.only_merge_on_switching_branches = 0;
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
 
@@ -1620,6 +1510,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	opts.dwim_new_local_branch = 0;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
+	opts.only_merge_on_switching_branches = 1;
 	opts.implicit_detach = 0;
 
 	options = parse_options_dup(switch_options);
diff --git a/t/t1090-sparse-checkout-scope.sh b/t/t1090-sparse-checkout-scope.sh
index 090b7fc3d3..40cc004326 100755
--- a/t/t1090-sparse-checkout-scope.sh
+++ b/t/t1090-sparse-checkout-scope.sh
@@ -31,20 +31,6 @@ test_expect_success 'perform sparse checkout of master' '
 	test_path_is_file c
 '
 
-test_expect_success 'checkout -b checkout.optimizeNewBranch interaction' '
-	cp .git/info/sparse-checkout .git/info/sparse-checkout.bak &&
-	test_when_finished "
-		mv -f .git/info/sparse-checkout.bak .git/info/sparse-checkout
-		git checkout master
-	" &&
-	echo "/b" >>.git/info/sparse-checkout &&
-	test "$(git ls-files -t b)" = "S b" &&
-	git -c checkout.optimizeNewBranch=true checkout -b fast &&
-	test "$(git ls-files -t b)" = "S b" &&
-	git checkout -b slow &&
-	test "$(git ls-files -t b)" = "H b"
-'
-
 test_expect_success 'merge feature branch into sparse checkout of master' '
 	git merge feature &&
 	test_path_is_file a &&
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:03 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 17/19] t: add tests for switch

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 t/t2060-switch.sh | 87 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 87 insertions(+)
 create mode 100755 t/t2060-switch.sh
Show changes to t/t2060-switch.sh +87 −0
diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
new file mode 100755
index 0000000000..1e1e834c1b
--- /dev/null
+++ b/t/t2060-switch.sh
@@ -0,0 +1,87 @@
+#!/bin/sh
+
+test_description='switch basic functionality'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit first &&
+	git branch first-branch &&
+	test_commit second &&
+	test_commit third &&
+	git remote add origin nohost:/nopath &&
+	git update-ref refs/remotes/origin/foo first-branch
+'
+
+test_expect_success 'switch branch no arguments' '
+	test_must_fail git switch
+'
+
+test_expect_success 'switch branch' '
+	git switch first-branch &&
+	test_path_is_missing second.t
+'
+
+test_expect_success 'switch to a commit' '
+	test_must_fail git switch master^{commit}
+'
+
+test_expect_success 'switch and detach' '
+	test_when_finished git switch master &&
+	git switch --detach master^{commit} &&
+	test_must_fail git symbolic-ref HEAD
+'
+
+test_expect_success 'switch and detach current branch' '
+	test_when_finished git switch master &&
+	git switch master &&
+	git switch --detach &&
+	test_must_fail git symbolic-ref HEAD
+'
+
+test_expect_success 'switch and create branch' '
+	test_when_finished git switch master &&
+	git switch -c temp master^ &&
+	test_cmp_rev master^ refs/heads/temp &&
+	echo refs/heads/temp >expected-branch &&
+	git symbolic-ref HEAD >actual-branch &&
+	test_cmp expected-branch actual-branch
+'
+
+test_expect_success 'force create branch from HEAD' '
+	test_when_finished git switch master &&
+	git switch --detach master &&
+	git switch -C temp &&
+	test_cmp_rev master refs/heads/temp &&
+	echo refs/heads/temp >expected-branch &&
+	git symbolic-ref HEAD >actual-branch &&
+	test_cmp expected-branch actual-branch
+'
+
+test_expect_success 'new orphan branch' '
+	test_when_finished git switch master &&
+	git switch --orphan new-orphan master^ &&
+	test_commit orphan &&
+	git cat-file commit refs/heads/new-orphan >commit &&
+	! grep ^parent commit
+'
+
+test_expect_success 'switching ignores file of same branch name' '
+	test_when_finished git switch master &&
+	: >first-branch &&
+	git switch first-branch &&
+	echo refs/heads/first-branch >expected &&
+	git symbolic-ref HEAD >actual &&
+	test_commit expected actual
+'
+
+test_expect_success 'guess and create branch ' '
+	test_when_finished git switch master &&
+	test_must_fail git switch foo &&
+	git switch --guess foo &&
+	echo refs/heads/foo >expected &&
+	git symbolic-ref HEAD >actual &&
+	test_cmp expected actual
+'
+
+test_done
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:04 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 18/19] completion: support switch

Completion support for --guess could be made better. If no --detach is given, we should only provide a list of refs/heads/* and dwim ones, not the entire ref space. But I still can't penetrate that __git_refs() function yet.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 contrib/completion/git-completion.bash | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)
Show changes to contrib/completion/git-completion.bash +26 −0
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 499e56f83d..891abb72d7 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -2126,6 +2126,32 @@ _git_status ()
 	__git_complete_index_file "$complete_opt"
 }
 
+_git_switch ()
+{
+	case "$cur" in
+	--conflict=*)
+		__gitcomp "diff3 merge" "" "${cur##--conflict=}"
+		;;
+	--*)
+		__gitcomp_builtin switch
+		;;
+	*)
+		# check if ---guess was specified to enable DWIM mode
+		local track_opt= only_local_ref=n
+		if [ -n "$(__git_find_on_cmdline "-g --guess")" ]; then
+			track_opt='--track'
+		elif [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then
+			only_local_ref=y
+		fi
+		if [ $only_local_ref = y ]; then
+			__gitcomp_direct "$(__git_heads "" "$cur" " ")"
+		else
+			__git_complete_refs $track_opt
+		fi
+		;;
+	esac
+}
+
 __git_config_get_set_variables ()
 {
 	local prevword word config_file= c=$cword
-- 
2.20.1.682.gd5861c6d90
SZEDER Gábor· Feb 8, 2019, 14:19 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v2 18/19] completion: support switch

On Fri, Feb 08, 2019 at 04:04:00PM +0700, Nguyễn Thái Ngọc Duy wrote:
Show 29 quoted lines
> Completion support for --guess could be made better. If no --detach is
> given, we should only provide a list of refs/heads/* and dwim ones,
> not the entire ref space. But I still can't penetrate that
> __git_refs() function yet.
> 
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  contrib/completion/git-completion.bash | 26 ++++++++++++++++++++++++++
>  1 file changed, 26 insertions(+)
> 
> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
> index 499e56f83d..891abb72d7 100644
> --- a/contrib/completion/git-completion.bash
> +++ b/contrib/completion/git-completion.bash
> @@ -2126,6 +2126,32 @@ _git_status ()
>  	__git_complete_index_file "$complete_opt"
>  }
>  
> +_git_switch ()
> +{
> +	case "$cur" in
> +	--conflict=*)
> +		__gitcomp "diff3 merge" "" "${cur##--conflict=}"
> +		;;
> +	--*)
> +		__gitcomp_builtin switch
> +		;;
> +	*)
> +		# check if ---guess was specified to enable DWIM mode
Nit: s/---/--/
Show 6 quoted lines
> +		local track_opt= only_local_ref=n
> +		if [ -n "$(__git_find_on_cmdline "-g --guess")" ]; then
> +			track_opt='--track'
> +		elif [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then
> +			only_local_ref=y
> +		fi

Could these two options be used together? I think they could. If that's the case, then the two conditions shouldn't be chained with elif, but should be two separate if statements (even eliminating $only_local_ref, while at it?). If that's not the case, then the two __git_find_on_cmdline() calls could be combined into one, and a case statement could act according the option found, sparing one of the subshells from the two.

Show 15 quoted lines
> +		if [ $only_local_ref = y ]; then
> +			__gitcomp_direct "$(__git_heads "" "$cur" " ")"
> +		else
> +			__git_complete_refs $track_opt
> +		fi
> +		;;
> +	esac
> +}
> +
>  __git_config_get_set_variables ()
>  {
>  	local prevword word config_file= c=$cword
> -- 
> 2.20.1.682.gd5861c6d90
> 
Duy Nguyen· Feb 9, 2019, 05:30 UTC · re: SZEDER Gábor · lore

Re: [PATCH v2 18/19] completion: support switch

On Fri, Feb 8, 2019 at 9:19 PM SZEDER Gábor <szeder.dev@gmail.com> wrote:
Show 42 quoted lines
>
> On Fri, Feb 08, 2019 at 04:04:00PM +0700, Nguyễn Thái Ngọc Duy wrote:
> > Completion support for --guess could be made better. If no --detach is
> > given, we should only provide a list of refs/heads/* and dwim ones,
> > not the entire ref space. But I still can't penetrate that
> > __git_refs() function yet.
> >
> > Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> > ---
> >  contrib/completion/git-completion.bash | 26 ++++++++++++++++++++++++++
> >  1 file changed, 26 insertions(+)
> >
> > diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
> > index 499e56f83d..891abb72d7 100644
> > --- a/contrib/completion/git-completion.bash
> > +++ b/contrib/completion/git-completion.bash
> > @@ -2126,6 +2126,32 @@ _git_status ()
> >       __git_complete_index_file "$complete_opt"
> >  }
> >
> > +_git_switch ()
> > +{
> > +     case "$cur" in
> > +     --conflict=*)
> > +             __gitcomp "diff3 merge" "" "${cur##--conflict=}"
> > +             ;;
> > +     --*)
> > +             __gitcomp_builtin switch
> > +             ;;
> > +     *)
> > +             # check if ---guess was specified to enable DWIM mode
>
> Nit: s/---/--/
>
> > +             local track_opt= only_local_ref=n
> > +             if [ -n "$(__git_find_on_cmdline "-g --guess")" ]; then
> > +                     track_opt='--track'
> > +             elif [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then
> > +                     only_local_ref=y
> > +             fi
>
> Could these two options be used together?  I think they could.

It does not make much sense when dwim is active since you'll be creating a new branch then detach from it. But yeah when you give a real branch, no dwim, no new branch created, then "git switch -dg" should work. Will fix.

Show 23 quoted lines
> If
> that's the case, then the two conditions shouldn't be chained with
> elif, but should be two separate if statements (even eliminating
> $only_local_ref, while at it?).  If that's not the case, then the two
> __git_find_on_cmdline() calls could be combined into one, and a case
> statement could act according the option found, sparing one of the
> subshells from the two.
>
> > +             if [ $only_local_ref = y ]; then
> > +                     __gitcomp_direct "$(__git_heads "" "$cur" " ")"
> > +             else
> > +                     __git_complete_refs $track_opt
> > +             fi
> > +             ;;
> > +     esac
> > +}
> > +
> >  __git_config_get_set_variables ()
> >  {
> >       local prevword word config_file= c=$cword
> > --
> > 2.20.1.682.gd5861c6d90
> >
-- 
Duy
Nguyễn Thái Ngọc Duy· Feb 8, 2019, 09:04 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2 19/19] doc: promote "git switch"

The new command "git switch" is added to avoid the confusion of one-command-do-all "git checkout" for new users. They are also helpful to avoid ambiguation context.

For these reasons, promote it everywhere possible. This includes documentation, suggestions/advice from other commands...

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-branch.txt           | 12 +++---
 Documentation/git-check-ref-format.txt |  3 +-
 Documentation/git-format-patch.txt     |  2 +-
 Documentation/git-merge-base.txt       |  2 +-
 Documentation/git-rebase.txt           |  2 +-
 Documentation/git-remote.txt           |  2 +-
 Documentation/git-rerere.txt           | 10 ++---
 Documentation/git-reset.txt            | 20 ++++-----
 Documentation/git-stash.txt            |  9 +++--
 Documentation/gitcore-tutorial.txt     | 18 ++++-----
 Documentation/giteveryday.txt          | 24 +++++------
 Documentation/gittutorial.txt          |  4 +-
 Documentation/revisions.txt            |  2 +-
 Documentation/user-manual.txt          | 56 +++++++++++++-------------
 advice.c                               | 11 +++--
 sha1-name.c                            |  2 +-
 t/t2020-checkout-detach.sh             | 16 ++++----
 17 files changed, 100 insertions(+), 95 deletions(-)
Show changes to 17 files +100 −95

Documentation/git-branch.txt, Documentation/git-check-ref-format.txt, Documentation/git-format-patch.txt, Documentation/git-merge-base.txt, Documentation/git-rebase.txt, Documentation/git-remote.txt, Documentation/git-rerere.txt, Documentation/git-reset.txt, Documentation/git-stash.txt, Documentation/gitcore-tutorial.txt, Documentation/giteveryday.txt, Documentation/gittutorial.txt, Documentation/revisions.txt, Documentation/user-manual.txt, advice.c, sha1-name.c, t/t2020-checkout-detach.sh

diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index bf5316ffa9..3cf6153415 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -48,7 +48,7 @@ The command's second form creates a new branch head named <branchname>
 which points to the current `HEAD`, or <start-point> if given.
 
 Note that this will create the new branch, but it will not switch the
-working tree to it; use "git checkout <newbranch>" to switch to the
+working tree to it; use "git switch <newbranch>" to switch to the
 new branch.
 
 When a local branch is started off a remote-tracking branch, Git sets up the
@@ -194,7 +194,7 @@ This option is only applicable in non-verbose mode.
 +
 This behavior is the default when the start point is a remote-tracking branch.
 Set the branch.autoSetupMerge configuration variable to `false` if you
-want `git checkout` and `git branch` to always behave as if `--no-track`
+want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
 were given. Set it to `always` if you want this behavior when the
 start-point is either a local or remote-tracking branch.
 
@@ -293,7 +293,7 @@ Start development from a known tag::
 $ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6
 $ cd my2.6
 $ git branch my2.6.14 v2.6.14   <1>
-$ git checkout my2.6.14
+$ git switch my2.6.14
 ------------
 +
 <1> This step and the next one could be combined into a single step with
@@ -318,9 +318,9 @@ is currently checked out) does not have all commits from the test branch.
 NOTES
 -----
 
-If you are creating a branch that you want to checkout immediately, it is
-easier to use the git checkout command with its `-b` option to create
-a branch and check it out with a single command.
+If you are creating a branch that you want to switch to immediately,
+it is easier to use the "git switch" command with its `-c` option to
+do the same thing with a single command.
 
 The options `--contains`, `--no-contains`, `--merged` and `--no-merged`
 serve four related but different purposes:
diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt
index d9de992585..ee6a4144fb 100644
--- a/Documentation/git-check-ref-format.txt
+++ b/Documentation/git-check-ref-format.txt
@@ -88,7 +88,8 @@ but it is explicitly forbidden at the beginning of a branch name).
 When run with `--branch` option in a repository, the input is first
 expanded for the ``previous checkout syntax''
 `@{-n}`.  For example, `@{-1}` is a way to refer the last thing that
-was checked out using "git checkout" operation. This option should be
+was checked out using "git switch" or "git checkout" operation.
+This option should be
 used by porcelains to accept this syntax anywhere a branch name is
 expected, so they can act as if you typed the branch name. As an
 exception note that, the ``previous checkout operation'' might result
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 27304428a1..3d42853529 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -421,7 +421,7 @@ One way to test if your MUA is set up correctly is:
 * Apply it:
 
     $ git fetch <project> master:test-apply
-    $ git checkout test-apply
+    $ git switch test-apply
     $ git reset --hard
     $ git am a.patch
 
diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt
index 9f07f4f6ed..261d5c1164 100644
--- a/Documentation/git-merge-base.txt
+++ b/Documentation/git-merge-base.txt
@@ -149,7 +149,7 @@ instead.
 Discussion on fork-point mode
 -----------------------------
 
-After working on the `topic` branch created with `git checkout -b
+After working on the `topic` branch created with `git switch -c
 topic origin/master`, the history of remote-tracking branch
 `origin/master` may have been rewound and rebuilt, leading to a
 history of this shape:
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 4dd5853d6e..420777c10b 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -17,7 +17,7 @@ SYNOPSIS
 DESCRIPTION
 -----------
 If <branch> is specified, 'git rebase' will perform an automatic
-`git checkout <branch>` before doing anything else.  Otherwise
+`git switch <branch>` before doing anything else.  Otherwise
 it remains on the current branch.
 
 If <upstream> is not specified, the upstream configured in
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index 0cad37fb81..9659abbf8e 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -230,7 +230,7 @@ $ git branch -r
   staging/master
   staging/staging-linus
   staging/staging-next
-$ git checkout -b staging staging/master
+$ git switch -c staging staging/master
 ...
 ------------
 
diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt
index df310d2a58..fe4434ad9d 100644
--- a/Documentation/git-rerere.txt
+++ b/Documentation/git-rerere.txt
@@ -91,7 +91,7 @@ For such a test, you need to merge master and topic somehow.
 One way to do it is to pull master into the topic branch:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 
               o---*---o---+ topic
@@ -113,10 +113,10 @@ the upstream might have been advanced since the test merge `+`,
 in which case the final commit graph would look like this:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 	$ ... work on both topic and master branches
-	$ git checkout master
+	$ git switch master
 	$ git merge topic
 
               o---*---o---+---o---o topic
@@ -136,11 +136,11 @@ merges, you could blow away the test merge, and keep building on
 top of the tip before the test merge:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 	$ git reset --hard HEAD^ ;# rewind the test merge
 	$ ... work on both topic and master branches
-	$ git checkout master
+	$ git switch master
 	$ git merge topic
 
               o---*---o-------o---o topic
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 9f69ae8b69..020ba838c9 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -149,9 +149,9 @@ See also the `--amend` option to linkgit:git-commit[1].
 Undo a commit, making it a topic branch::
 +
 ------------
-$ git branch topic/wip     <1>
-$ git reset --hard HEAD~3  <2>
-$ git checkout topic/wip   <3>
+$ git branch topic/wip          <1>
+$ git reset --hard HEAD~3       <2>
+$ git switch topic/wip          <3>
 ------------
 +
 <1> You have made some commits, but realize they were premature
@@ -232,13 +232,13 @@ working tree are not in any shape to be committed yet, but you
 need to get to the other branch for a quick bugfix.
 +
 ------------
-$ git checkout feature ;# you were working in "feature" branch and
-$ work work work       ;# got interrupted
+$ git switch feature  ;# you were working in "feature" branch and
+$ work work work      ;# got interrupted
 $ git commit -a -m "snapshot WIP"                 <1>
-$ git checkout master
+$ git switch master
 $ fix fix fix
 $ git commit ;# commit with real log
-$ git checkout feature
+$ git switch feature
 $ git reset --soft HEAD^ ;# go back to WIP state  <2>
 $ git reset                                       <3>
 ------------
@@ -279,18 +279,18 @@ reset it while keeping the changes in your working tree.
 +
 ------------
 $ git tag start
-$ git checkout -b branch1
+$ git switch -c branch1
 $ edit
 $ git commit ...                            <1>
 $ edit
-$ git checkout -b branch2                   <2>
+$ git switch -c branch2                     <2>
 $ git reset --keep start                    <3>
 ------------
 +
 <1> This commits your first edits in `branch1`.
 <2> In the ideal world, you could have realized that the earlier
     commit did not belong to the new topic when you created and switched
-    to `branch2` (i.e. `git checkout -b branch2 start`), but nobody is
+    to `branch2` (i.e. `git switch -c branch2 start`), but nobody is
     perfect.
 <3> But you can use `reset --keep` to remove the unwanted commit after
     you switched to `branch2`.
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 7ef8c47911..ebb6282db3 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -235,12 +235,12 @@ return to your original branch to make the emergency fix, like this:
 +
 ----------------------------------------------------------------
 # ... hack hack hack ...
-$ git checkout -b my_wip
+$ git switch -c my_wip
 $ git commit -a -m "WIP"
-$ git checkout master
+$ git switch master
 $ edit emergency fix
 $ git commit -a -m "Fix in a hurry"
-$ git checkout my_wip
+$ git switch my_wip
 $ git reset --soft HEAD^
 # ... continue hacking ...
 ----------------------------------------------------------------
@@ -293,7 +293,8 @@ SEE ALSO
 linkgit:git-checkout[1],
 linkgit:git-commit[1],
 linkgit:git-reflog[1],
-linkgit:git-reset[1]
+linkgit:git-reset[1],
+linkgit:git-switch[1]
 
 GIT
 ---
diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt
index e29a9effcc..6bd0f192cc 100644
--- a/Documentation/gitcore-tutorial.txt
+++ b/Documentation/gitcore-tutorial.txt
@@ -741,7 +741,7 @@ used earlier, and create a branch in it. You do that by simply just
 saying that you want to check out a new branch:
 
 ------------
-$ git checkout -b mybranch
+$ git switch -c mybranch
 ------------
 
 will create a new branch based at the current `HEAD` position, and switch
@@ -755,7 +755,7 @@ just telling 'git checkout' what the base of the checkout would be.
 In other words, if you have an earlier tag or branch, you'd just do
 
 ------------
-$ git checkout -b mybranch earlier-commit
+$ git switch -c mybranch earlier-commit
 ------------
 
 and it would create the new branch `mybranch` at the earlier commit,
@@ -765,7 +765,7 @@ and check out the state at that time.
 You can always just jump back to your original `master` branch by doing
 
 ------------
-$ git checkout master
+$ git switch master
 ------------
 
 (or any other branch-name, for that matter) and if you forget which
@@ -794,7 +794,7 @@ $ git branch <branchname> [startingpoint]
 
 which will simply _create_ the branch, but will not do anything further.
 You can then later -- once you decide that you want to actually develop
-on that branch -- switch to that branch with a regular 'git checkout'
+on that branch -- switch to that branch with a regular 'git switch'
 with the branchname as the argument.
 
 
@@ -808,7 +808,7 @@ being the same as the original `master` branch, let's make sure we're in
 that branch, and do some work there.
 
 ------------------------------------------------
-$ git checkout mybranch
+$ git switch mybranch
 $ echo "Work, work, work" >>hello
 $ git commit -m "Some work." -i hello
 ------------------------------------------------
@@ -825,7 +825,7 @@ does some work in the original branch, and simulate that by going back
 to the master branch, and editing the same file differently there:
 
 ------------
-$ git checkout master
+$ git switch master
 ------------
 
 Here, take a moment to look at the contents of `hello`, and notice how they
@@ -958,7 +958,7 @@ to the `master` branch. Let's go back to `mybranch`, and run
 'git merge' to get the "upstream changes" back to your branch.
 
 ------------
-$ git checkout mybranch
+$ git switch mybranch
 $ git merge -m "Merge upstream changes." master
 ------------
 
@@ -1133,9 +1133,9 @@ Remember, before running 'git merge', our `master` head was at
 work." commit.
 
 ------------
-$ git checkout mybranch
+$ git switch mybranch
 $ git reset --hard master^2
-$ git checkout master
+$ git switch master
 $ git reset --hard master^
 ------------
 
diff --git a/Documentation/giteveryday.txt b/Documentation/giteveryday.txt
index 9f2528fc8c..38015026bb 100644
--- a/Documentation/giteveryday.txt
+++ b/Documentation/giteveryday.txt
@@ -41,7 +41,7 @@ following commands.
 
   * linkgit:git-log[1] to see what happened.
 
-  * linkgit:git-checkout[1] and linkgit:git-branch[1] to switch
+  * linkgit:git-switch[1] and linkgit:git-branch[1] to switch
     branches.
 
   * linkgit:git-add[1] to manage the index file.
@@ -80,7 +80,7 @@ $ git tag v2.43 <2>
 Create a topic branch and develop.::
 +
 ------------
-$ git checkout -b alsa-audio <1>
+$ git switch -c alsa-audio <1>
 $ edit/compile/test
 $ git checkout -- curses/ux_audio_oss.c <2>
 $ git add curses/ux_audio_alsa.c <3>
@@ -90,7 +90,7 @@ $ git commit -a -s <5>
 $ edit/compile/test
 $ git diff HEAD^ <6>
 $ git commit -a --amend <7>
-$ git checkout master <8>
+$ git switch master <8>
 $ git merge alsa-audio <9>
 $ git log --since='3 days ago' <10>
 $ git log v2.43.. curses/ <11>
@@ -148,11 +148,11 @@ Clone the upstream and work on it.  Feed changes to upstream.::
 ------------
 $ git clone git://git.kernel.org/pub/scm/.../torvalds/linux-2.6 my2.6
 $ cd my2.6
-$ git checkout -b mine master <1>
+$ git switch -c mine master <1>
 $ edit/compile/test; git commit -a -s <2>
 $ git format-patch master <3>
 $ git send-email --to="person <email@example.com>" 00*.patch <4>
-$ git checkout master <5>
+$ git switch master <5>
 $ git pull <6>
 $ git log -p ORIG_HEAD.. arch/i386 include/asm-i386 <7>
 $ git ls-remote --heads http://git.kernel.org/.../jgarzik/libata-dev.git <8>
@@ -194,7 +194,7 @@ satellite$ edit/compile/test/commit
 satellite$ git push origin <4>
 
 mothership$ cd frotz
-mothership$ git checkout master
+mothership$ git switch master
 mothership$ git merge satellite/master <5>
 ------------
 +
@@ -216,7 +216,7 @@ machine into the master branch.
 Branch off of a specific tag.::
 +
 ------------
-$ git checkout -b private2.6.14 v2.6.14 <1>
+$ git switch -c private2.6.14 v2.6.14 <1>
 $ edit/compile/test; git commit -a
 $ git checkout master
 $ git cherry-pick v2.6.14..private2.6.14 <2>
@@ -274,14 +274,14 @@ $ mailx <3>
 & s 2 3 4 5 ./+to-apply
 & s 7 8 ./+hold-linus
 & q
-$ git checkout -b topic/one master
+$ git switch -c topic/one master
 $ git am -3 -i -s ./+to-apply <4>
 $ compile/test
-$ git checkout -b hold/linus && git am -3 -i -s ./+hold-linus <5>
-$ git checkout topic/one && git rebase master <6>
-$ git checkout pu && git reset --hard next <7>
+$ git switch -c hold/linus && git am -3 -i -s ./+hold-linus <5>
+$ git switch topic/one && git rebase master <6>
+$ git switch pu && git reset --hard next <7>
 $ git merge topic/one topic/two && git merge hold/linus <8>
-$ git checkout maint
+$ git switch maint
 $ git cherry-pick master~4 <9>
 $ compile/test
 $ git tag -s -m "GIT 0.99.9x" v0.99.9x <10>
diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt
index 242de31cb6..e6ad6b5f8d 100644
--- a/Documentation/gittutorial.txt
+++ b/Documentation/gittutorial.txt
@@ -207,7 +207,7 @@ automatically.  The asterisk marks the branch you are currently on;
 type
 
 ------------------------------------------------
-$ git checkout experimental
+$ git switch experimental
 ------------------------------------------------
 
 to switch to the experimental branch.  Now edit a file, commit the
@@ -216,7 +216,7 @@ change, and switch back to the master branch:
 ------------------------------------------------
 (edit file)
 $ git commit -a
-$ git checkout master
+$ git switch master
 ------------------------------------------------
 
 Check that the change you made is no longer visible, since it was
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
index 72daa20e76..a1c7a65da6 100644
--- a/Documentation/revisions.txt
+++ b/Documentation/revisions.txt
@@ -115,7 +115,7 @@ Here's an example to make it more clear:
 ------------------------------
 $ git config push.default current
 $ git config remote.pushdefault myfork
-$ git checkout -b mybranch origin/master
+$ git switch -c mybranch origin/master
 
 $ git rev-parse --symbolic-full-name @{upstream}
 refs/remotes/origin/master
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index eff7890274..94799faa2b 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -122,10 +122,10 @@ Tags are expected to always point at the same version of a project,
 while heads are expected to advance as development progresses.
 
 Create a new branch head pointing to one of these versions and check it
-out using linkgit:git-checkout[1]:
+out using linkgit:git-switch[1]:
 
 ------------------------------------------------
-$ git checkout -b new v2.6.13
+$ git switch -c new v2.6.13
 ------------------------------------------------
 
 The working directory then reflects the contents that the project had
@@ -282,10 +282,10 @@ a summary of the commands:
 	this command will fail with a warning.
 `git branch -D <branch>`::
 	delete the branch `<branch>` irrespective of its merged status.
-`git checkout <branch>`::
+`git switch <branch>`::
 	make the current branch `<branch>`, updating the working
 	directory to reflect the version referenced by `<branch>`.
-`git checkout -b <new> <start-point>`::
+`git switch -c <new> <start-point>`::
 	create a new branch `<new>` referencing `<start-point>`, and
 	check it out.
 
@@ -302,22 +302,22 @@ ref: refs/heads/master
 Examining an old version without creating a new branch
 ------------------------------------------------------
 
-The `git checkout` command normally expects a branch head, but will also
-accept an arbitrary commit; for example, you can check out the commit
-referenced by a tag:
+The `git switch` command normally expects a branch head, but will also
+accept an arbitrary commit when invoked with --detach; for example,
+you can check out the commit referenced by a tag:
 
 ------------------------------------------------
-$ git checkout v2.6.17
+$ git switch --detach v2.6.17
 Note: checking out 'v2.6.17'.
 
 You are in 'detached HEAD' state. You can look around, make experimental
 changes and commit them, and you can discard any commits you make in this
-state without impacting any branches by performing another checkout.
+state without impacting any branches by performing another switch.
 
 If you want to create a new branch to retain commits you create, you may
-do so (now or later) by using -b with the checkout command again. Example:
+do so (now or later) by using -c with the switch command again. Example:
 
-  git checkout -b new_branch_name
+  git switch -c new_branch_name
 
 HEAD is now at 427abfa Linux v2.6.17
 ------------------------------------------------
@@ -373,7 +373,7 @@ You might want to build on one of these remote-tracking branches
 on a branch of your own, just as you would for a tag:
 
 ------------------------------------------------
-$ git checkout -b my-todo-copy origin/todo
+$ git switch -c my-todo-copy origin/todo
 ------------------------------------------------
 
 You can also check out `origin/todo` directly to examine it or
@@ -2211,8 +2211,8 @@ $ git branch --track release origin/master
 These can be easily kept up to date using linkgit:git-pull[1].
 
 -------------------------------------------------
-$ git checkout test && git pull
-$ git checkout release && git pull
+$ git switch test && git pull
+$ git switch release && git pull
 -------------------------------------------------
 
 Important note!  If you have any local changes in these branches, then
@@ -2264,7 +2264,7 @@ tested changes
 2) help future bug hunters that use `git bisect` to find problems
 
 -------------------------------------------------
-$ git checkout -b speed-up-spinlocks v2.6.35
+$ git switch -c speed-up-spinlocks v2.6.35
 -------------------------------------------------
 
 Now you apply the patch(es), run some tests, and commit the change(s).  If
@@ -2279,7 +2279,7 @@ When you are happy with the state of this change, you can merge it into the
 "test" branch in preparation to make it public:
 
 -------------------------------------------------
-$ git checkout test && git merge speed-up-spinlocks
+$ git switch test && git merge speed-up-spinlocks
 -------------------------------------------------
 
 It is unlikely that you would have any conflicts here ... but you might if you
@@ -2291,7 +2291,7 @@ see the value of keeping each patch (or patch series) in its own branch.  It
 means that the patches can be moved into the `release` tree in any order.
 
 -------------------------------------------------
-$ git checkout release && git merge speed-up-spinlocks
+$ git switch release && git merge speed-up-spinlocks
 -------------------------------------------------
 
 After a while, you will have a number of branches, and despite the
@@ -2512,7 +2512,7 @@ Suppose that you create a branch `mywork` on a remote-tracking branch
 `origin`, and create some commits on top of it:
 
 -------------------------------------------------
-$ git checkout -b mywork origin
+$ git switch -c mywork origin
 $ vi file.txt
 $ git commit
 $ vi otherfile.txt
@@ -2552,7 +2552,7 @@ commits without any merges, you may instead choose to use
 linkgit:git-rebase[1]:
 
 -------------------------------------------------
-$ git checkout mywork
+$ git switch mywork
 $ git rebase origin
 -------------------------------------------------
 
@@ -3668,13 +3668,13 @@ change within the submodule, and then update the superproject to reference the
 new commit:
 
 -------------------------------------------------
-$ git checkout master
+$ git switch master
 -------------------------------------------------
 
 or
 
 -------------------------------------------------
-$ git checkout -b fix-up
+$ git switch -c fix-up
 -------------------------------------------------
 
 then
@@ -4194,7 +4194,7 @@ start.
 A good place to start is with the contents of the initial commit, with:
 
 ----------------------------------------------------
-$ git checkout e83c5163
+$ git switch --detach e83c5163
 ----------------------------------------------------
 
 The initial revision lays the foundation for almost everything Git has
@@ -4437,10 +4437,10 @@ Managing branches
 -----------------
 
 -----------------------------------------------
-$ git branch	     # list all local branches in this repo
-$ git checkout test  # switch working directory to branch "test"
-$ git branch new     # create branch "new" starting at current HEAD
-$ git branch -d new  # delete branch "new"
+$ git branch			# list all local branches in this repo
+$ git switch test	        # switch working directory to branch "test"
+$ git branch new		# create branch "new" starting at current HEAD
+$ git branch -d new		# delete branch "new"
 -----------------------------------------------
 
 Instead of basing a new branch on current HEAD (the default), use:
@@ -4456,7 +4456,7 @@ $ git branch new test~10 # ten commits before tip of branch "test"
 Create and switch to a new branch at the same time:
 
 -----------------------------------------------
-$ git checkout -b new v2.6.15
+$ git switch -c new v2.6.15
 -----------------------------------------------
 
 Update and examine branches from the repository you cloned from:
@@ -4467,7 +4467,7 @@ $ git branch -r		# list
   origin/master
   origin/next
   ...
-$ git checkout -b masterwork origin/master
+$ git switch -c masterwork origin/master
 -----------------------------------------------
 
 Fetch a branch from a different repository, and give it a new
diff --git a/advice.c b/advice.c
index 567209aa79..457c1a255e 100644
--- a/advice.c
+++ b/advice.c
@@ -191,13 +191,16 @@ void NORETURN die_conclude_merge(void)
 void detach_advice(const char *new_name)
 {
 	const char *fmt =
-	_("Note: checking out '%s'.\n\n"
+	_("Note: switching to '%s'.\n"
+	"\n"
 	"You are in 'detached HEAD' state. You can look around, make experimental\n"
 	"changes and commit them, and you can discard any commits you make in this\n"
-	"state without impacting any branches by performing another checkout.\n\n"
+	"state without impacting any branches by switching back to a branch.\n"
+	"\n"
 	"If you want to create a new branch to retain commits you create, you may\n"
-	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
-	"  git checkout -b <new-branch-name>\n\n");
+	"do so (now or later) by using -c with the switch command. Example:\n"
+	"\n"
+	"  git switch -c <new-branch-name>\n\n");
 
 	fprintf(stderr, fmt, new_name);
 }
diff --git a/sha1-name.c b/sha1-name.c
index a656481c6a..e5f0832995 100644
--- a/sha1-name.c
+++ b/sha1-name.c
@@ -745,7 +745,7 @@ static int get_oid_basic(const char *str, int len, struct object_id *oid,
 	"because it will be ignored when you just specify 40-hex. These refs\n"
 	"may be created by mistake. For example,\n"
 	"\n"
-	"  git checkout -b $br $(git rev-parse ...)\n"
+	"  git switch -c $br $(git rev-parse ...)\n"
 	"\n"
 	"where \"$br\" is somehow empty and a 40-hex ref is created. Please\n"
 	"examine these refs and maybe delete them. Turn this message off by\n"
diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
index 1fa670625c..f85dbd6740 100755
--- a/t/t2020-checkout-detach.sh
+++ b/t/t2020-checkout-detach.sh
@@ -195,16 +195,16 @@ test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not as
 
 	# The first detach operation is more chatty than the following ones.
 	cat >1st_detach <<-EOF &&
-	Note: checking out 'HEAD^'.
+	Note: switching to 'HEAD^'.
 
 	You are in 'detached HEAD' state. You can look around, make experimental
 	changes and commit them, and you can discard any commits you make in this
-	state without impacting any branches by performing another checkout.
+	state without impacting any branches by switching back to a branch.
 
 	If you want to create a new branch to retain commits you create, you may
-	do so (now or later) by using -b with the checkout command again. Example:
+	do so (now or later) by using -c with the switch command. Example:
 
-	  git checkout -b <new-branch-name>
+	  git switch -c <new-branch-name>
 
 	HEAD is now at \$commit three
 	EOF
@@ -271,16 +271,16 @@ test_expect_success 'describe_detached_head does print SHA-1 ellipsis when asked
 
 	# The first detach operation is more chatty than the following ones.
 	cat >1st_detach <<-EOF &&
-	Note: checking out 'HEAD^'.
+	Note: switching to 'HEAD^'.
 
 	You are in 'detached HEAD' state. You can look around, make experimental
 	changes and commit them, and you can discard any commits you make in this
-	state without impacting any branches by performing another checkout.
+	state without impacting any branches by switching back to a branch.
 
 	If you want to create a new branch to retain commits you create, you may
-	do so (now or later) by using -b with the checkout command again. Example:
+	do so (now or later) by using -c with the switch command. Example:
 
-	  git checkout -b <new-branch-name>
+	  git switch -c <new-branch-name>
 
 	HEAD is now at \$commit... three
 	EOF
-- 
2.20.1.682.gd5861c6d90
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 00/21] Add new command "switch"

v3 contains document and completion updates based on v2's feedback. It also contains some extra git-checkout.txt updates (blame Eric for this, he points out problems in git-switch.txt and makes me want to go fix git-checkout.txt too).

The series is now based on 'master' (yay!)
Nguyễn Thái Ngọc Duy (21):
  git-checkout.txt: spell out --no-option
  git-checkout.txt: fix one syntax line
  doc: document --overwrite-ignore
  git-checkout.txt: fix monospace typeset
  t: rename t2014-switch.sh to t2014-checkout-switch.sh
  checkout: factor out some code in parse_branchname_arg()
  checkout: make "opts" in cmd_checkout() a pointer
  checkout: move 'confict_style' and 'dwim_..' to checkout_opts
  checkout: split options[] array in three pieces
  checkout: split part of it to new command 'switch'
  switch: better names for -b and -B
  switch: remove -l
  switch: stop accepting pathspec
  switch: reject "do nothing" case
  switch: only allow explicit detached HEAD
  switch: add short option for --detach
  switch: no implicit dwim, use --guess to dwim
  switch: no worktree status unless real branch switch happens
  t: add tests for switch
  completion: support switch
  doc: promote "git switch"
 .gitignore                                    |   1 +
 Documentation/config/advice.txt               |  13 +-
 Documentation/config/branch.txt               |   4 +-
 Documentation/config/checkout.txt             |  17 +-
 Documentation/config/diff.txt                 |   3 +-
 Documentation/git-branch.txt                  |  12 +-
 Documentation/git-check-ref-format.txt        |   3 +-
 Documentation/git-checkout.txt                | 119 +++--
 Documentation/git-format-patch.txt            |   2 +-
 Documentation/git-merge-base.txt              |   2 +-
 Documentation/git-merge.txt                   |   5 +
 Documentation/git-rebase.txt                  |   2 +-
 Documentation/git-remote.txt                  |   2 +-
 Documentation/git-rerere.txt                  |  10 +-
 Documentation/git-reset.txt                   |  20 +-
 Documentation/git-stash.txt                   |   9 +-
 Documentation/git-switch.txt                  | 259 ++++++++++
 Documentation/gitattributes.txt               |   3 +-
 Documentation/gitcore-tutorial.txt            |  19 +-
 Documentation/giteveryday.txt                 |  24 +-
 Documentation/githooks.txt                    |   8 +-
 Documentation/gittutorial.txt                 |   4 +-
 Documentation/gitworkflows.txt                |   3 +-
 Documentation/revisions.txt                   |   2 +-
 Documentation/user-manual.txt                 |  56 +--
 Makefile                                      |   1 +
 advice.c                                      |  11 +-
 builtin.h                                     |   1 +
 builtin/checkout.c                            | 471 +++++++++---------
 command-list.txt                              |   1 +
 contrib/completion/git-completion.bash        |  27 +
 git.c                                         |   1 +
 parse-options-cb.c                            |  17 +
 parse-options.h                               |   1 +
 sha1-name.c                                   |   2 +-
 t/t1090-sparse-checkout-scope.sh              |  14 -
 ...014-switch.sh => t2014-checkout-switch.sh} |   0
 t/t2020-checkout-detach.sh                    |  16 +-
 t/t2060-switch.sh                             |  87 ++++
 39 files changed, 837 insertions(+), 415 deletions(-)
 create mode 100644 Documentation/git-switch.txt
 rename t/{t2014-switch.sh => t2014-checkout-switch.sh} (100%)
 create mode 100755 t/t2060-switch.sh
Range-diff dựa trên v2:
 -:  ---------- >  1:  949f3dd4fd git-checkout.txt: spell out --no-option
 1:  8358b9ca36 =  2:  1ddbbae3e2 git-checkout.txt: fix one syntax line
 2:  1686ccbf8d !  3:  b0cb2372db doc: document --overwrite-ignore
    @@ -14,14 +14,15 @@
      	out anyway. In other words, the ref can be held by more than one
      	worktree.
      
    -+--[no-]overwrite-ignore::
    ++--overwrite-ignore::
    ++--no-overwrite-ignore::
     +	Silently overwrite ignored files when switching branches. This
    -+	is the default behavior. Use --no-overwrite-ignore to abort
    ++	is the default behavior. Use `--no-overwrite-ignore` to abort
     +	the operation when the new branch contains ignored files.
     +
    - --[no-]recurse-submodules::
    + --recurse-submodules::
    + --no-recurse-submodules::
      	Using --recurse-submodules will update the content of all initialized
    - 	submodules according to the commit recorded in the superproject. If
     
      diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
      --- a/Documentation/git-merge.txt
    @@ -30,9 +31,10 @@
      	Allow the rerere mechanism to update the index with the
      	result of auto-conflict resolution if possible.
      
    -+--[no-]overwrite-ignore::
    ++--overwrite-ignore::
    ++--no-overwrite-ignore::
     +	Silently overwrite ignored files from the merge result. This
    -+	is the default behavior. Use --no-overwrite-ignore to abort.
    ++	is the default behavior. Use `--no-overwrite-ignore` to abort.
     +
      --abort::
      	Abort the current conflict resolution process, and
 -:  ---------- >  4:  5b165524d1 git-checkout.txt: fix monospace typeset
 3:  1e34862ad8 =  5:  a4240a888e t: rename t2014-switch.sh to t2014-checkout-switch.sh
 4:  aba8f41f73 =  6:  dc4a8e8933 checkout: factor out some code in parse_branchname_arg()
 5:  f165322652 =  7:  f0d933c2ac checkout: make "opts" in cmd_checkout() a pointer
 6:  75f9a38e85 =  8:  0aa541689c checkout: move 'confict_style' and 'dwim_..' to checkout_opts
 7:  9188718493 =  9:  84ca42bb26 checkout: split options[] array in three pieces
 8:  b5af7d22fd ! 10:  e983c8bb2c checkout: split part of it to new command switch
    @@ -1,6 +1,6 @@
     Author: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
     
    -    checkout: split part of it to new command switch
    +    checkout: split part of it to new command 'switch'
     
         "git checkout" doing too many things is a source of confusion for many
         users (and it even bites old timers sometimes). To remedy that, the
    @@ -180,7 +180,7 @@
     ++
     +You can use the `"@{-N}"` syntax to refer to the N-th last
     +branch/commit switched to "git switch" or "git checkout"
    -+operation. You may also specify `-` which is synonymous to `"@{-1}`.
    ++operation. You may also specify `-` which is synonymous to `"@{-1}"`.
     ++
     +As a special case, you may use `"A...B"` as a shortcut for the merge
     +base of `A` and `B` if there is exactly one merge base. You can leave
    @@ -188,8 +188,8 @@
     +
     +-c <new-branch>::
     +--create <new-branch>::
    -+	Create a new branch named <new-branch> starting at
    -+	<start-point> before switching to the branch. This is a
    ++	Create a new branch named `<new-branch>` starting at
    ++	`<start-point>` before switching to the branch. This is a
     +	convenient shortcut for:
     ++
     +------------
    @@ -199,8 +199,8 @@
     +
     +-C <new-branch>::
     +--force-create <new-branch>::
    -+	Similar to `--create` except that if <new-branch> already
    -+	exists, it will be reset to <start-point>. This is a
    ++	Similar to `--create` except that if `<new-branch>` already
    ++	exists, it will be reset to `<start-point>`. This is a
     +	convenient shortcut for:
     ++
     +------------
    @@ -216,8 +216,8 @@
     +
     +-g::
     +--guess::
    -+	If <branch> is not found but there does exist a tracking
    -+	branch in exactly one remote (call it <remote>) with a
    ++	If `<branch>` is not found but there does exist a tracking
    ++	branch in exactly one remote (call it `<remote>`) with a
     +	matching name, treat as equivalent to
     ++
     +------------
    @@ -243,7 +243,8 @@
     +	the switching target. This is used to throw away local
     +	changes.
     +
    -+--[no-]progress::
    ++--progress::
    ++--no-progress::
     +	Progress status is reported on the standard error stream
     +	by default when it is attached to a terminal, unless `--quiet`
     +	is specified. This flag enables progress reporting even if not
    @@ -292,8 +293,8 @@
     +	"merge" style, shows the original contents).
     +
     +--orphan <new-branch>::
    -+	Create a new 'orphan' branch, named <new-branch>, started from
    -+	<start-point> and switch to it. See explanation of the same
    ++	Create a new 'orphan' branch, named `<new-branch>`, started from
    ++	`<start-point>` and switch to it. See explanation of the same
     +	option in linkgit:git-checkout[1] for details.
     +
     +--ignore-other-worktrees::
    @@ -302,7 +303,8 @@
     +	the ref out anyway. In other words, the ref can be held by
     +	more than one worktree.
     +
    -+--[no-]recurse-submodules::
    ++--recurse-submodules::
    ++--no-recurse-submodules::
     +	Using --recurse-submodules will update the content of all initialized
     +	submodules according to the commit recorded in the superproject. If
     +	local modifications in a submodule would be overwritten the checkout
 9:  5be400099c ! 11:  5491932cc8 switch: better names for -b and -B
    @@ -3,9 +3,9 @@
         switch: better names for -b and -B
     
         The shortcut of these options do not make much sense when used with
    -    switch. And their descriptions are also tied to checkout
    -    out. Move -b/-B to cmd_checkout() and new -c/-C with the same
    -    functionality in cmd_switch_branch()
    +    switch. And their descriptions are also tied to checkout. Move -b/-B
    +    to cmd_checkout() and new -c/-C with the same functionality in
    +    cmd_switch_branch()
     
      diff --git a/builtin/checkout.c b/builtin/checkout.c
      --- a/builtin/checkout.c
10:  38cfc1fada = 12:  e0f1247b09 switch: remove -l
11:  7f0abab72c = 13:  f7ff1a7df6 switch: stop accepting pathspec
12:  386ba95007 = 14:  0750d63d38 switch: reject "do nothing" case
13:  e488c7d8ca = 15:  bcd843146d switch: only allow explicit detached HEAD
14:  9cd5f63c01 = 16:  d5cc55525d switch: add short option for --detach
15:  fc31b3d4f3 ! 17:  4a27400a58 switch: no implicit dwim, use --guess to dwim
    @@ -15,13 +15,13 @@
      --- a/Documentation/git-checkout.txt
      +++ b/Documentation/git-checkout.txt
     @@
    - 	<branch>.
    + 	`<branch>`.
      +
    - If <branch> is not found but there does exist a tracking branch in
    --exactly one remote (call it <remote>) with a matching name, treat as
    + If `<branch>` is not found but there does exist a tracking branch in
    +-exactly one remote (call it `<remote>`) with a matching name, treat as
     -equivalent to
    -+exactly one remote (call it <remote>) with a matching name and
    -+--no-guess is not specified, treat as equivalent to
    ++exactly one remote (call it `<remote>`) with a matching name and
    ++`--no-guess` is not specified, treat as equivalent to
      +
      ------------
      $ git checkout -b <branch> --track <remote>/<branch>
    @@ -36,16 +36,17 @@
     -'origin' remote. See also `checkout.defaultRemote` in
     -linkgit:git-config[1].
     -+
    - You could omit <branch>, in which case the command degenerates to
    + You could omit `<branch>`, in which case the command degenerates to
      "check out the current branch", which is a glorified no-op with
      rather expensive side-effects to show only the tracking information,
     @@
      	Do not set up "upstream" configuration, even if the
      	branch.autoSetupMerge configuration variable is true.
      
    -+--[no-]guess::
    -+	If <branch> is not found but there does exist a tracking
    -+	branch in exactly one remote (call it <remote>) with a
    ++--guess::
    ++--no-guess::
    ++	If `<branch>` is not found but there does exist a tracking
    ++	branch in exactly one remote (call it `<remote>`) with a
     +	matching name, treat as equivalent to
     ++
     +------------
    @@ -74,9 +75,9 @@
     -	Do not attempt to create a branch if a remote tracking branch
     -	of the same name exists.
     -
    - --[no-]overlay::
    + --overlay::
    + --no-overlay::
      	In the default overlay mode, `git checkout` never
    - 	removes files from the index or the working tree.  When
     
      diff --git a/builtin/checkout.c b/builtin/checkout.c
      --- a/builtin/checkout.c
16:  761f953b40 = 18:  f7dd33abe6 switch: no worktree status unless real branch switch happens
17:  206f1bda79 = 19:  5b182abea8 t: add tests for switch
18:  3cfb2913eb ! 20:  1575064fe6 completion: support switch
    @@ -28,10 +28,11 @@
     +		local track_opt= only_local_ref=n
     +		if [ -n "$(__git_find_on_cmdline "-g --guess")" ]; then
     +			track_opt='--track'
    -+		elif [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then
    ++		fi
    ++		if [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then
     +			only_local_ref=y
     +		fi
    -+		if [ $only_local_ref = y ]; then
    ++		if [ $only_local_ref = y -a -n "$track_opt"]; then
     +			__gitcomp_direct "$(__git_heads "" "$cur" " ")"
     +		else
     +			__git_complete_refs $track_opt
19:  2d018878bd ! 21:  8ed01ad0c5 doc: promote "git switch"
    @@ -317,9 +317,9 @@
      
      ------------
     -$ git checkout mybranch
    -+$ git switch mybranch
    - $ git reset --hard master^2
    +-$ git reset --hard master^2
     -$ git checkout master
    ++$ git switch -C mybranch master^2
     +$ git switch master
      $ git reset --hard master^
      ------------
    @@ -400,7 +400,7 @@
     -$ git checkout pu && git reset --hard next <7>
     +$ git switch -c hold/linus && git am -3 -i -s ./+hold-linus <5>
     +$ git switch topic/one && git rebase master <6>
    -+$ git switch pu && git reset --hard next <7>
    ++$ git switch -C pu next <7>
      $ git merge topic/one topic/two && git merge hold/linus <8>
     -$ git checkout maint
     +$ git switch maint
    @@ -430,6 +430,20 @@
      
      Check that the change you made is no longer visible, since it was
     
    + diff --git a/Documentation/gitworkflows.txt b/Documentation/gitworkflows.txt
    + --- a/Documentation/gitworkflows.txt
    + +++ b/Documentation/gitworkflows.txt
    +@@
    + .Rewind and rebuild next
    + [caption="Recipe: "]
    + =====================================
    +-* `git checkout next`
    +-* `git reset --hard master`
    ++* `git switch -C next master`
    + * `git merge ai/topic_in_next1`
    + * `git merge ai/topic_in_next2`
    + * ...
    +
      diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
      --- a/Documentation/revisions.txt
      +++ b/Documentation/revisions.txt
-- 
2.21.0.rc1.337.gdf7f8d0522
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 01/21] git-checkout.txt: spell out --no-option

It's easier to search for and also less cryptic.
---
 Documentation/git-checkout.txt | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)
Show changes to Documentation/git-checkout.txt +7 −1
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index f179b43732..99c8c0dc0f 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -118,7 +118,8 @@ OPTIONS
 --quiet::
 	Quiet, suppress feedback messages.
 
---[no-]progress::
+--progress::
+--no-progress::
 	Progress status is reported on the standard error stream
 	by default when it is attached to a terminal, unless `--quiet`
 	is specified. This flag enables progress reporting even if not
@@ -262,7 +263,7 @@ edits from your current working tree. See the ``Interactive Mode''
 section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
 +
 Note that this option uses the no overlay mode by default (see also
-`--[no-]overlay`), and currently doesn't support overlay mode.
+`--overlay`), and currently doesn't support overlay mode.
 
 --ignore-other-worktrees::
 	`git checkout` refuses when the wanted ref is already checked
@@ -270,7 +271,8 @@ Note that this option uses the no overlay mode by default (see also
 	out anyway. In other words, the ref can be held by more than one
 	worktree.
 
---[no-]recurse-submodules::
+--recurse-submodules::
+--no-recurse-submodules::
 	Using --recurse-submodules will update the content of all initialized
 	submodules according to the commit recorded in the superproject. If
 	local modifications in a submodule would be overwritten the checkout
@@ -283,7 +285,8 @@ Note that this option uses the no overlay mode by default (see also
 	Do not attempt to create a branch if a remote tracking branch
 	of the same name exists.
 
---[no-]overlay::
+--overlay::
+--no-overlay::
 	In the default overlay mode, `git checkout` never
 	removes files from the index or the working tree.  When
 	specifying `--no-overlay`, files that appear in the index and
-- 
2.21.0.rc1.337.gdf7f8d0522
Eric Sunshine· Mar 10, 2019, 03:28 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v3 01/21] git-checkout.txt: spell out --no-option

On Fri, Mar 8, 2019 at 4:58 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> It's easier to search for and also less cryptic.
> ---
Your sign-off is missing from the entire series.
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 02/21] git-checkout.txt: fix one syntax line

<branch> can be omitted in this syntax, and it's actually documented a few paragraphs down:

  You could omit <branch>, in which case the command degenerates to
  "check out the current branch", which is a glorified no-op with
  rather expensive side-effects to show only the tracking information,
  if exists, for the current branch.
---
 Documentation/git-checkout.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to Documentation/git-checkout.txt +1 −1
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 99c8c0dc0f..28817cfa41 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -23,7 +23,7 @@ or the specified tree.  If no paths are given, 'git checkout' will
 also update `HEAD` to set the specified branch as the current
 branch.
 
-'git checkout' <branch>::
+'git checkout' [<branch>]::
 	To prepare for working on <branch>, switch to it by updating
 	the index and the files in the working tree, and by pointing
 	HEAD at the branch. Local modifications to the files in the
-- 
2.21.0.rc1.337.gdf7f8d0522
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 03/21] doc: document --overwrite-ignore

I added this option in git-checkout and git-merge in c1d7036b6b
(checkout,merge: disallow overwriting ignored files with
--no-overwrite-ignore - 2011-11-27) but did not remember to update
documentation. This completes that commit.
---
 Documentation/git-checkout.txt | 6 ++++++
 Documentation/git-merge.txt    | 5 +++++
 2 files changed, 11 insertions(+)
Show changes to 2 files +11 −0

Documentation/git-checkout.txt, Documentation/git-merge.txt

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 28817cfa41..5280d1f9ed 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -271,6 +271,12 @@ Note that this option uses the no overlay mode by default (see also
 	out anyway. In other words, the ref can be held by more than one
 	worktree.
 
+--overwrite-ignore::
+--no-overwrite-ignore::
+	Silently overwrite ignored files when switching branches. This
+	is the default behavior. Use `--no-overwrite-ignore` to abort
+	the operation when the new branch contains ignored files.
+
 --recurse-submodules::
 --no-recurse-submodules::
 	Using --recurse-submodules will update the content of all initialized
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index 4cc86469f3..6a9163d8fe 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -87,6 +87,11 @@ will be appended to the specified message.
 	Allow the rerere mechanism to update the index with the
 	result of auto-conflict resolution if possible.
 
+--overwrite-ignore::
+--no-overwrite-ignore::
+	Silently overwrite ignored files from the merge result. This
+	is the default behavior. Use `--no-overwrite-ignore` to abort.
+
 --abort::
 	Abort the current conflict resolution process, and
 	try to reconstruct the pre-merge state.
-- 
2.21.0.rc1.337.gdf7f8d0522
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 04/21] git-checkout.txt: fix monospace typeset

---
 Documentation/git-checkout.txt | 60 +++++++++++++++++-----------------
 1 file changed, 30 insertions(+), 30 deletions(-)
Show changes to Documentation/git-checkout.txt +30 −30
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 5280d1f9ed..1b9d689933 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -24,14 +24,14 @@ also update `HEAD` to set the specified branch as the current
 branch.
 
 'git checkout' [<branch>]::
-	To prepare for working on <branch>, switch to it by updating
+	To prepare for working on `<branch>`, switch to it by updating
 	the index and the files in the working tree, and by pointing
 	HEAD at the branch. Local modifications to the files in the
 	working tree are kept, so that they can be committed to the
-	<branch>.
+	`<branch>`.
 +
-If <branch> is not found but there does exist a tracking branch in
-exactly one remote (call it <remote>) with a matching name, treat as
+If `<branch>` is not found but there does exist a tracking branch in
+exactly one remote (call it `<remote>`) with a matching name, treat as
 equivalent to
 +
 ------------
@@ -47,7 +47,7 @@ branches from there if `<branch>` is ambiguous but exists on the
 'origin' remote. See also `checkout.defaultRemote` in
 linkgit:git-config[1].
 +
-You could omit <branch>, in which case the command degenerates to
+You could omit `<branch>`, in which case the command degenerates to
 "check out the current branch", which is a glorified no-op with
 rather expensive side-effects to show only the tracking information,
 if exists, for the current branch.
@@ -61,7 +61,7 @@ if exists, for the current branch.
 	`--track` without `-b` implies branch creation; see the
 	description of `--track` below.
 +
-If `-B` is given, <new_branch> is created if it doesn't exist; otherwise, it
+If `-B` is given, `<new_branch>` is created if it doesn't exist; otherwise, it
 is reset. This is the transactional equivalent of
 +
 ------------
@@ -75,25 +75,25 @@ successful.
 'git checkout' --detach [<branch>]::
 'git checkout' [--detach] <commit>::
 
-	Prepare to work on top of <commit>, by detaching HEAD at it
+	Prepare to work on top of `<commit>`, by detaching HEAD at it
 	(see "DETACHED HEAD" section), and updating the index and the
 	files in the working tree.  Local modifications to the files
 	in the working tree are kept, so that the resulting working
 	tree will be the state recorded in the commit plus the local
 	modifications.
 +
-When the <commit> argument is a branch name, the `--detach` option can
+When the `<commit>` argument is a branch name, the `--detach` option can
 be used to detach HEAD at the tip of the branch (`git checkout
-<branch>` would check out that branch without detaching HEAD).
+`<branch>`` would check out that branch without detaching HEAD).
 +
-Omitting <branch> detaches HEAD at the tip of the current branch.
+Omitting `<branch>` detaches HEAD at the tip of the current branch.
 
 'git checkout' [<tree-ish>] [--] <pathspec>...::
 
 	Overwrite paths in the working tree by replacing with the
-	contents in the index or in the <tree-ish> (most often a
-	commit).  When a <tree-ish> is given, the paths that
-	match the <pathspec> are updated both in the index and in
+	contents in the index or in the `<tree-ish>` (most often a
+	commit).  When a `<tree-ish>` is given, the paths that
+	match the `<pathspec>` are updated both in the index and in
 	the working tree.
 +
 The index may contain unmerged entries because of a previous failed merge.
@@ -155,12 +155,12 @@ on your side branch as `theirs` (i.e. "one contributor's work on top
 of it").
 
 -b <new_branch>::
-	Create a new branch named <new_branch> and start it at
-	<start_point>; see linkgit:git-branch[1] for details.
+	Create a new branch named `<new_branch>` and start it at
+	`<start_point>`; see linkgit:git-branch[1] for details.
 
 -B <new_branch>::
-	Creates the branch <new_branch> and start it at <start_point>;
-	if it already exists, then reset it to <start_point>. This is
+	Creates the branch `<new_branch>` and start it at `<start_point>`;
+	if it already exists, then reset it to `<start_point>`. This is
 	equivalent to running "git branch" with "-f"; see
 	linkgit:git-branch[1] for details.
 
@@ -191,19 +191,19 @@ explicitly give a name with `-b` in such a case.
 	Rather than checking out a branch to work on it, check out a
 	commit for inspection and discardable experiments.
 	This is the default behavior of "git checkout <commit>" when
-	<commit> is not a branch name.  See the "DETACHED HEAD" section
+	`<commit>` is not a branch name.  See the "DETACHED HEAD" section
 	below for details.
 
 --orphan <new_branch>::
-	Create a new 'orphan' branch, named <new_branch>, started from
-	<start_point> and switch to it.  The first commit made on this
+	Create a new 'orphan' branch, named `<new_branch>`, started from
+	`<start_point>` and switch to it.  The first commit made on this
 	new branch will have no parents and it will be the root of a new
 	history totally disconnected from all the other branches and
 	commits.
 +
 The index and the working tree are adjusted as if you had previously run
 "git checkout <start_point>".  This allows you to start a new history
-that records a set of paths similar to <start_point> by easily running
+that records a set of paths similar to `<start_point>` by easily running
 "git commit -a" to make the root commit.
 +
 This can be useful when you want to publish the tree from a commit
@@ -213,7 +213,7 @@ whose full history contains proprietary or otherwise encumbered bits of
 code.
 +
 If you want to start a disconnected history that records a set of paths
-that is totally different from the one of <start_point>, then you should
+that is totally different from the one of `<start_point>`, then you should
 clear the index and the working tree right after creating the orphan
 branch by running "git rm -rf ." from the top level of the working tree.
 Afterwards you will be ready to prepare your new files, repopulating the
@@ -221,9 +221,9 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.
 
 --ignore-skip-worktree-bits::
 	In sparse checkout mode, `git checkout -- <paths>` would
-	update only entries matched by <paths> and sparse patterns
+	update only entries matched by `<paths>` and sparse patterns
 	in $GIT_DIR/info/sparse-checkout. This option ignores
-	the sparse patterns and adds back any files in <paths>.
+	the sparse patterns and adds back any files in `<paths>`.
 
 -m::
 --merge::
@@ -254,9 +254,9 @@ the conflicted merge in the specified paths.
 -p::
 --patch::
 	Interactively select hunks in the difference between the
-	<tree-ish> (or the index, if unspecified) and the working
+	`<tree-ish>` (or the index, if unspecified) and the working
 	tree.  The chosen hunks are then applied in reverse to the
-	working tree (and if a <tree-ish> was specified, the index).
+	working tree (and if a `<tree-ish>` was specified, the index).
 +
 This means that you can use `git checkout -p` to selectively discard
 edits from your current working tree. See the ``Interactive Mode''
@@ -279,10 +279,10 @@ Note that this option uses the no overlay mode by default (see also
 
 --recurse-submodules::
 --no-recurse-submodules::
-	Using --recurse-submodules will update the content of all initialized
+	Using `--recurse-submodules` will update the content of all initialized
 	submodules according to the commit recorded in the superproject. If
 	local modifications in a submodule would be overwritten the checkout
-	will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
+	will fail unless `-f` is used. If nothing (or `--no-recurse-submodules`)
 	is used, the work trees of submodules will not be updated.
 	Just like linkgit:git-submodule[1], this will detach the
 	submodules HEAD.
@@ -296,8 +296,8 @@ Note that this option uses the no overlay mode by default (see also
 	In the default overlay mode, `git checkout` never
 	removes files from the index or the working tree.  When
 	specifying `--no-overlay`, files that appear in the index and
-	working tree, but not in <tree-ish> are removed, to make them
-	match <tree-ish> exactly.
+	working tree, but not in `<tree-ish>` are removed, to make them
+	match `<tree-ish>` exactly.
 
 <branch>::
 	Branch to checkout; if it refers to a branch (i.e., a name that,
-- 
2.21.0.rc1.337.gdf7f8d0522
Martin Ågren· Mar 9, 2019, 12:35 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v3 04/21] git-checkout.txt: fix monospace typeset

On Fri, 8 Mar 2019 at 10:58, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 8 quoted lines
>  Documentation/git-checkout.txt | 60 +++++++++++++++++-----------------
>  1 file changed, 30 insertions(+), 30 deletions(-)
>
>  'git checkout' [<branch>]::
> -       To prepare for working on <branch>, switch to it by updating
> +       To prepare for working on `<branch>`, switch to it by updating
>         the index and the files in the working tree, and by pointing
>         HEAD at the branch. Local modifications to the files in the
You could wrap HEAD in backticks as well: `HEAD`.
> -       Prepare to work on top of <commit>, by detaching HEAD at it
> +       Prepare to work on top of `<commit>`, by detaching HEAD at it
Likewise.
>         (see "DETACHED HEAD" section), and updating the index and the
(but not here)
>  be used to detach HEAD at the tip of the branch (`git checkout
> -<branch>` would check out that branch without detaching HEAD).
> +`<branch>`` would check out that branch without detaching HEAD).

This results in a nesting. I would say this was already correct. (A few more `HEAD` here.)

> -       Create a new branch named <new_branch> and start it at
> -       <start_point>; see linkgit:git-branch[1] for details.
> +       Create a new branch named `<new_branch>` and start it at
> +       `<start_point>`; see linkgit:git-branch[1] for details.
Lots of changes like this. Good.
>         This is the default behavior of "git checkout <commit>" when
Should be `git checkout <commit>`?
> -       <commit> is not a branch name.  See the "DETACHED HEAD" section
> +       `<commit>` is not a branch name.  See the "DETACHED HEAD" section
Good.
> -that records a set of paths similar to <start_point> by easily running
> +that records a set of paths similar to `<start_point>` by easily running
>  "git commit -a" to make the root commit.
Another opportunity for backticks.

I saw one change that I disagreed with and a few missed opportunities (IMHO). How about something like the below squashed in? (This is based on this point in the series.) It might be worth polishing this document before copying it (or parts of it) in the next commit.

Martin
-- >8 --
Subject: git-checkout.txt: add more backticks for monospacing

Add backticks where we have none, replace single quotes with backticks and replace double-quotes. Drop double-quotes from nested constructions such as `"@{-1}"`. Add a missing possessive apostrophe after the word "submodules" while at it.

Signed-off-by: Martin Ågren <martin.agren@gmail.com>
---
 Documentation/git-checkout.txt | 106 ++++++++++++++++-----------------
 1 file changed, 53 insertions(+), 53 deletions(-)
Show changes to Documentation/git-checkout.txt +53 −53
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 1b9d689933..af5906fbf5 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -26,7 +26,7 @@ branch.
 'git checkout' [<branch>]::
 	To prepare for working on `<branch>`, switch to it by updating
 	the index and the files in the working tree, and by pointing
-	HEAD at the branch. Local modifications to the files in the
+	`HEAD` at the branch. Local modifications to the files in the
 	working tree are kept, so that they can be committed to the
 	`<branch>`.
 +
@@ -75,7 +75,7 @@ successful.
 'git checkout' --detach [<branch>]::
 'git checkout' [--detach] <commit>::
 
-	Prepare to work on top of `<commit>`, by detaching HEAD at it
+	Prepare to work on top of `<commit>`, by detaching `HEAD` at it
 	(see "DETACHED HEAD" section), and updating the index and the
 	files in the working tree.  Local modifications to the files
 	in the working tree are kept, so that the resulting working
@@ -83,10 +83,10 @@ successful.
 	modifications.
 +
 When the `<commit>` argument is a branch name, the `--detach` option can
-be used to detach HEAD at the tip of the branch (`git checkout
-`<branch>`` would check out that branch without detaching HEAD).
+be used to detach `HEAD` at the tip of the branch (`git checkout
+<branch>` would check out that branch without detaching `HEAD`).
 +
-Omitting `<branch>` detaches HEAD at the tip of the current branch.
+Omitting `<branch>` detaches `HEAD` at the tip of the current branch.
 
 'git checkout' [<tree-ish>] [--] <pathspec>...::
 
@@ -128,7 +128,7 @@ OPTIONS
 -f::
 --force::
 	When switching branches, proceed even if the index or the
-	working tree differs from HEAD.  This is used to throw away
+	working tree differs from `HEAD`.  This is used to throw away
 	local changes.
 +
 When checking out paths from the index, do not fail upon unmerged
@@ -173,15 +173,15 @@ If no `-b` option is given, the name of the new branch will be
 derived from the remote-tracking branch, by looking at the local part of
 the refspec configured for the corresponding remote, and then stripping
 the initial part up to the "*".
-This would tell us to use "hack" as the local branch when branching
-off of "origin/hack" (or "remotes/origin/hack", or even
-"refs/remotes/origin/hack").  If the given name has no slash, or the above
+This would tell us to use `hack` as the local branch when branching
+off of `origin/hack` (or `remotes/origin/hack`, or even
+`refs/remotes/origin/hack`).  If the given name has no slash, or the above
 guessing results in an empty name, the guessing is aborted.  You can
 explicitly give a name with `-b` in such a case.
 
 --no-track::
 	Do not set up "upstream" configuration, even if the
-	branch.autoSetupMerge configuration variable is true.
+	`branch.autoSetupMerge` configuration variable is true.
 
 -l::
 	Create the new branch's reflog; see linkgit:git-branch[1] for
@@ -190,7 +190,7 @@ explicitly give a name with `-b` in such a case.
 --detach::
 	Rather than checking out a branch to work on it, check out a
 	commit for inspection and discardable experiments.
-	This is the default behavior of "git checkout <commit>" when
+	This is the default behavior of `git checkout <commit>` when
 	`<commit>` is not a branch name.  See the "DETACHED HEAD" section
 	below for details.
 
@@ -202,9 +202,9 @@ explicitly give a name with `-b` in such a case.
 	commits.
 +
 The index and the working tree are adjusted as if you had previously run
-"git checkout <start_point>".  This allows you to start a new history
+`git checkout <start_point>`.  This allows you to start a new history
 that records a set of paths similar to `<start_point>` by easily running
-"git commit -a" to make the root commit.
+`git commit -a` to make the root commit.
 +
 This can be useful when you want to publish the tree from a commit
 without exposing its full history. You might want to do this to publish
@@ -215,14 +215,14 @@ code.
 If you want to start a disconnected history that records a set of paths
 that is totally different from the one of `<start_point>`, then you should
 clear the index and the working tree right after creating the orphan
-branch by running "git rm -rf ." from the top level of the working tree.
+branch by running `git rm -rf .` from the top level of the working tree.
 Afterwards you will be ready to prepare your new files, repopulating the
 working tree, by copying them from elsewhere, extracting a tarball, etc.
 
 --ignore-skip-worktree-bits::
 	In sparse checkout mode, `git checkout -- <paths>` would
 	update only entries matched by `<paths>` and sparse patterns
-	in $GIT_DIR/info/sparse-checkout. This option ignores
+	in `$GIT_DIR/info/sparse-checkout`. This option ignores
 	the sparse patterns and adds back any files in `<paths>`.
 
 -m::
@@ -245,9 +245,9 @@ When checking out paths from the index, this option lets you recreate
 the conflicted merge in the specified paths.
 
 --conflict=<style>::
-	The same as --merge option above, but changes the way the
+	The same as `--merge` option above, but changes the way the
 	conflicting hunks are presented, overriding the
-	merge.conflictStyle configuration variable.  Possible values are
+	`merge.conflictStyle` configuration variable.  Possible values are
 	"merge" (default) and "diff3" (in addition to what is shown by
 	"merge" style, shows the original contents).
 
@@ -285,7 +285,7 @@ Note that this option uses the no overlay mode by default (see also
 	will fail unless `-f` is used. If nothing (or `--no-recurse-submodules`)
 	is used, the work trees of submodules will not be updated.
 	Just like linkgit:git-submodule[1], this will detach the
-	submodules HEAD.
+	submodules' `HEAD`.
 
 --no-guess::
 	Do not attempt to create a branch if a remote tracking branch
@@ -303,14 +303,14 @@ Note that this option uses the no overlay mode by default (see also
 	Branch to checkout; if it refers to a branch (i.e., a name that,
 	when prepended with "refs/heads/", is a valid ref), then that
 	branch is checked out. Otherwise, if it refers to a valid
-	commit, your HEAD becomes "detached" and you are no longer on
+	commit, your `HEAD` becomes "detached" and you are no longer on
 	any branch (see below for details).
 +
-You can use the `"@{-N}"` syntax to refer to the N-th last
+You can use the `@{-N}` syntax to refer to the N-th last
 branch/commit checked out using "git checkout" operation. You may
-also specify `-` which is synonymous to `"@{-1}"`.
+also specify `-` which is synonymous to `@{-1}`.
 +
-As a special case, you may use `"A...B"` as a shortcut for the
+As a special case, you may use `A...B` as a shortcut for the
 merge base of `A` and `B` if there is exactly one merge base. You can
 leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 
@@ -319,7 +319,7 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 
 <start_point>::
 	The name of a commit at which to start the new branch; see
-	linkgit:git-branch[1] for details. Defaults to HEAD.
+	linkgit:git-branch[1] for details. Defaults to `HEAD`.
 
 <tree-ish>::
 	Tree to checkout from (when paths are given). If not specified,
@@ -329,9 +329,9 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 
 DETACHED HEAD
 -------------
-HEAD normally refers to a named branch (e.g. 'master'). Meanwhile, each
+`HEAD` normally refers to a named branch (e.g. `master`). Meanwhile, each
 branch refers to a specific commit. Let's look at a repo with three
-commits, one of them tagged, and with branch 'master' checked out:
+commits, one of them tagged, and with branch `master` checked out:
 
 ------------
            HEAD (refers to branch 'master')
@@ -344,10 +344,10 @@ a---b---c  branch 'master' (refers to commit 'c')
 ------------
 
 When a commit is created in this state, the branch is updated to refer to
-the new commit. Specifically, 'git commit' creates a new commit 'd', whose
-parent is commit 'c', and then updates branch 'master' to refer to new
-commit 'd'. HEAD still refers to branch 'master' and so indirectly now refers
-to commit 'd':
+the new commit. Specifically, 'git commit' creates a new commit `d`, whose
+parent is commit `c`, and then updates branch `master` to refer to new
+commit `d`. `HEAD` still refers to branch `master` and so indirectly now refers
+to commit `d`:
 
 ------------
 $ edit; git add; git commit
@@ -364,7 +364,7 @@ a---b---c---d  branch 'master' (refers to commit 'd')
 It is sometimes useful to be able to checkout a commit that is not at
 the tip of any named branch, or even to create a new commit that is not
 referenced by a named branch. Let's look at what happens when we
-checkout commit 'b' (here we show two ways this may be done):
+checkout commit `b` (here we show two ways this may be done):
 
 ------------
 $ git checkout v2.0  # or
@@ -379,9 +379,9 @@ a---b---c---d  branch 'master' (refers to commit 'd')
   tag 'v2.0' (refers to commit 'b')
 ------------
 
-Notice that regardless of which checkout command we use, HEAD now refers
-directly to commit 'b'. This is known as being in detached HEAD state.
-It means simply that HEAD refers to a specific commit, as opposed to
+Notice that regardless of which checkout command we use, `HEAD` now refers
+directly to commit `b`. This is known as being in detached `HEAD` state.
+It means simply that `HEAD` refers to a specific commit, as opposed to
 referring to a named branch. Let's see what happens when we create a commit:
 
 ------------
@@ -398,7 +398,7 @@ a---b---c---d  branch 'master' (refers to commit 'd')
   tag 'v2.0' (refers to commit 'b')
 ------------
 
-There is now a new commit 'e', but it is referenced only by HEAD. We can
+There is now a new commit `e`, but it is referenced only by `HEAD`. We can
 of course add yet another commit in this state:
 
 ------------
@@ -416,7 +416,7 @@ a---b---c---d  branch 'master' (refers to commit 'd')
 ------------
 
 In fact, we can perform all the normal Git operations. But, let's look
-at what happens when we then checkout master:
+at what happens when we then checkout `master`:
 
 ------------
 $ git checkout master
@@ -431,9 +431,9 @@ a---b---c---d  branch 'master' (refers to commit 'd')
 ------------
 
 It is important to realize that at this point nothing refers to commit
-'f'. Eventually commit 'f' (and by extension commit 'e') will be deleted
+`f`. Eventually commit `f` (and by extension commit `e`) will be deleted
 by the routine Git garbage collection process, unless we create a reference
-before that happens. If we have not yet moved away from commit 'f',
+before that happens. If we have not yet moved away from commit `f`,
 any of these will create a reference to it:
 
 ------------
@@ -442,19 +442,19 @@ $ git branch foo        <2>
 $ git tag foo           <3>
 ------------
 
-<1> creates a new branch 'foo', which refers to commit 'f', and then
-    updates HEAD to refer to branch 'foo'. In other words, we'll no longer
-    be in detached HEAD state after this command.
+<1> creates a new branch `foo`, which refers to commit `f`, and then
+    updates `HEAD` to refer to branch `foo`. In other words, we'll no longer
+    be in detached `HEAD` state after this command.
 
-<2> similarly creates a new branch 'foo', which refers to commit 'f',
-    but leaves HEAD detached.
+<2> similarly creates a new branch `foo`, which refers to commit `f`,
+    but leaves `HEAD` detached.
 
-<3> creates a new tag 'foo', which refers to commit 'f',
-    leaving HEAD detached.
+<3> creates a new tag `foo`, which refers to commit `f`,
+    leaving `HEAD` detached.
 
-If we have moved away from commit 'f', then we must first recover its object
+If we have moved away from commit `f`, then we must first recover its object
 name (typically by using git reflog), and then we can create a reference to
-it. For example, to see the last two commits to which HEAD referred, we
+it. For example, to see the last two commits to which `HEAD` referred, we
 can use either of these commands:
 
 ------------
@@ -465,12 +465,12 @@ $ git log -g -2 HEAD
 ARGUMENT DISAMBIGUATION
 -----------------------
 
-When there is only one argument given and it is not `--` (e.g. "git
-checkout abc"), and when the argument is both a valid `<tree-ish>`
-(e.g. a branch "abc" exists) and a valid `<pathspec>` (e.g. a file
+When there is only one argument given and it is not `--` (e.g. `git
+checkout abc`), and when the argument is both a valid `<tree-ish>`
+(e.g. a branch `abc` exists) and a valid `<pathspec>` (e.g. a file
 or a directory whose name is "abc" exists), Git would usually ask
 you to disambiguate.  Because checking out a branch is so common an
-operation, however, "git checkout abc" takes "abc" as a `<tree-ish>`
+operation, however, `git checkout abc` takes "abc" as a `<tree-ish>`
 in such a situation.  Use `git checkout -- <pathspec>` if you want
 to checkout these paths out of the index.
 
@@ -478,7 +478,7 @@ EXAMPLES
 --------
 
 . The following sequence checks out the `master` branch, reverts
-  the `Makefile` to two revisions back, deletes hello.c by
+  the `Makefile` to two revisions back, deletes `hello.c` by
   mistake, and gets it back from the index.
 +
 ------------
@@ -490,7 +490,7 @@ $ git checkout hello.c            <3>
 +
 <1> switch branch
 <2> take a file out of another commit
-<3> restore hello.c from the index
+<3> restore `hello.c` from the index
 +
 If you want to check out _all_ C source files out of the index,
 you can say
@@ -519,7 +519,7 @@ $ git checkout -- hello.c
 $ git checkout mytopic
 ------------
 +
-However, your "wrong" branch and correct "mytopic" branch may
+However, your "wrong" branch and correct `mytopic` branch may
 differ in files that you have modified locally, in which case
 the above checkout would fail like this:
 +
-- 
2.21.0
Duy Nguyen· Mar 11, 2019, 09:35 UTC · re: Martin Ågren · lore

Re: [PATCH v3 04/21] git-checkout.txt: fix monospace typeset

On Sat, Mar 9, 2019 at 7:35 PM Martin Ågren <martin.agren@gmail.com> wrote:
> @@ -285,7 +285,7 @@ Note that this option uses the no overlay mode by default (see also
The part not shown here is
    Using `--recurse-submodules` will update the content of all initialized
    submodules according to the commit recorded in the superproject. If
    local modifications in a submodule would be overwritten the checkout

and the --recurse-submodules is rendered incorrectly (not with monospace font, and the quotes remain) because...

Show 5 quoted lines
>         will fail unless `-f` is used. If nothing (or `--no-recurse-submodules`)
>         is used, the work trees of submodules will not be updated.
>         Just like linkgit:git-submodule[1], this will detach the
> -       submodules HEAD.
> +       submodules' `HEAD`.

...of this apostrophe, it seems, on both man and html versions. This is with asciidoc 8.6.9.

Martin, could you check if your asciidoc (or asciidoctor) behaves the same? If it's not just my buggy asciidoc version, I can turn this to "wil detach `HEAD` of the submodule" which should fix the problem.

-- 
Duy
Martin Ågren· Mar 11, 2019, 10:41 UTC · re: Duy Nguyen · lore

Re: [PATCH v3 04/21] git-checkout.txt: fix monospace typeset

On Mon, 11 Mar 2019 at 10:35, Duy Nguyen <pclouds@gmail.com> wrote:
Show 21 quoted lines
>
> On Sat, Mar 9, 2019 at 7:35 PM Martin Ågren <martin.agren@gmail.com> wrote:
>
> > @@ -285,7 +285,7 @@ Note that this option uses the no overlay mode by default (see also
> The part not shown here is
>
>     Using `--recurse-submodules` will update the content of all initialized
>     submodules according to the commit recorded in the superproject. If
>     local modifications in a submodule would be overwritten the checkout
>
> and the --recurse-submodules is rendered incorrectly (not with
> monospace font, and the quotes remain) because...
>
> >         will fail unless `-f` is used. If nothing (or `--no-recurse-submodules`)
> >         is used, the work trees of submodules will not be updated.
> >         Just like linkgit:git-submodule[1], this will detach the
> > -       submodules HEAD.
> > +       submodules' `HEAD`.
>
> ...of this apostrophe, it seems, on both man and html versions. This
> is with asciidoc 8.6.9.

Oh wow, nicely debugged. Yeah, I see this with 8.6.10, but not with Asciidoctor (1.5.5).

> Martin, could you check if your asciidoc (or asciidoctor) behaves the
> same? If it's not just my buggy asciidoc version, I can turn this to
> "wil detach `HEAD` of the submodule" which should fix the problem.
Sounds good.
Martin
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 05/21] t: rename t2014-switch.sh to t2014-checkout-switch.sh

The old name does not really say that this is about 'checkout -b'. See
49d833dc07 (Revert "checkout branch: prime cache-tree fully" -
2009-05-12) for more information
---
 t/{t2014-switch.sh => t2014-checkout-switch.sh} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename t/{t2014-switch.sh => t2014-checkout-switch.sh} (100%)
Show changes to t/t2014-switch.sh +0 −0
diff --git a/t/t2014-switch.sh b/t/t2014-checkout-switch.sh
similarity index 100%
rename from t/t2014-switch.sh
rename to t/t2014-checkout-switch.sh
-- 
2.21.0.rc1.337.gdf7f8d0522
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 06/21] checkout: factor out some code in parse_branchname_arg()

This is in preparation for the new command restore, which also
needs to parse opts->source_tree but does not need all the
disambiguation logic.
---
 builtin/checkout.c | 51 ++++++++++++++++++++++++++++------------------
 1 file changed, 31 insertions(+), 20 deletions(-)
Show changes to builtin/checkout.c +31 −20
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 0e6037b296..cbdcb1417f 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1081,6 +1081,34 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
 	return git_xmerge_config(var, value, NULL);
 }
 
+static void setup_new_branch_info_and_source_tree(
+	struct branch_info *new_branch_info,
+	struct checkout_opts *opts,
+	struct object_id *rev,
+	const char *arg)
+{
+	struct tree **source_tree = &opts->source_tree;
+	struct object_id branch_rev;
+
+	new_branch_info->name = arg;
+	setup_branch_path(new_branch_info);
+
+	if (!check_refname_format(new_branch_info->path, 0) &&
+	    !read_ref(new_branch_info->path, &branch_rev))
+		oidcpy(rev, &branch_rev);
+	else
+		new_branch_info->path = NULL; /* not an existing branch */
+
+	new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
+	if (!new_branch_info->commit) {
+		/* not a commit */
+		*source_tree = parse_tree_indirect(rev);
+	} else {
+		parse_commit_or_die(new_branch_info->commit);
+		*source_tree = get_commit_tree(new_branch_info->commit);
+	}
+}
+
 static int parse_branchname_arg(int argc, const char **argv,
 				int dwim_new_local_branch_ok,
 				struct branch_info *new_branch_info,
@@ -1088,10 +1116,8 @@ static int parse_branchname_arg(int argc, const char **argv,
 				struct object_id *rev,
 				int *dwim_remotes_matched)
 {
-	struct tree **source_tree = &opts->source_tree;
 	const char **new_branch = &opts->new_branch;
 	int argcount = 0;
-	struct object_id branch_rev;
 	const char *arg;
 	int dash_dash_pos;
 	int has_dash_dash = 0;
@@ -1213,26 +1239,11 @@ static int parse_branchname_arg(int argc, const char **argv,
 	argv++;
 	argc--;
 
-	new_branch_info->name = arg;
-	setup_branch_path(new_branch_info);
-
-	if (!check_refname_format(new_branch_info->path, 0) &&
-	    !read_ref(new_branch_info->path, &branch_rev))
-		oidcpy(rev, &branch_rev);
-	else
-		new_branch_info->path = NULL; /* not an existing branch */
+	setup_new_branch_info_and_source_tree(new_branch_info, opts, rev, arg);
 
-	new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
-	if (!new_branch_info->commit) {
-		/* not a commit */
-		*source_tree = parse_tree_indirect(rev);
-	} else {
-		parse_commit_or_die(new_branch_info->commit);
-		*source_tree = get_commit_tree(new_branch_info->commit);
-	}
-
-	if (!*source_tree)                   /* case (1): want a tree */
+	if (!opts->source_tree)                   /* case (1): want a tree */
 		die(_("reference is not a tree: %s"), arg);
+
 	if (!has_dash_dash) {	/* case (3).(d) -> (1) */
 		/*
 		 * Do not complain the most common case
-- 
2.21.0.rc1.337.gdf7f8d0522
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 07/21] checkout: make "opts" in cmd_checkout() a pointer

"opts" will soon be moved out of cmd_checkout(). To keep changes in
that patch smaller, convert "opts" to a pointer and keep the real
thing behind "real_opts".
---
 builtin/checkout.c | 115 +++++++++++++++++++++++----------------------
 1 file changed, 58 insertions(+), 57 deletions(-)
Show changes to builtin/checkout.c +58 −57
diff --git a/builtin/checkout.c b/builtin/checkout.c
index cbdcb1417f..baefe3c942 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1341,82 +1341,83 @@ static int checkout_branch(struct checkout_opts *opts,
 
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
-	struct checkout_opts opts;
+	struct checkout_opts real_opts;
+	struct checkout_opts *opts = &real_opts;
 	struct branch_info new_branch_info;
 	char *conflict_style = NULL;
 	int dwim_new_local_branch, no_dwim_new_local_branch = 0;
 	int dwim_remotes_matched = 0;
 	struct option options[] = {
-		OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
-		OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
+		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
+		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"),
+		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"),
+		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),
-		OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
-		OPT_SET_INT_F('2', "ours", &opts.writeout_stage,
+		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
+		OPT_SET_INT_F('2', "ours", &opts->writeout_stage,
 			      N_("checkout our version for unmerged files"),
 			      2, PARSE_OPT_NONEG),
-		OPT_SET_INT_F('3', "theirs", &opts.writeout_stage,
+		OPT_SET_INT_F('3', "theirs", &opts->writeout_stage,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
-		OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)"),
+		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")),
-		OPT_BOOL_F(0, "overwrite-ignore", &opts.overwrite_ignore,
+		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
+		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
 		OPT_STRING(0, "conflict", &conflict_style, N_("style"),
 			   N_("conflict style (merge or diff3)")),
-		OPT_BOOL('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
-		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree,
+		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_BOOL(0, "no-guess", &no_dwim_new_local_branch,
 			 N_("do not second guess 'git checkout <no-such-branch>'")),
-		OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
+		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
 		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
 			    "checkout", "control recursive updating of submodules",
 			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
-		OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
-		OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")),
+		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
+		OPT_BOOL(0, "overlay", &opts->overlay_mode, N_("use overlay mode (default)")),
 		OPT_END(),
 	};
 
-	memset(&opts, 0, sizeof(opts));
+	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->overwrite_ignore = 1;
+	opts->prefix = prefix;
+	opts->show_progress = -1;
+	opts->overlay_mode = -1;
 
-	git_config(git_checkout_config, &opts);
+	git_config(git_checkout_config, opts);
 
-	opts.track = BRANCH_TRACK_UNSPECIFIED;
+	opts->track = BRANCH_TRACK_UNSPECIFIED;
 
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
 	dwim_new_local_branch = !no_dwim_new_local_branch;
-	if (opts.show_progress < 0) {
-		if (opts.quiet)
-			opts.show_progress = 0;
+	if (opts->show_progress < 0) {
+		if (opts->quiet)
+			opts->show_progress = 0;
 		else
-			opts.show_progress = isatty(2);
+			opts->show_progress = isatty(2);
 	}
 
 	if (conflict_style) {
-		opts.merge = 1; /* implied */
+		opts->merge = 1; /* implied */
 		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
 	}
 
-	if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)
+	if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
 		die(_("-b, -B and --orphan are mutually exclusive"));
 
-	if (opts.overlay_mode == 1 && opts.patch_mode)
+	if (opts->overlay_mode == 1 && opts->patch_mode)
 		die(_("-p and --overlay are mutually exclusive"));
 
 	/*
@@ -1424,14 +1425,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	 * and new_branch_force and new_orphan_branch will tell us which one of
 	 * -b/-B/--orphan is being used.
 	 */
-	if (opts.new_branch_force)
-		opts.new_branch = opts.new_branch_force;
+	if (opts->new_branch_force)
+		opts->new_branch = opts->new_branch_force;
 
-	if (opts.new_orphan_branch)
-		opts.new_branch = opts.new_orphan_branch;
+	if (opts->new_orphan_branch)
+		opts->new_branch = opts->new_orphan_branch;
 
 	/* --track without -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];
 		if (!argc || !strcmp(argv0, "--"))
 			die(_("--track needs a branch name"));
@@ -1440,7 +1441,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		argv0 = strchr(argv0, '/');
 		if (!argv0 || !argv0[1])
 			die(_("missing branch name; try -b"));
-		opts.new_branch = argv0 + 1;
+		opts->new_branch = argv0 + 1;
 	}
 
 	/*
@@ -1459,56 +1460,56 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	if (argc) {
 		struct object_id rev;
 		int dwim_ok =
-			!opts.patch_mode &&
+			!opts->patch_mode &&
 			dwim_new_local_branch &&
-			opts.track == BRANCH_TRACK_UNSPECIFIED &&
-			!opts.new_branch;
+			opts->track == BRANCH_TRACK_UNSPECIFIED &&
+			!opts->new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
-					     &new_branch_info, &opts, &rev,
+					     &new_branch_info, opts, &rev,
 					     &dwim_remotes_matched);
 		argv += n;
 		argc -= n;
 	}
 
 	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)
+		if (opts->new_branch && argc == 1)
 			die(_("'%s' is not a commit and a branch '%s' cannot be created from it"),
-				argv[0], opts.new_branch);
+				argv[0], opts->new_branch);
 
-		if (opts.force_detach)
+		if (opts->force_detach)
 			die(_("git checkout: --detach does not take a path argument '%s'"),
 			    argv[0]);
 
-		if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
+		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.new_branch) {
+	if (opts->new_branch) {
 		struct strbuf buf = STRBUF_INIT;
 
-		if (opts.new_branch_force)
-			opts.branch_exists = validate_branchname(opts.new_branch, &buf);
+		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);
+			opts->branch_exists =
+				validate_new_branchname(opts->new_branch, &buf, 0);
 		strbuf_release(&buf);
 	}
 
 	UNLEAK(opts);
-	if (opts.patch_mode || opts.pathspec.nr) {
-		int ret = checkout_paths(&opts, new_branch_info.name);
+	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"
@@ -1527,6 +1528,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			       dwim_remotes_matched);
 		return ret;
 	} else {
-		return checkout_branch(&opts, &new_branch_info);
+		return checkout_branch(opts, &new_branch_info);
 	}
 }
-- 
2.21.0.rc1.337.gdf7f8d0522
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 08/21] checkout: move 'confict_style' and 'dwim_..' to checkout_opts

These local variables are referenced by struct option[]. This struct
will soon be broken down, moved away and we can't rely on local
variables anymore. Move these two to struct checkout_opts in
preparation for that.
---
 builtin/checkout.c | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)
Show changes to builtin/checkout.c +10 −7
diff --git a/builtin/checkout.c b/builtin/checkout.c
index baefe3c942..4b43433941 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -47,6 +47,8 @@ struct checkout_opts {
 	int show_progress;
 	int count_checkout_paths;
 	int overlay_mode;
+	int no_dwim_new_local_branch;
+
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
 	 * should be updated accordingly.
@@ -58,6 +60,7 @@ struct checkout_opts {
 	int new_branch_log;
 	enum branch_track track;
 	struct diff_options diff_options;
+	char *conflict_style;
 
 	int branch_exists;
 	const char *prefix;
@@ -1344,8 +1347,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	struct checkout_opts real_opts;
 	struct checkout_opts *opts = &real_opts;
 	struct branch_info new_branch_info;
-	char *conflict_style = NULL;
-	int dwim_new_local_branch, no_dwim_new_local_branch = 0;
+	int dwim_new_local_branch;
 	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
@@ -1370,12 +1372,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_STRING(0, "conflict", &conflict_style, N_("style"),
+		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
 			   N_("conflict style (merge or diff3)")),
 		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_BOOL(0, "no-guess", &no_dwim_new_local_branch,
+		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
 			 N_("do not second guess 'git checkout <no-such-branch>'")),
 		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
@@ -1393,6 +1395,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts->prefix = prefix;
 	opts->show_progress = -1;
 	opts->overlay_mode = -1;
+	opts->no_dwim_new_local_branch = 0;
 
 	git_config(git_checkout_config, opts);
 
@@ -1401,7 +1404,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
-	dwim_new_local_branch = !no_dwim_new_local_branch;
+	dwim_new_local_branch = !opts->no_dwim_new_local_branch;
 	if (opts->show_progress < 0) {
 		if (opts->quiet)
 			opts->show_progress = 0;
@@ -1409,9 +1412,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			opts->show_progress = isatty(2);
 	}
 
-	if (conflict_style) {
+	if (opts->conflict_style) {
 		opts->merge = 1; /* implied */
-		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
+		git_xmerge_config("merge.conflictstyle", opts->conflict_style, NULL);
 	}
 
 	if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
-- 
2.21.0.rc1.337.gdf7f8d0522
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 09/21] checkout: split options[] array in three pieces

This is a preparation step for introducing new commands that do parts
of what checkout does. There will be two new commands, one is about
switching branches, detaching HEAD... one about checking out
paths. These share the a subset of command line options. The rest of
command line options are separate.
---
 builtin/checkout.c | 82 +++++++++++++++++++++++++++++++++-------------
 parse-options-cb.c | 17 ++++++++++
 parse-options.h    |  1 +
 3 files changed, 77 insertions(+), 23 deletions(-)
Show changes to 3 files +77 −23

builtin/checkout.c, parse-options-cb.c, parse-options.h

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 4b43433941..7d23083282 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1342,15 +1342,31 @@ static int checkout_branch(struct checkout_opts *opts,
 	return switch_branches(opts, new_branch_info);
 }
 
-int cmd_checkout(int argc, const char **argv, const char *prefix)
+static struct option *add_common_options(struct checkout_opts *opts,
+					 struct option *prevopts)
 {
-	struct checkout_opts real_opts;
-	struct checkout_opts *opts = &real_opts;
-	struct branch_info new_branch_info;
-	int dwim_new_local_branch;
-	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
+		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
+			    "checkout", "control recursive updating of submodules",
+			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
+		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
+		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
+			   PARSE_OPT_NOCOMPLETE),
+		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
+		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
+			   N_("conflict style (merge or diff3)")),
+		OPT_END()
+	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+static struct option *add_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"),
@@ -1360,34 +1376,49 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
 			BRANCH_TRACK_EXPLICIT),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
+		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
+			   N_("update ignored files (default)"),
+			   PARSE_OPT_NOCOMPLETE),
+		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
+			 N_("second guess 'git checkout <no-such-branch>'")),
+		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
+			 N_("do not check if another worktree is holding the given ref")),
+		OPT_END()
+	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+static struct option *add_checkout_path_options(struct checkout_opts *opts,
+						struct option *prevopts)
+{
+	struct option options[] = {
 		OPT_SET_INT_F('2', "ours", &opts->writeout_stage,
 			      N_("checkout our version for unmerged files"),
 			      2, PARSE_OPT_NONEG),
 		OPT_SET_INT_F('3', "theirs", &opts->writeout_stage,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
-		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
-			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
-		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
-			   N_("update ignored files (default)"),
-			   PARSE_OPT_NOCOMPLETE),
-		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
-			   N_("conflict style (merge or diff3)")),
 		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_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
-			 N_("do not second guess 'git checkout <no-such-branch>'")),
-		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
-			 N_("do not check if another worktree is holding the given ref")),
-		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
-			    "checkout", "control recursive updating of submodules",
-			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
-		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
 		OPT_BOOL(0, "overlay", &opts->overlay_mode, N_("use overlay mode (default)")),
-		OPT_END(),
+		OPT_END()
 	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+int cmd_checkout(int argc, const char **argv, const char *prefix)
+{
+	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));
@@ -1401,6 +1432,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	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,
 			     PARSE_OPT_KEEP_DASHDASH);
 
diff --git a/parse-options-cb.c b/parse-options-cb.c
index 2733393546..caaeed896f 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -122,6 +122,23 @@ int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
 	return 0;
 }
 
+struct option *parse_options_dup(const struct option *o)
+{
+	struct option *opts;
+	int nr = 0;
+
+	while (o && o->type != OPTION_END) {
+		nr++;
+		o++;
+	}
+
+	ALLOC_ARRAY(opts, nr + 1);
+	memcpy(opts, o - nr, sizeof(*o) * nr);
+	memset(opts + nr, 0, sizeof(*opts));
+	opts[nr].type = OPTION_END;
+	return opts;
+}
+
 struct option *parse_options_concat(struct option *a, struct option *b)
 {
 	struct option *ret;
diff --git a/parse-options.h b/parse-options.h
index 7d83e2971d..9a90c332a5 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -257,6 +257,7 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
 
 int parse_options_end(struct parse_opt_ctx_t *ctx);
 
+struct option *parse_options_dup(const struct option *a);
 struct option *parse_options_concat(struct option *a, struct option *b);
 
 /*----- some often used options -----*/
-- 
2.21.0.rc1.337.gdf7f8d0522
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 10/21] checkout: split part of it to new command 'switch'

"git checkout" doing too many things is a source of confusion for many users (and it even bites old timers sometimes). To remedy that, the command will be split into two new ones: switch and something-to-checkout-paths. The good old "git checkout" command is still here and will be until all (or most of users) are sick of it.

See the new man page for the final design of switch. The actual
implementation though is still pretty much the same as "git checkout"
and not completely aligned with the man page. Following patches will
adjust their behavior to match the man page.
---
 .gitignore                        |   1 +
 Documentation/config/advice.txt   |  13 +-
 Documentation/config/branch.txt   |   4 +-
 Documentation/config/checkout.txt |   9 +-
 Documentation/config/diff.txt     |   3 +-
 Documentation/git-checkout.txt    |   4 +
 Documentation/git-switch.txt      | 259 ++++++++++++++++++++++++++++++
 Documentation/gitattributes.txt   |   3 +-
 Documentation/githooks.txt        |   8 +-
 Makefile                          |   1 +
 builtin.h                         |   1 +
 builtin/checkout.c                |  60 +++++--
 command-list.txt                  |   1 +
 git.c                             |   1 +
 14 files changed, 341 insertions(+), 27 deletions(-)
 create mode 100644 Documentation/git-switch.txt
Show changes to 14 files +341 −27

.gitignore, Documentation/config/advice.txt, Documentation/config/branch.txt, Documentation/config/checkout.txt, Documentation/config/diff.txt, Documentation/git-checkout.txt, Documentation/git-switch.txt, Documentation/gitattributes.txt, Documentation/githooks.txt, Makefile, builtin.h, builtin/checkout.c, command-list.txt, git.c

diff --git a/.gitignore b/.gitignore
index 7374587f9d..c687b92b1c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -167,6 +167,7 @@
 /git-submodule
 /git-submodule--helper
 /git-svn
+/git-switch
 /git-symbolic-ref
 /git-tag
 /git-unpack-file
diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
index 88620429ea..239d479506 100644
--- a/Documentation/config/advice.txt
+++ b/Documentation/config/advice.txt
@@ -42,7 +42,8 @@ advice.*::
 		state in the output of linkgit:git-status[1], in
 		the template shown when writing commit messages in
 		linkgit:git-commit[1], and in the help message shown
-		by linkgit:git-checkout[1] when switching branch.
+		by linkgit:git-switch[1] or
+		linkgit:git-checkout[1] when switching branch.
 	statusUoption::
 		Advise to consider using the `-u` option to linkgit:git-status[1]
 		when the command takes more than 2 seconds to enumerate untracked
@@ -62,12 +63,14 @@ advice.*::
 		your information is guessed from the system username and
 		domain name.
 	detachedHead::
-		Advice shown when you used linkgit:git-checkout[1] to
-		move to the detach HEAD state, to instruct how to create
-		a local branch after the fact.
+		Advice shown when you used
+		linkgit:git-switch[1] or linkgit:git-checkout[1]
+		to move to the detach HEAD state, to instruct how to
+		create a local branch after the fact.
 	checkoutAmbiguousRemoteBranchName::
 		Advice shown when the argument to
-		linkgit:git-checkout[1] ambiguously resolves to a
+		linkgit:git-checkout[1] and linkgit:git-switch[1]
+		ambiguously resolves to a
 		remote tracking branch on more than one remote in
 		situations where an unambiguous argument would have
 		otherwise caused a remote-tracking branch to be
diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index 019d60ede2..8050466159 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -1,5 +1,5 @@
 branch.autoSetupMerge::
-	Tells 'git branch' and 'git checkout' to set up new branches
+	Tells 'git branch', 'git switch' and 'git checkout' to set up new branches
 	so that linkgit:git-pull[1] will appropriately merge from the
 	starting point branch. Note that even if this option is not set,
 	this behavior can be chosen per-branch using the `--track`
@@ -11,7 +11,7 @@ branch.autoSetupMerge::
 	branch. This option defaults to true.
 
 branch.autoSetupRebase::
-	When a new branch is created with 'git branch' or 'git checkout'
+	When a new branch is created with 'git branch', 'git switch' or 'git checkout'
 	that tracks another branch, this variable tells Git to set
 	up pull to rebase instead of merge (see "branch.<name>.rebase").
 	When `never`, rebase is never automatically set to true.
diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt
index c4118fa196..d6872ffa83 100644
--- a/Documentation/config/checkout.txt
+++ b/Documentation/config/checkout.txt
@@ -1,5 +1,6 @@
 checkout.defaultRemote::
-	When you run 'git checkout <something>' and only have one
+	When you run 'git checkout <something>'
+	or 'git switch <something>' and only have one
 	remote, it may implicitly fall back on checking out and
 	tracking e.g. 'origin/<something>'. This stops working as soon
 	as you have more than one remote with a '<something>'
@@ -8,8 +9,10 @@ checkout.defaultRemote::
 	disambiguation. The typical use-case is to set this to
 	`origin`.
 +
-Currently this is used by linkgit:git-checkout[1] when 'git checkout
-<something>' will checkout the '<something>' branch on another remote,
+Currently this is used by linkgit:git-switch[1] and
+linkgit:git-checkout[1] when 'git checkout <something>'
+or 'git switch <something>'
+will checkout the '<something>' branch on another remote,
 and by linkgit:git-worktree[1] when 'git worktree add' refers to a
 remote branch. This setting might be used for other checkout-like
 commands or functionality in the future.
diff --git a/Documentation/config/diff.txt b/Documentation/config/diff.txt
index e48bb987d7..b3b304ee12 100644
--- a/Documentation/config/diff.txt
+++ b/Documentation/config/diff.txt
@@ -78,7 +78,8 @@ diff.external::
 diff.ignoreSubmodules::
 	Sets the default value of --ignore-submodules. Note that this
 	affects only 'git diff' Porcelain, and not lower level 'diff'
-	commands such as 'git diff-files'. 'git checkout' also honors
+	commands such as 'git diff-files'. 'git checkout'
+	and 'git switch' also honor
 	this setting when reporting uncommitted changes. Setting it to
 	'all' disables the submodule summary normally shown by 'git commit'
 	and 'git status' when `status.submoduleSummary` is set unless it is
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 1b9d689933..ac355dc3f3 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -560,6 +560,10 @@ $ edit frotz
 $ git add frotz
 ------------
 
+SEE ALSO
+--------
+linkgit:git-switch[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
new file mode 100644
index 0000000000..ff60ba69fb
--- /dev/null
+++ b/Documentation/git-switch.txt
@@ -0,0 +1,259 @@
+git-switch(1)
+=============
+
+NAME
+----
+git-switch - Switch branches
+
+SYNOPSIS
+--------
+[verse]
+'git switch' [<options>] [--guess] <branch>
+'git switch' [<options>] --detach [<start-point>]
+'git switch' [<options>] (-c|-C|--orphan) <new-branch> [<start-point>]
+
+DESCRIPTION
+-----------
+Switch to a specified branch. The working tree and the index are
+updated to match the branch. All new commits will be added to the tip
+of this branch.
+
+Optionally a new branch could be created with either `-c` or `-C`, or
+detach the working tree from any branch with `--detach`, along with
+switching.
+
+Switching branches does not require a clean index and working tree
+(i.e. no differences compared to 'HEAD'). The operation is aborted
+however if the switch leads to loss of local changes, unless told
+otherwise.
+
+OPTIONS
+-------
+<branch>::
+	Branch to switch to.
+
+<new-branch>::
+	Name for the new branch.
+
+<start-point>::
+	The name of a commit at which to switch to before creating a
+	new branch or detach from.
++
+You can use the `"@{-N}"` syntax to refer to the N-th last
+branch/commit switched to "git switch" or "git checkout"
+operation. You may also specify `-` which is synonymous to `"@{-1}"`.
++
+As a special case, you may use `"A...B"` as a shortcut for the merge
+base of `A` and `B` if there is exactly one merge base. You can leave
+out at most one of `A` and `B`, in which case it defaults to `HEAD`.
+
+-c <new-branch>::
+--create <new-branch>::
+	Create a new branch named `<new-branch>` starting at
+	`<start-point>` before switching to the branch. This is a
+	convenient shortcut for:
++
+------------
+$ git branch <new-branch>
+$ git switch <new-branch>
+------------
+
+-C <new-branch>::
+--force-create <new-branch>::
+	Similar to `--create` except that if `<new-branch>` already
+	exists, it will be reset to `<start-point>`. This is a
+	convenient shortcut for:
++
+------------
+$ git branch -f <new-branch>
+$ git switch <new-branch>
+------------
+
+-d::
+--detach::
+	Switch to a commit for inspection and discardable
+	experiments. See the "DETACHED HEAD" section in
+	linkgit:git-checkout[1] for details.
+
+-g::
+--guess::
+	If `<branch>` is not found but there does exist a tracking
+	branch in exactly one remote (call it `<remote>`) with a
+	matching name, treat as equivalent to
++
+------------
+$ git switch -c <branch> --track <remote>/<branch>
+------------
++
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to e.g. `checkout.defaultRemote=origin`
+to always checkout remote branches from there if `<branch>` is
+ambiguous but exists on the 'origin' remote. See also
+`checkout.defaultRemote` in linkgit:git-config[1].
+
+-q::
+--quiet::
+	Quiet, suppress feedback messages.
+
+-f::
+--force::
+	Proceed even if the index or the working tree differs from
+	HEAD. Both the index and working tree are restored to match
+	the switching target. This is used to throw away local
+	changes.
+
+--progress::
+--no-progress::
+	Progress status is reported on the standard error stream
+	by default when it is attached to a terminal, unless `--quiet`
+	is specified. This flag enables progress reporting even if not
+	attached to a terminal, regardless of `--quiet`.
+
+-t::
+--track::
+	When creating a new branch, set up "upstream" configuration.
+	`-c` is implied. See "--track" in linkgit:git-branch[1] for
+	details.
++
+If no `-c` option is given, the name of the new branch will be derived
+from the remote-tracking branch, by looking at the local part of the
+refspec configured for the corresponding remote, and then stripping
+the initial part up to the "*".  This would tell us to use "hack" as
+the local branch when branching off of "origin/hack" (or
+"remotes/origin/hack", or even "refs/remotes/origin/hack").  If the
+given name has no slash, or the above guessing results in an empty
+name, the guessing is aborted.  You can explicitly give a name with
+`-c` in such a case.
+
+--no-track::
+	Do not set up "upstream" configuration, even if the
+	branch.autoSetupMerge configuration variable is true.
+
+-m::
+--merge::
+	If you have local modifications to one or more files that are
+	different between the current branch and the branch to which
+	you are switching, the command refuses to switch branches in
+	order to preserve your modifications in context.  However,
+	with this option, a three-way merge between the current
+	branch, your working tree contents, and the new branch is
+	done, and you will be on the new branch.
++
+When a merge conflict happens, the index entries for conflicting
+paths are left unmerged, and you need to resolve the conflicts
+and mark the resolved paths with `git add` (or `git rm` if the merge
+should result in deletion of the path).
+
+--conflict=<style>::
+	The same as --merge option above, but changes the way the
+	conflicting hunks are presented, overriding the
+	merge.conflictStyle configuration variable.  Possible values are
+	"merge" (default) and "diff3" (in addition to what is shown by
+	"merge" style, shows the original contents).
+
+--orphan <new-branch>::
+	Create a new 'orphan' branch, named `<new-branch>`, started from
+	`<start-point>` and switch to it. See explanation of the same
+	option in linkgit:git-checkout[1] for details.
+
+--ignore-other-worktrees::
+	`git switch` refuses when the wanted ref is already
+	checked out by another worktree. This option makes it check
+	the ref out anyway. In other words, the ref can be held by
+	more than one worktree.
+
+--recurse-submodules::
+--no-recurse-submodules::
+	Using --recurse-submodules will update the content of all initialized
+	submodules according to the commit recorded in the superproject. If
+	local modifications in a submodule would be overwritten the checkout
+	will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
+	is used, the work trees of submodules will not be updated.
+	Just like linkgit:git-submodule[1], this will detach the
+	submodules HEAD.
+
+EXAMPLES
+--------
+
+The following command switches to the "master" branch:
+
+------------
+$ git switch master
+------------
+
+After working in the wrong branch, switching to the correct branch
+would be done using:
+
+------------
+$ git switch mytopic
+------------
+
+However, your "wrong" branch and correct "mytopic" branch may differ
+in files that you have modified locally, in which case the above
+switch would fail like this:
+
+------------
+$ git switch mytopic
+error: You have local changes to 'frotz'; not switching branches.
+------------
+
+You can give the `-m` flag to the command, which would try a three-way
+merge:
+
+------------
+$ git switch -m mytopic
+Auto-merging frotz
+------------
+
+After this three-way merge, the local modifications are _not_
+registered in your index file, so `git diff` would show you what
+changes you made since the tip of the new branch.
+
+To switch back to the previous branch before we switched to mytopic
+(i.e. "master" branch):
+
+------------
+$ git switch -
+------------
+
+You can grow a new branch from any commit. For example, switch to
+"HEAD~3" and create branch "fixup":
+
+------------
+$ git switch -c fixup HEAD~3
+Switched to a new branch 'fixup'
+------------
+
+If you want to start a new branch from a remote branch of the same
+name, use `-g`:
+
+------------
+$ git switch -g new-topic
+Branch 'new-topic' set up to track remote branch 'new-topic' from 'origin'
+Switched to a new branch 'new-topic'
+------------
+
+To check out commit "HEAD~3" for temporary inspection or experiment
+without creating a new branch:
+
+------------
+$ git switch --detach HEAD~3
+HEAD is now at 9fc9555312 Merge branch 'cc/shared-index-permbits'
+------------
+
+If it turns out whatever you have done is worth keeping, you can
+always create a new name for it (without switching away):
+
+------------
+$ git switch -c good-surprises
+------------
+
+SEE ALSO
+--------
+linkgit:git-checkout[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 9b41f81c06..cd0f9fa507 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -112,7 +112,8 @@ Checking-out and checking-in
 
 These attributes affect how the contents stored in the
 repository are copied to the working tree files when commands
-such as 'git checkout' and 'git merge' run.  They also affect how
+such as 'git switch', 'git checkout'  and 'git merge' run.
+They also affect how
 Git stores the contents you prepare in the working tree in the
 repository upon 'git add' and 'git commit'.
 
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 5bf653c111..8ff72f0613 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -165,12 +165,13 @@ rebased, and is not set when rebasing the current branch.
 post-checkout
 ~~~~~~~~~~~~~
 
-This hook is invoked when a linkgit:git-checkout[1] is run after having updated the
+This hook is invoked when a linkgit:git-checkout[1] or
+linkgit:git-switch[1] is run after having updated the
 worktree.  The hook is given three parameters: the ref of the previous HEAD,
 the ref of the new HEAD (which may or may not have changed), and a flag
 indicating whether the checkout was a branch checkout (changing branches,
 flag=1) or a file checkout (retrieving a file from the index, flag=0).
-This hook cannot affect the outcome of `git checkout`.
+This hook cannot affect the outcome of `git switch` or `git checkout`.
 
 It is also run after linkgit:git-clone[1], unless the `--no-checkout` (`-n`) option is
 used. The first parameter given to the hook is the null-ref, the second the
@@ -406,7 +407,8 @@ exit with a zero status.
 For example, the hook can simply run `git read-tree -u -m HEAD "$1"`
 in order to emulate `git fetch` that is run in the reverse direction
 with `git push`, as the two-tree form of `git read-tree -u -m` is
-essentially the same as `git checkout` that switches branches while
+essentially the same as `git switch` or `git checkout`
+that switches branches while
 keeping the local changes in the working tree that do not interfere
 with the difference between the branches.
 
diff --git a/Makefile b/Makefile
index 148668368b..8e91db73ad 100644
--- a/Makefile
+++ b/Makefile
@@ -802,6 +802,7 @@ BUILT_INS += git-merge-subtree$X
 BUILT_INS += git-show$X
 BUILT_INS += git-stage$X
 BUILT_INS += git-status$X
+BUILT_INS += git-switch$X
 BUILT_INS += git-whatchanged$X
 
 # what 'all' will build and 'install' will install in gitexecdir,
diff --git a/builtin.h b/builtin.h
index 6538932e99..c64e44450e 100644
--- a/builtin.h
+++ b/builtin.h
@@ -227,6 +227,7 @@ extern int cmd_show_index(int argc, const char **argv, const char *prefix);
 extern int cmd_status(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
+extern int cmd_switch(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 7d23083282..1eff10dbef 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -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;
@@ -1411,33 +1416,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;
@@ -1570,3 +1567,42 @@ 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 = NULL;
+	int ret;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.no_dwim_new_local_branch = 0;
+
+	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);
+
+	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;
+	int ret;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.no_dwim_new_local_branch = 0;
+
+	options = parse_options_dup(options);
+	options = add_common_options(&opts, options);
+	options = add_switch_branch_options(&opts, options);
+
+	ret = checkout_main(argc, argv, prefix, &opts,
+			    options, switch_branch_usage);
+	FREE_AND_NULL(options);
+	return ret;
+}
diff --git a/command-list.txt b/command-list.txt
index 3a9af104b5..13317f47d4 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -171,6 +171,7 @@ git-status                              mainporcelain           info
 git-stripspace                          purehelpers
 git-submodule                           mainporcelain
 git-svn                                 foreignscminterface
+git-switch                              mainporcelain           history
 git-symbolic-ref                        plumbingmanipulators
 git-tag                                 mainporcelain           history
 git-unpack-file                         plumbinginterrogators
diff --git a/git.c b/git.c
index 2014aab6b8..39582cf511 100644
--- a/git.c
+++ b/git.c
@@ -573,6 +573,7 @@ static struct cmd_struct commands[] = {
 	{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
 	{ "stripspace", cmd_stripspace },
 	{ "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
+	{ "switch", cmd_switch, RUN_SETUP | NEED_WORK_TREE },
 	{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
 	{ "tag", cmd_tag, RUN_SETUP | DELAY_PAGER_CONFIG },
 	{ "unpack-file", cmd_unpack_file, RUN_SETUP | NO_PARSEOPT },
-- 
2.21.0.rc1.337.gdf7f8d0522
Martin Ågren· Mar 9, 2019, 12:36 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Fri, 8 Mar 2019 at 11:00, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> ---
(Missing signed-off-by.)
> --- /dev/null
> +++ b/Documentation/git-switch.txt
> @@ -0,0 +1,259 @@
It looks like you base this on git-checkout.txt, which makes sense.
> +--recurse-submodules::
> +--no-recurse-submodules::
> +       Using --recurse-submodules will update the content of all initialized
But it's based on an older copy. You add backticks here in patch 4.
Martin
Jacob Keller· Mar 10, 2019, 22:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On March 8, 2019 1:57:41 AM PST, "Nguyễn Thái Ngọc Duy" <pclouds@gmail.com> wrote:
Show 18 quoted lines
>"git checkout" doing too many things is a source of confusion for many
>users (and it even bites old timers sometimes). To remedy that, the
>command will be split into two new ones: switch and
>something-to-checkout-paths. The good old "git checkout" command is
>still here and will be until all (or most of users) are sick of it.
>
>See the new man page for the final design of switch. The actual
>implementation though is still pretty much the same as "git checkout"
>and not completely aligned with the man page. Following patches will
>adjust their behavior to match the man page.
>---
> .gitignore                        |   1 +
> Documentation/config/advice.txt   |  13 +-
> Documentation/config/branch.txt   |   4 +-
> Documentation/config/checkout.txt |   9 +-
> Documentation/config/diff.txt     |   3 +-
> Documentation/git-checkout.txt    |   4 +
> Documentation/git-switch.txt      | 259 ++++++
Show 6 quoted lines
>+<new-branch>::
>+	Name for the new branch.
>+
>+<start-point>::
>+	The name of a commit at which to switch to before creating a
>+	new branch or detach from.
The wording here (and a few other places) feels awkward to me. I don't really have a better wording but maybe:

--- The name of the commit to switch to when creating a new branch or detaching HEAD ---

The original has weird tense when using detach.
There were a few other places like this where the wording was "or detach from" but where the verb tense was confusing

Thanks, Jake

-- 
Sent from my Android device with K-9 Mail. Please excuse my brevity.
Elijah Newren· Mar 11, 2019, 19:00 UTC · re: Jacob Keller · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Sun, Mar 10, 2019 at 5:29 PM Jacob Keller <jacob.keller@gmail.com> wrote:
>
> On March 8, 2019 1:57:41 AM PST, "Nguyễn Thái Ngọc Duy" <pclouds@gmail.com> wrote:
Show 12 quoted lines
> >+<new-branch>::
> >+      Name for the new branch.
> >+
> >+<start-point>::
> >+      The name of a commit at which to switch to before creating a
> >+      new branch or detach from.
>
> The wording here (and a few other places) feels awkward to me. I don't really have a better wording but maybe:
>
> ---
> The name of the commit to switch to when creating a new branch or detaching HEAD
> ---

I also struggle a bit to get better wording in this case. But maybe throwing another idea will inspire someone to synthesize (or rewrite) these into something better, so...

-- The starting point for the new branch. Specifying a <start-point> allows you to create a branch based on some other point in history than where HEAD currently points. (Or, in the case of --detach, allows you to inspect and detach from some other point.) --

I know it's slightly on the longer side, but it's the best I could think of.
Phillip Wood· Mar 11, 2019, 11:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

Hi Duy
On 08/03/2019 09:57, Nguyễn Thái Ngọc Duy wrote:
> "git checkout" doing too many things is a source of confusion for many
> users (and it even bites old timers sometimes). To remedy that, the
> command will be split into two new ones: switch and
> something-to-checkout-paths. 

I think this is a good idea, thanks for working on it. I wonder if it would be a good idea to have the new command refuse to checkout a new branch if there is a cherry-pick/revert/merge/rebase in progress (with an option to override the check) as switching branches in the middle of one of those is likely to be confusing to users (if I do it it is normally because I've forgotten that I've not run 'git whatever --continue').

Show 96 quoted lines
> The good old "git checkout" command is
> still here and will be until all (or most of users) are sick of it.
> 
> See the new man page for the final design of switch. The actual
> implementation though is still pretty much the same as "git checkout"
> and not completely aligned with the man page. Following patches will
> adjust their behavior to match the man page.
> ---
>  .gitignore                        |   1 +
>  Documentation/config/advice.txt   |  13 +-
>  Documentation/config/branch.txt   |   4 +-
>  Documentation/config/checkout.txt |   9 +-
>  Documentation/config/diff.txt     |   3 +-
>  Documentation/git-checkout.txt    |   4 +
>  Documentation/git-switch.txt      | 259 ++++++++++++++++++++++++++++++
>  Documentation/gitattributes.txt   |   3 +-
>  Documentation/githooks.txt        |   8 +-
>  Makefile                          |   1 +
>  builtin.h                         |   1 +
>  builtin/checkout.c                |  60 +++++--
>  command-list.txt                  |   1 +
>  git.c                             |   1 +
>  14 files changed, 341 insertions(+), 27 deletions(-)
>  create mode 100644 Documentation/git-switch.txt
> 
> diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
> new file mode 100644
> index 0000000000..ff60ba69fb
> --- /dev/null
> +++ b/Documentation/git-switch.txt
> @@ -0,0 +1,259 @@
> +git-switch(1)
> +=============
> +
> +NAME
> +----
> +git-switch - Switch branches
> +
> +SYNOPSIS
> +--------
> +[verse]
> +'git switch' [<options>] [--guess] <branch>
> +'git switch' [<options>] --detach [<start-point>]
> +'git switch' [<options>] (-c|-C|--orphan) <new-branch> [<start-point>]
> +
> +DESCRIPTION
> +-----------
> +Switch to a specified branch. The working tree and the index are
> +updated to match the branch. All new commits will be added to the tip
> +of this branch.
> +
> +Optionally a new branch could be created with either `-c` or `-C`, or
> +detach the working tree from any branch with `--detach`, along with
> +switching.
> +
> +Switching branches does not require a clean index and working tree
> +(i.e. no differences compared to 'HEAD'). The operation is aborted
> +however if the switch leads to loss of local changes, unless told
> +otherwise.
> +
> +OPTIONS
> +-------
> +<branch>::
> +	Branch to switch to.
> +
> +<new-branch>::
> +	Name for the new branch.
> +
> +<start-point>::
> +	The name of a commit at which to switch to before creating a
> +	new branch or detach from.
> ++
> +You can use the `"@{-N}"` syntax to refer to the N-th last
> +branch/commit switched to "git switch" or "git checkout"
> +operation. You may also specify `-` which is synonymous to `"@{-1}"`.
> ++
> +As a special case, you may use `"A...B"` as a shortcut for the merge
> +base of `A` and `B` if there is exactly one merge base. You can leave
> +out at most one of `A` and `B`, in which case it defaults to `HEAD`.
> +
> +-c <new-branch>::
> +--create <new-branch>::
> +	Create a new branch named `<new-branch>` starting at
> +	`<start-point>` before switching to the branch. This is a
> +	convenient shortcut for:
> ++
> +------------
> +$ git branch <new-branch>
> +$ git switch <new-branch>
> +------------
> +
> +-C <new-branch>::
> +--force-create <new-branch>::
> +	Similar to `--create` except that if `<new-branch>` already
> +	exists, it will be reset to `<start-point>`. This is a
> +	convenient shortcut for:

If we're renaming the options to be more meaningful then maybe we should consider a different name for this one - it has nothing to do with creating a branch. Maybe --reset or --update?

Show 40 quoted lines
> ++
> +------------
> +$ git branch -f <new-branch>
> +$ git switch <new-branch>
> +------------
> +
> +-d::
> +--detach::
> +	Switch to a commit for inspection and discardable
> +	experiments. See the "DETACHED HEAD" section in
> +	linkgit:git-checkout[1] for details.
> +
> +-g::
> +--guess::
> +	If `<branch>` is not found but there does exist a tracking
> +	branch in exactly one remote (call it `<remote>`) with a
> +	matching name, treat as equivalent to
> ++
> +------------
> +$ git switch -c <branch> --track <remote>/<branch>
> +------------
> ++
> +If the branch exists in multiple remotes and one of them is named by
> +the `checkout.defaultRemote` configuration variable, we'll use that
> +one for the purposes of disambiguation, even if the `<branch>` isn't
> +unique across all remotes. Set it to e.g. `checkout.defaultRemote=origin`
> +to always checkout remote branches from there if `<branch>` is
> +ambiguous but exists on the 'origin' remote. See also
> +`checkout.defaultRemote` in linkgit:git-config[1].
> +
> +-q::
> +--quiet::
> +	Quiet, suppress feedback messages.
> +
> +-f::
> +--force::
> +	Proceed even if the index or the working tree differs from
> +	HEAD. Both the index and working tree are restored to match
> +	the switching target. This is used to throw away local
> +	changes.

I'd always thought that --force meant "throw away my local changes if they conflict with the new branch" not "throw them away regardless" (which is better as it is deterministic). Maybe we can come up with a clearer name here --discard-changes? At the moment --force does not throw away conflicts properly (see the script below in my comments about --merge).

Show 37 quoted lines
> +
> +--progress::
> +--no-progress::
> +	Progress status is reported on the standard error stream
> +	by default when it is attached to a terminal, unless `--quiet`
> +	is specified. This flag enables progress reporting even if not
> +	attached to a terminal, regardless of `--quiet`.
> +
> +-t::
> +--track::
> +	When creating a new branch, set up "upstream" configuration.
> +	`-c` is implied. See "--track" in linkgit:git-branch[1] for
> +	details.
> ++
> +If no `-c` option is given, the name of the new branch will be derived
> +from the remote-tracking branch, by looking at the local part of the
> +refspec configured for the corresponding remote, and then stripping
> +the initial part up to the "*".  This would tell us to use "hack" as
> +the local branch when branching off of "origin/hack" (or
> +"remotes/origin/hack", or even "refs/remotes/origin/hack").  If the
> +given name has no slash, or the above guessing results in an empty
> +name, the guessing is aborted.  You can explicitly give a name with
> +`-c` in such a case.
> +
> +--no-track::
> +	Do not set up "upstream" configuration, even if the
> +	branch.autoSetupMerge configuration variable is true.
> +
> +-m::
> +--merge::
> +	If you have local modifications to one or more files that are
> +	different between the current branch and the branch to which
> +	you are switching, the command refuses to switch branches in
> +	order to preserve your modifications in context.  However,
> +	with this option, a three-way merge between the current
> +	branch, your working tree contents, and the new branch is
> +	done, and you will be on the new branch.

I was wondering what people felt about making this the default for the new command. If I'm carrying changes over to the new branch then I want them to be merged in, it's annoying to have them carried over most of the time but then sometimes have the checkout fail without specifying --merge. At the moment if --merge falls back to doing a three-way merge then it loses any newly staged files that are not in either HEAD. I think they are thrown away in reset_tree(). Here is a script to demonstrate that

d="$(mktemp -d)" && cd "$d" && git init && echo a>a && git add a && git commit -ama && git branch topic && echo b>a && git commit -amb && git checkout topic && echo c>a && echo x>x && git add x && git checkout -m master && echo 'x is missing && ! git ls-files --error-unmatch x && # a is unmerged git checkout -f topic && echo 'a is in stage 1' git ls-files -s a

Best Wishes
Phillip
Show 307 quoted lines
> ++
> +When a merge conflict happens, the index entries for conflicting
> +paths are left unmerged, and you need to resolve the conflicts
> +and mark the resolved paths with `git add` (or `git rm` if the merge
> +should result in deletion of the path).
> +
> +--conflict=<style>::
> +	The same as --merge option above, but changes the way the
> +	conflicting hunks are presented, overriding the
> +	merge.conflictStyle configuration variable.  Possible values are
> +	"merge" (default) and "diff3" (in addition to what is shown by
> +	"merge" style, shows the original contents).
> +
> +--orphan <new-branch>::
> +	Create a new 'orphan' branch, named `<new-branch>`, started from
> +	`<start-point>` and switch to it. See explanation of the same
> +	option in linkgit:git-checkout[1] for details.
> +
> +--ignore-other-worktrees::
> +	`git switch` refuses when the wanted ref is already
> +	checked out by another worktree. This option makes it check
> +	the ref out anyway. In other words, the ref can be held by
> +	more than one worktree.
> +
> +--recurse-submodules::
> +--no-recurse-submodules::
> +	Using --recurse-submodules will update the content of all initialized
> +	submodules according to the commit recorded in the superproject. If
> +	local modifications in a submodule would be overwritten the checkout
> +	will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
> +	is used, the work trees of submodules will not be updated.
> +	Just like linkgit:git-submodule[1], this will detach the
> +	submodules HEAD.
> +
> +EXAMPLES
> +--------
> +
> +The following command switches to the "master" branch:
> +
> +------------
> +$ git switch master
> +------------
> +
> +After working in the wrong branch, switching to the correct branch
> +would be done using:
> +
> +------------
> +$ git switch mytopic
> +------------
> +
> +However, your "wrong" branch and correct "mytopic" branch may differ
> +in files that you have modified locally, in which case the above
> +switch would fail like this:
> +
> +------------
> +$ git switch mytopic
> +error: You have local changes to 'frotz'; not switching branches.
> +------------
> +
> +You can give the `-m` flag to the command, which would try a three-way
> +merge:
> +
> +------------
> +$ git switch -m mytopic
> +Auto-merging frotz
> +------------
> +
> +After this three-way merge, the local modifications are _not_
> +registered in your index file, so `git diff` would show you what
> +changes you made since the tip of the new branch.
> +
> +To switch back to the previous branch before we switched to mytopic
> +(i.e. "master" branch):
> +
> +------------
> +$ git switch -
> +------------
> +
> +You can grow a new branch from any commit. For example, switch to
> +"HEAD~3" and create branch "fixup":
> +
> +------------
> +$ git switch -c fixup HEAD~3
> +Switched to a new branch 'fixup'
> +------------
> +
> +If you want to start a new branch from a remote branch of the same
> +name, use `-g`:
> +
> +------------
> +$ git switch -g new-topic
> +Branch 'new-topic' set up to track remote branch 'new-topic' from 'origin'
> +Switched to a new branch 'new-topic'
> +------------
> +
> +To check out commit "HEAD~3" for temporary inspection or experiment
> +without creating a new branch:
> +
> +------------
> +$ git switch --detach HEAD~3
> +HEAD is now at 9fc9555312 Merge branch 'cc/shared-index-permbits'
> +------------
> +
> +If it turns out whatever you have done is worth keeping, you can
> +always create a new name for it (without switching away):
> +
> +------------
> +$ git switch -c good-surprises
> +------------
> +
> +SEE ALSO
> +--------
> +linkgit:git-checkout[1]
> +
> +GIT
> +---
> +Part of the linkgit:git[1] suite
> diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
> index 9b41f81c06..cd0f9fa507 100644
> --- a/Documentation/gitattributes.txt
> +++ b/Documentation/gitattributes.txt
> @@ -112,7 +112,8 @@ Checking-out and checking-in
>  
>  These attributes affect how the contents stored in the
>  repository are copied to the working tree files when commands
> -such as 'git checkout' and 'git merge' run.  They also affect how
> +such as 'git switch', 'git checkout'  and 'git merge' run.
> +They also affect how
>  Git stores the contents you prepare in the working tree in the
>  repository upon 'git add' and 'git commit'.
>  
> diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
> index 5bf653c111..8ff72f0613 100644
> --- a/Documentation/githooks.txt
> +++ b/Documentation/githooks.txt
> @@ -165,12 +165,13 @@ rebased, and is not set when rebasing the current branch.
>  post-checkout
>  ~~~~~~~~~~~~~
>  
> -This hook is invoked when a linkgit:git-checkout[1] is run after having updated the
> +This hook is invoked when a linkgit:git-checkout[1] or
> +linkgit:git-switch[1] is run after having updated the
>  worktree.  The hook is given three parameters: the ref of the previous HEAD,
>  the ref of the new HEAD (which may or may not have changed), and a flag
>  indicating whether the checkout was a branch checkout (changing branches,
>  flag=1) or a file checkout (retrieving a file from the index, flag=0).
> -This hook cannot affect the outcome of `git checkout`.
> +This hook cannot affect the outcome of `git switch` or `git checkout`.
>  
>  It is also run after linkgit:git-clone[1], unless the `--no-checkout` (`-n`) option is
>  used. The first parameter given to the hook is the null-ref, the second the
> @@ -406,7 +407,8 @@ exit with a zero status.
>  For example, the hook can simply run `git read-tree -u -m HEAD "$1"`
>  in order to emulate `git fetch` that is run in the reverse direction
>  with `git push`, as the two-tree form of `git read-tree -u -m` is
> -essentially the same as `git checkout` that switches branches while
> +essentially the same as `git switch` or `git checkout`
> +that switches branches while
>  keeping the local changes in the working tree that do not interfere
>  with the difference between the branches.
>  
> diff --git a/Makefile b/Makefile
> index 148668368b..8e91db73ad 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -802,6 +802,7 @@ BUILT_INS += git-merge-subtree$X
>  BUILT_INS += git-show$X
>  BUILT_INS += git-stage$X
>  BUILT_INS += git-status$X
> +BUILT_INS += git-switch$X
>  BUILT_INS += git-whatchanged$X
>  
>  # what 'all' will build and 'install' will install in gitexecdir,
> diff --git a/builtin.h b/builtin.h
> index 6538932e99..c64e44450e 100644
> --- a/builtin.h
> +++ b/builtin.h
> @@ -227,6 +227,7 @@ extern int cmd_show_index(int argc, const char **argv, const char *prefix);
>  extern int cmd_status(int argc, const char **argv, const char *prefix);
>  extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
>  extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
> +extern int cmd_switch(int argc, const char **argv, const char *prefix);
>  extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
>  extern int cmd_tag(int argc, const char **argv, const char *prefix);
>  extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index 7d23083282..1eff10dbef 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -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;
> @@ -1411,33 +1416,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;
> @@ -1570,3 +1567,42 @@ 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 = NULL;
> +	int ret;
> +
> +	memset(&opts, 0, sizeof(opts));
> +	opts.no_dwim_new_local_branch = 0;
> +
> +	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);
> +
> +	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;
> +	int ret;
> +
> +	memset(&opts, 0, sizeof(opts));
> +	opts.no_dwim_new_local_branch = 0;
> +
> +	options = parse_options_dup(options);
> +	options = add_common_options(&opts, options);
> +	options = add_switch_branch_options(&opts, options);
> +
> +	ret = checkout_main(argc, argv, prefix, &opts,
> +			    options, switch_branch_usage);
> +	FREE_AND_NULL(options);
> +	return ret;
> +}
> diff --git a/command-list.txt b/command-list.txt
> index 3a9af104b5..13317f47d4 100644
> --- a/command-list.txt
> +++ b/command-list.txt
> @@ -171,6 +171,7 @@ git-status                              mainporcelain           info
>  git-stripspace                          purehelpers
>  git-submodule                           mainporcelain
>  git-svn                                 foreignscminterface
> +git-switch                              mainporcelain           history
>  git-symbolic-ref                        plumbingmanipulators
>  git-tag                                 mainporcelain           history
>  git-unpack-file                         plumbinginterrogators
> diff --git a/git.c b/git.c
> index 2014aab6b8..39582cf511 100644
> --- a/git.c
> +++ b/git.c
> @@ -573,6 +573,7 @@ static struct cmd_struct commands[] = {
>  	{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
>  	{ "stripspace", cmd_stripspace },
>  	{ "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
> +	{ "switch", cmd_switch, RUN_SETUP | NEED_WORK_TREE },
>  	{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
>  	{ "tag", cmd_tag, RUN_SETUP | DELAY_PAGER_CONFIG },
>  	{ "unpack-file", cmd_unpack_file, RUN_SETUP | NO_PARSEOPT },
> 
Duy Nguyen· Mar 11, 2019, 11:47 UTC · re: Phillip Wood · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Mon, Mar 11, 2019 at 6:16 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 17 quoted lines
>
>
> Hi Duy
>
> On 08/03/2019 09:57, Nguyễn Thái Ngọc Duy wrote:
> > "git checkout" doing too many things is a source of confusion for many
> > users (and it even bites old timers sometimes). To remedy that, the
> > command will be split into two new ones: switch and
> > something-to-checkout-paths.
>
> I think this is a good idea, thanks for working on it. I wonder if it
> would be a good idea to have the new command refuse to checkout a new
> branch if there is a cherry-pick/revert/merge/rebase in progress (with
> an option to override the check) as switching branches in the middle of
> one of those is likely to be confusing to users (if I do it it is
> normally because I've forgotten that I've not run 'git whatever
> --continue').

Interesting. I think this would be a good default if we have an escape hatch (which could even come later). I often wander off to some other branch and go back. But then half the time I end up forgetting I'm in a middle of something and just "git rebase --quit" :P

Of course with git-stash (*) and git-worktree, I guess there's little reason to just switch away from a something-in-progress worktree. I'll try to implement this in the next round, unless someone objects.

(*) I hope git-stash remembers and restores "something-in-progress" status. Dunno. Never used it much.

Show 9 quoted lines
> > +-C <new-branch>::
> > +--force-create <new-branch>::
> > +     Similar to `--create` except that if `<new-branch>` already
> > +     exists, it will be reset to `<start-point>`. This is a
> > +     convenient shortcut for:
>
> If we're renaming the options to be more meaningful then maybe we should
> consider a different name for this one - it has nothing to do with
> creating a branch. Maybe --reset or --update?

-C can also create a new branch like -c though. --reset or --update does not convey that (and --update sounds a bit too safe). Another option is --recreate.

Show 13 quoted lines
> > +-f::
> > +--force::
> > +     Proceed even if the index or the working tree differs from
> > +     HEAD. Both the index and working tree are restored to match
> > +     the switching target. This is used to throw away local
> > +     changes.
>
> I'd always thought that --force meant "throw away my local changes if
> they conflict with the new branch" not "throw them away regardless"
> (which is better as it is deterministic). Maybe we can come up with a
> clearer name here --discard-changes? At the moment --force does not
> throw away conflicts properly (see the script below in my comments about
> --merge).

Yeah if you want to redefine --force, now is a very good time. Personally I'd rather have separate options than the "one catch all" --force (or worse, multiple of --force). I'll leave this for the community to decide.

Adding Elijah too. He also had some concern about "git restore --force". Maybe he's interested in this as well.

-- 
Duy
Phillip Wood· Mar 11, 2019, 17:03 UTC · re: Duy Nguyen · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

Hi Duy
On 11/03/2019 11:47, Duy Nguyen wrote:
Show 30 quoted lines
> On Mon, Mar 11, 2019 at 6:16 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
>>
>>
>> Hi Duy
>>
>> On 08/03/2019 09:57, Nguyễn Thái Ngọc Duy wrote:
>>> "git checkout" doing too many things is a source of confusion for many
>>> users (and it even bites old timers sometimes). To remedy that, the
>>> command will be split into two new ones: switch and
>>> something-to-checkout-paths.
>>
>> I think this is a good idea, thanks for working on it. I wonder if it
>> would be a good idea to have the new command refuse to checkout a new
>> branch if there is a cherry-pick/revert/merge/rebase in progress (with
>> an option to override the check) as switching branches in the middle of
>> one of those is likely to be confusing to users (if I do it it is
>> normally because I've forgotten that I've not run 'git whatever
>> --continue').
> 
> Interesting. I think this would be a good default if we have an escape
> hatch (which could even come later). I often wander off to some other
> branch and go back. But then half the time I end up forgetting I'm in
> a middle of something and just "git rebase --quit" :P
> 
> Of course with git-stash (*) and git-worktree, I guess there's little
> reason to just switch away from a something-in-progress worktree. I'll
> try to implement this in the next round, unless someone objects.
> 
> (*) I hope git-stash remembers and restores "something-in-progress"
> status. Dunno. Never used it much.

I don't think it does. For rebase it's non trivial as it needs to remember the refs under refs/rewritten and stop gc from collecting any of them or the original head (in theory the todo list can contain commits that the user has added from other branches as well so they'd also need to be protected from gc). For cherry-pick there are gc issues as well.

Best Wishes
Phillip
Show 37 quoted lines
> 
>>> +-C <new-branch>::
>>> +--force-create <new-branch>::
>>> +     Similar to `--create` except that if `<new-branch>` already
>>> +     exists, it will be reset to `<start-point>`. This is a
>>> +     convenient shortcut for:
>>
>> If we're renaming the options to be more meaningful then maybe we should
>> consider a different name for this one - it has nothing to do with
>> creating a branch. Maybe --reset or --update?
> 
> -C can also create a new branch like -c though. --reset or --update
> does not convey that (and --update sounds a bit too safe). Another
> option is --recreate.
> 
>>> +-f::
>>> +--force::
>>> +     Proceed even if the index or the working tree differs from
>>> +     HEAD. Both the index and working tree are restored to match
>>> +     the switching target. This is used to throw away local
>>> +     changes.
>>
>> I'd always thought that --force meant "throw away my local changes if
>> they conflict with the new branch" not "throw them away regardless"
>> (which is better as it is deterministic). Maybe we can come up with a
>> clearer name here --discard-changes? At the moment --force does not
>> throw away conflicts properly (see the script below in my comments about
>> --merge).
> 
> Yeah if you want to redefine --force, now is a very good time.
> Personally I'd rather have separate options than the "one catch all"
> --force (or worse, multiple of --force). I'll leave this for the
> community to decide.
> 
> Adding Elijah too. He also had some concern about "git restore
> --force". Maybe he's interested in this as well.
> 
Duy Nguyen· Mar 12, 2019, 11:54 UTC · re: Phillip Wood · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Tue, Mar 12, 2019 at 12:03 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 41 quoted lines
>
> Hi Duy
>
> On 11/03/2019 11:47, Duy Nguyen wrote:
> > On Mon, Mar 11, 2019 at 6:16 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
> >>
> >>
> >> Hi Duy
> >>
> >> On 08/03/2019 09:57, Nguyễn Thái Ngọc Duy wrote:
> >>> "git checkout" doing too many things is a source of confusion for many
> >>> users (and it even bites old timers sometimes). To remedy that, the
> >>> command will be split into two new ones: switch and
> >>> something-to-checkout-paths.
> >>
> >> I think this is a good idea, thanks for working on it. I wonder if it
> >> would be a good idea to have the new command refuse to checkout a new
> >> branch if there is a cherry-pick/revert/merge/rebase in progress (with
> >> an option to override the check) as switching branches in the middle of
> >> one of those is likely to be confusing to users (if I do it it is
> >> normally because I've forgotten that I've not run 'git whatever
> >> --continue').
> >
> > Interesting. I think this would be a good default if we have an escape
> > hatch (which could even come later). I often wander off to some other
> > branch and go back. But then half the time I end up forgetting I'm in
> > a middle of something and just "git rebase --quit" :P
> >
> > Of course with git-stash (*) and git-worktree, I guess there's little
> > reason to just switch away from a something-in-progress worktree. I'll
> > try to implement this in the next round, unless someone objects.
> >
> > (*) I hope git-stash remembers and restores "something-in-progress"
> > status. Dunno. Never used it much.
>
> I don't think it does. For rebase it's non trivial as it needs to
> remember the refs under refs/rewritten and stop gc from collecting any
> of them or the original head (in theory the todo list can contain
> commits that the user has added from other branches as well so they'd
> also need to be protected from gc). For cherry-pick there are gc issues
> as well.

gc issues should be fixed anyway because gc could start any time (even manually by the user). And teaching pack-objects and friends to not delete original head and the todo list does not look so hard. #leftovers stuff?

-- 
Duy
Elijah Newren· Mar 11, 2019, 17:24 UTC · re: Duy Nguyen · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Mon, Mar 11, 2019 at 4:47 AM Duy Nguyen <pclouds@gmail.com> wrote:
Show 27 quoted lines
>
> On Mon, Mar 11, 2019 at 6:16 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
> >
> > Hi Duy
> >
> > On 08/03/2019 09:57, Nguyễn Thái Ngọc Duy wrote:
> > > "git checkout" doing too many things is a source of confusion for many
> > > users (and it even bites old timers sometimes). To remedy that, the
> > > command will be split into two new ones: switch and
> > > something-to-checkout-paths.
> >
> > I think this is a good idea, thanks for working on it. I wonder if it
> > would be a good idea to have the new command refuse to checkout a new
> > branch if there is a cherry-pick/revert/merge/rebase in progress (with
> > an option to override the check) as switching branches in the middle of
> > one of those is likely to be confusing to users (if I do it it is
> > normally because I've forgotten that I've not run 'git whatever
> > --continue').
>
> Interesting. I think this would be a good default if we have an escape
> hatch (which could even come later). I often wander off to some other
> branch and go back. But then half the time I end up forgetting I'm in
> a middle of something and just "git rebase --quit" :P
>
> Of course with git-stash (*) and git-worktree, I guess there's little
> reason to just switch away from a something-in-progress worktree. I'll
> try to implement this in the next round, unless someone objects.
No objection here; I like this idea.
Show 16 quoted lines
> (*) I hope git-stash remembers and restores "something-in-progress"
> status. Dunno. Never used it much.
>
> > > +-C <new-branch>::
> > > +--force-create <new-branch>::
> > > +     Similar to `--create` except that if `<new-branch>` already
> > > +     exists, it will be reset to `<start-point>`. This is a
> > > +     convenient shortcut for:
> >
> > If we're renaming the options to be more meaningful then maybe we should
> > consider a different name for this one - it has nothing to do with
> > creating a branch. Maybe --reset or --update?
>
> -C can also create a new branch like -c though. --reset or --update
> does not convey that (and --update sounds a bit too safe). Another
> option is --recreate.

Maybe --recreate, but I don't see as much of a problem with the original name you gave the option. Which name is better probably depends on how you envision its usage. If you view this option as only being used if/when '-c' fails (perhaps Phillip sees it that way?), then it'd make sense to use --recreate instead. But if you think some might adopt a workflow where they just use -C without first trying -c ("create this branch, and I don't care if I made it before just create it here"), then --force-create makes sense.

Another option, possibly showing my lack of understanding why this flag was useful in the first place: just drop this set of flags from this command. People can switch to the branch and then use reset --hard <startpoint>, right? Or (if they don't care about the reflog, which they probably don't) delete the branch first and then recreate it? Not sure why we need to give another way to do these operations. (In contrast, I see -c as being used frequently enough to have merit even if it could be implemented as two separate commands.)

I don't have much of an opinion about which of these three is the best option here (I'm slightly biased towards just jettisoning the option, but I understand there might be a good reason for it even if I don't know what it is), so really I'm just giving some food for thought on this one.

Show 21 quoted lines
> > > +-f::
> > > +--force::
> > > +     Proceed even if the index or the working tree differs from
> > > +     HEAD. Both the index and working tree are restored to match
> > > +     the switching target. This is used to throw away local
> > > +     changes.
> >
> > I'd always thought that --force meant "throw away my local changes if
> > they conflict with the new branch" not "throw them away regardless"
> > (which is better as it is deterministic). Maybe we can come up with a
> > clearer name here --discard-changes? At the moment --force does not
> > throw away conflicts properly (see the script below in my comments about
> > --merge).
>
> Yeah if you want to redefine --force, now is a very good time.
> Personally I'd rather have separate options than the "one catch all"
> --force (or worse, multiple of --force). I'll leave this for the
> community to decide.
>
> Adding Elijah too. He also had some concern about "git restore
> --force". Maybe he's interested in this as well.

I like Phillip's suggestion of --discard-changes. I also like how he came up with a simple testcase showing one bug each with checkout's current -m and -f handling; we should fix those.

Phillip Wood· Mar 11, 2019, 20:51 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On 11/03/2019 17:24, Elijah Newren wrote:
Show 53 quoted lines
> On Mon, Mar 11, 2019 at 4:47 AM Duy Nguyen <pclouds@gmail.com> wrote:
>>
>> On Mon, Mar 11, 2019 at 6:16 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
>>>
>>> Hi Duy
>>>
>>> On 08/03/2019 09:57, Nguyễn Thái Ngọc Duy wrote:
>>>> "git checkout" doing too many things is a source of confusion for many
>>>> users (and it even bites old timers sometimes). To remedy that, the
>>>> command will be split into two new ones: switch and
>>>> something-to-checkout-paths.
>>>
>>> I think this is a good idea, thanks for working on it. I wonder if it
>>> would be a good idea to have the new command refuse to checkout a new
>>> branch if there is a cherry-pick/revert/merge/rebase in progress (with
>>> an option to override the check) as switching branches in the middle of
>>> one of those is likely to be confusing to users (if I do it it is
>>> normally because I've forgotten that I've not run 'git whatever
>>> --continue').
>>
>> Interesting. I think this would be a good default if we have an escape
>> hatch (which could even come later). I often wander off to some other
>> branch and go back. But then half the time I end up forgetting I'm in
>> a middle of something and just "git rebase --quit" :P
>>
>> Of course with git-stash (*) and git-worktree, I guess there's little
>> reason to just switch away from a something-in-progress worktree. I'll
>> try to implement this in the next round, unless someone objects.
> 
> No objection here; I like this idea.
> 
>> (*) I hope git-stash remembers and restores "something-in-progress"
>> status. Dunno. Never used it much.
>>
>>>> +-C <new-branch>::
>>>> +--force-create <new-branch>::
>>>> +     Similar to `--create` except that if `<new-branch>` already
>>>> +     exists, it will be reset to `<start-point>`. This is a
>>>> +     convenient shortcut for:
>>>
>>> If we're renaming the options to be more meaningful then maybe we should
>>> consider a different name for this one - it has nothing to do with
>>> creating a branch. Maybe --reset or --update?
>>
>> -C can also create a new branch like -c though. --reset or --update
>> does not convey that (and --update sounds a bit too safe). Another
>> option is --recreate.
> 
> Maybe --recreate, but I don't see as much of a problem with the
> original name you gave the option.  Which name is better probably
> depends on how you envision its usage.  If you view this option as
> only being used if/when '-c' fails (perhaps Phillip sees it that
> way?), 

Yes I was thinking it was used to reset an existing branch in order to reuse the name, --recreate is a good suggestion in that context.

Show 13 quoted lines
> then it'd make sense to use --recreate instead.  But if you
> think some might adopt a workflow where they just use -C without first
> trying -c ("create this branch, and I don't care if I made it before
> just create it here"), then --force-create makes sense.
> 
> Another option, possibly showing my lack of understanding why this
> flag was useful in the first place: just drop this set of flags from
> this command.  People can switch to the branch and then use reset
> --hard <startpoint>, right?  Or (if they don't care about the reflog,
> which they probably don't) delete the branch first and then recreate
> it?  Not sure why we need to give another way to do these operations.
> (In contrast, I see -c as being used frequently enough to have merit
> even if it could be implemented as two separate commands.)

I tend to agree with this but that's probably because I don't really use checkout -B. I'm not sure if it's widely used or not. I do find checkout -b convenient though.

Show 31 quoted lines
> I don't have much of an opinion about which of these three is the best
> option here (I'm slightly biased towards just jettisoning the option,
> but I understand there might be a good reason for it even if I don't
> know what it is), so really I'm just giving some food for thought on
> this one.
> 
>>>> +-f::
>>>> +--force::
>>>> +     Proceed even if the index or the working tree differs from
>>>> +     HEAD. Both the index and working tree are restored to match
>>>> +     the switching target. This is used to throw away local
>>>> +     changes.
>>>
>>> I'd always thought that --force meant "throw away my local changes if
>>> they conflict with the new branch" not "throw them away regardless"
>>> (which is better as it is deterministic). Maybe we can come up with a
>>> clearer name here --discard-changes? At the moment --force does not
>>> throw away conflicts properly (see the script below in my comments about
>>> --merge).
>>
>> Yeah if you want to redefine --force, now is a very good time.
>> Personally I'd rather have separate options than the "one catch all"
>> --force (or worse, multiple of --force). I'll leave this for the
>> community to decide.
>>
>> Adding Elijah too. He also had some concern about "git restore
>> --force". Maybe he's interested in this as well.
> 
> I like Phillip's suggestion of --discard-changes.  I also like how he
> came up with a simple testcase showing one bug each with checkout's
> current -m and -f handling; we should fix those.

With regard to discarding conflicts, do we want it to clear up any state associated with the conflicts (like reset)? They rarely happen in isolation, there's a MERGE_HEAD or CHERRY_PICK_HEAD etc. I'm not sure what it should do in the middle of a rebase or when cherry-picking a range of commits. I think it would be surprising if it was the equivalent of rebase/cherry-pick --quit but just clearing the conflicts in those contexts may not be very useful in practice.

Best Wishes
Phillip
Elijah Newren· Mar 11, 2019, 22:04 UTC · re: Phillip Wood · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Mon, Mar 11, 2019 at 1:51 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 28 quoted lines
> On 11/03/2019 17:24, Elijah Newren wrote:
> > On Mon, Mar 11, 2019 at 4:47 AM Duy Nguyen <pclouds@gmail.com> wrote:
> >> On Mon, Mar 11, 2019 at 6:16 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
> >>> On 08/03/2019 09:57, Nguyễn Thái Ngọc Duy wrote:
> >>>> "git checkout" doing too many things is a source of confusion for many
> >>>> users (and it even bites old timers sometimes). To remedy that, the
> >>>> command will be split into two new ones: switch and
> >>>> something-to-checkout-paths.
> >>>
> >>> I think this is a good idea, thanks for working on it. I wonder if it
> >>> would be a good idea to have the new command refuse to checkout a new
> >>> branch if there is a cherry-pick/revert/merge/rebase in progress (with
> >>> an option to override the check) as switching branches in the middle of
> >>> one of those is likely to be confusing to users (if I do it it is
> >>> normally because I've forgotten that I've not run 'git whatever
> >>> --continue').
> >>
> >> Interesting. I think this would be a good default if we have an escape
> >> hatch (which could even come later). I often wander off to some other
> >> branch and go back. But then half the time I end up forgetting I'm in
> >> a middle of something and just "git rebase --quit" :P
> >>
> >> Of course with git-stash (*) and git-worktree, I guess there's little
> >> reason to just switch away from a something-in-progress worktree. I'll
> >> try to implement this in the next round, unless someone objects.
> >
> > No objection here; I like this idea.
> >
Keeping this hunk since it's now relevant to the comment below...
Show 33 quoted lines
> >>>> +-f::
> >>>> +--force::
> >>>> +     Proceed even if the index or the working tree differs from
> >>>> +     HEAD. Both the index and working tree are restored to match
> >>>> +     the switching target. This is used to throw away local
> >>>> +     changes.
> >>>
> >>> I'd always thought that --force meant "throw away my local changes if
> >>> they conflict with the new branch" not "throw them away regardless"
> >>> (which is better as it is deterministic). Maybe we can come up with a
> >>> clearer name here --discard-changes? At the moment --force does not
> >>> throw away conflicts properly (see the script below in my comments about
> >>> --merge).
> >>
> >> Yeah if you want to redefine --force, now is a very good time.
> >> Personally I'd rather have separate options than the "one catch all"
> >> --force (or worse, multiple of --force). I'll leave this for the
> >> community to decide.
> >>
> >> Adding Elijah too. He also had some concern about "git restore
> >> --force". Maybe he's interested in this as well.
> >
> > I like Phillip's suggestion of --discard-changes.  I also like how he
> > came up with a simple testcase showing one bug each with checkout's
> > current -m and -f handling; we should fix those.
>
> With regard to discarding conflicts, do we want it to clear up any state
> associated with the conflicts (like reset)? They rarely happen in
> isolation, there's a MERGE_HEAD or CHERRY_PICK_HEAD etc. I'm not sure
> what it should do in the middle of a rebase or when cherry-picking a
> range of commits. I think it would be surprising if it was the
> equivalent of rebase/cherry-pick --quit but just clearing the conflicts
> in those contexts may not be very useful in practice.

You already suggested above (outside the context of --discard-changes) that we should just error out if there is some special mid-operation state (be it from a merge, cherry-pick, rebase, or bisect). The user can then manually resolve the operation first, or, perhaps use a special override to force the switch command to proceed despite the presence of mid-operation state.

Personally, I'm leaning towards --discard-changes operating within that same context; I think that mid-operation special state should require a more explicit and operation-specific step to remove (e.g. rebase --quit).

Phillip Wood· Mar 12, 2019, 10:58 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

Hi Elijah
On 11/03/2019 22:04, Elijah Newren wrote:
Show 77 quoted lines
> On Mon, Mar 11, 2019 at 1:51 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
>> On 11/03/2019 17:24, Elijah Newren wrote:
>>> On Mon, Mar 11, 2019 at 4:47 AM Duy Nguyen <pclouds@gmail.com> wrote:
>>>> On Mon, Mar 11, 2019 at 6:16 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
>>>>> On 08/03/2019 09:57, Nguyễn Thái Ngọc Duy wrote:
>>>>>> "git checkout" doing too many things is a source of confusion for many
>>>>>> users (and it even bites old timers sometimes). To remedy that, the
>>>>>> command will be split into two new ones: switch and
>>>>>> something-to-checkout-paths.
>>>>>
>>>>> I think this is a good idea, thanks for working on it. I wonder if it
>>>>> would be a good idea to have the new command refuse to checkout a new
>>>>> branch if there is a cherry-pick/revert/merge/rebase in progress (with
>>>>> an option to override the check) as switching branches in the middle of
>>>>> one of those is likely to be confusing to users (if I do it it is
>>>>> normally because I've forgotten that I've not run 'git whatever
>>>>> --continue').
>>>>
>>>> Interesting. I think this would be a good default if we have an escape
>>>> hatch (which could even come later). I often wander off to some other
>>>> branch and go back. But then half the time I end up forgetting I'm in
>>>> a middle of something and just "git rebase --quit" :P
>>>>
>>>> Of course with git-stash (*) and git-worktree, I guess there's little
>>>> reason to just switch away from a something-in-progress worktree. I'll
>>>> try to implement this in the next round, unless someone objects.
>>>
>>> No objection here; I like this idea.
>>>
> 
> Keeping this hunk since it's now relevant to the comment below...
> 
>>>>>> +-f::
>>>>>> +--force::
>>>>>> +     Proceed even if the index or the working tree differs from
>>>>>> +     HEAD. Both the index and working tree are restored to match
>>>>>> +     the switching target. This is used to throw away local
>>>>>> +     changes.
>>>>>
>>>>> I'd always thought that --force meant "throw away my local changes if
>>>>> they conflict with the new branch" not "throw them away regardless"
>>>>> (which is better as it is deterministic). Maybe we can come up with a
>>>>> clearer name here --discard-changes? At the moment --force does not
>>>>> throw away conflicts properly (see the script below in my comments about
>>>>> --merge).
>>>>
>>>> Yeah if you want to redefine --force, now is a very good time.
>>>> Personally I'd rather have separate options than the "one catch all"
>>>> --force (or worse, multiple of --force). I'll leave this for the
>>>> community to decide.
>>>>
>>>> Adding Elijah too. He also had some concern about "git restore
>>>> --force". Maybe he's interested in this as well.
>>>
>>> I like Phillip's suggestion of --discard-changes.  I also like how he
>>> came up with a simple testcase showing one bug each with checkout's
>>> current -m and -f handling; we should fix those.
>>
>> With regard to discarding conflicts, do we want it to clear up any state
>> associated with the conflicts (like reset)? They rarely happen in
>> isolation, there's a MERGE_HEAD or CHERRY_PICK_HEAD etc. I'm not sure
>> what it should do in the middle of a rebase or when cherry-picking a
>> range of commits. I think it would be surprising if it was the
>> equivalent of rebase/cherry-pick --quit but just clearing the conflicts
>> in those contexts may not be very useful in practice.
> 
> You already suggested above (outside the context of --discard-changes)
> that we should just error out if there is some special mid-operation
> state (be it from a merge, cherry-pick, rebase, or bisect).  The user
> can then manually resolve the operation first, or, perhaps use a
> special override to force the switch command to proceed despite the
> presence of mid-operation state.
> 
> Personally, I'm leaning towards --discard-changes operating within
> that same context; I think that mid-operation special state should
> require a more explicit and operation-specific step to remove (e.g.
> rebase --quit).

I think that makes sense, I was wondering if --discard-changes should remove the state of a conflicted merge/single cherry-pick/revert but it is probably simpler and easier to understand just to error out. I think it should still clean up conflicts from other checkouts and applying stashes though as there is no other state in those cases.

Best Wishes
Phillip
Duy Nguyen· Mar 12, 2019, 12:19 UTC · re: Phillip Wood · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Tue, Mar 12, 2019 at 3:51 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 17 quoted lines
> > then it'd make sense to use --recreate instead.  But if you
> > think some might adopt a workflow where they just use -C without first
> > trying -c ("create this branch, and I don't care if I made it before
> > just create it here"), then --force-create makes sense.
> >
> > Another option, possibly showing my lack of understanding why this
> > flag was useful in the first place: just drop this set of flags from
> > this command.  People can switch to the branch and then use reset
> > --hard <startpoint>, right?  Or (if they don't care about the reflog,
> > which they probably don't) delete the branch first and then recreate
> > it?  Not sure why we need to give another way to do these operations.
> > (In contrast, I see -c as being used frequently enough to have merit
> > even if it could be implemented as two separate commands.)
>
> I tend to agree with this but that's probably because I don't really use
> checkout -B. I'm not sure if it's widely used or not. I do find checkout
> -b convenient though.
Yeah I think both -b and -B are about convenience.

Another point is multiple steps of switching and resetting could be really painful for people with large worktrees. Doing everything in one go is probably preferable because it generates less I/O.

The mention of "git reset --hard" triggers me because I secretly want to replace that command ("git reset", all modes), and in the last "promotion" patch, "reset --hard" is replaced with "switch -C" three times. We get really close to replacing git-reset if we have something to say "-C <current branch>". Whether this operation fits in the "switching things" mentality, I'm still not sure.

But I would not mind dropping -C for now, if people think it's not that useful. We can bring it back in incremental updates if we realize we miss it so much. I'll keep it unless somebody says something.

PS. The same probably goes for --orphan too. Wait and see if people complain, then we know how they actually use it.

-- 
Duy
Eric Sunshine· Mar 12, 2019, 15:36 UTC · re: Duy Nguyen · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Tue, Mar 12, 2019 at 8:19 AM Duy Nguyen <pclouds@gmail.com> wrote:
Show 10 quoted lines
> On Tue, Mar 12, 2019 at 3:51 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
> > I tend to agree with this but that's probably because I don't really use
> > checkout -B. I'm not sure if it's widely used or not. I do find checkout
> > -b convenient though.
>
> Yeah I think both -b and -B are about convenience.
>
> But I would not mind dropping -C for now, if people think it's not
> that useful. We can bring it back in incremental updates if we realize
> we miss it so much. I'll keep it unless somebody says something.

It's not much of a datapoint, but I do use "git checkout -B" (and therefore would use "git switch -C") periodically (in addition to -b/-c, which I use all the time). And, convenience is important, especially considering that "git switch" is already more painful in some ways than "git checkout", due to having to trigger and spell out certain things explicitly (such as detaching).

> PS. The same probably goes for --orphan too. Wait and see if people
> complain, then we know how they actually use it.

Again, not much of a datapoint, but I do use --orphan periodically. The idea of "fixing" the behavior so that --orphan starts with a clean slate is certainly appealing (since it matches how I've used orphan branches in each case).

Elijah Newren· Mar 12, 2019, 16:51 UTC · re: Eric Sunshine · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Tue, Mar 12, 2019 at 8:37 AM Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 19 quoted lines
>
> On Tue, Mar 12, 2019 at 8:19 AM Duy Nguyen <pclouds@gmail.com> wrote:
> > On Tue, Mar 12, 2019 at 3:51 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
> > > I tend to agree with this but that's probably because I don't really use
> > > checkout -B. I'm not sure if it's widely used or not. I do find checkout
> > > -b convenient though.
> >
> > Yeah I think both -b and -B are about convenience.
> >
> > But I would not mind dropping -C for now, if people think it's not
> > that useful. We can bring it back in incremental updates if we realize
> > we miss it so much. I'll keep it unless somebody says something.
>
> It's not much of a datapoint, but I do use "git checkout -B" (and
> therefore would use "git switch -C") periodically (in addition to
> -b/-c, which I use all the time). And, convenience is important,
> especially considering that "git switch" is already more painful in
> some ways than "git checkout", due to having to trigger and spell out
> certain things explicitly (such as detaching).

Ooh, interesting. I haven't used it and didn't know who did, but since you do you can probably answer the question surrounding the long-name for the -C option from earlier in the thread:

Do you use checkout -B only when checkout -b fails, or do you use it pre-emptively? The former would suggest we should use a name like --recreate, while the latter would suggest a name more like --force-create.

Show 7 quoted lines
> > PS. The same probably goes for --orphan too. Wait and see if people
> > complain, then we know how they actually use it.
>
> Again, not much of a datapoint, but I do use --orphan periodically.
> The idea of "fixing" the behavior so that --orphan starts with a clean
> slate is certainly appealing (since it matches how I've used orphan
> branches in each case).

The only three people who have commented on --orphan in this thread all apparently feel the same way: the current behavior is wrong. Maybe we can switch it to start with an empty index after all?

Eric Sunshine· Mar 12, 2019, 17:28 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Tue, Mar 12, 2019 at 12:51 PM Elijah Newren <newren@gmail.com> wrote:
Show 16 quoted lines
> On Tue, Mar 12, 2019 at 8:37 AM Eric Sunshine <sunshine@sunshineco.com> wrote:
> > It's not much of a datapoint, but I do use "git checkout -B" (and
> > therefore would use "git switch -C") periodically (in addition to
> > -b/-c, which I use all the time). And, convenience is important,
> > especially considering that "git switch" is already more painful in
> > some ways than "git checkout", due to having to trigger and spell out
> > certain things explicitly (such as detaching).
>
> Ooh, interesting.  I haven't used it and didn't know who did, but
> since you do you can probably answer the question surrounding the
> long-name for the -C option from earlier in the thread:
>
> Do you use checkout -B only when checkout -b fails, or do you use it
> pre-emptively?  The former would suggest we should use a name like
> --recreate, while the latter would suggest a name more like
> --force-create.

It doesn't come up often, but I use "git checkout -B" when I know that I want to start an existing branch over from scratch to build upon some unrelated branch. This almost always happens when I'm programming experimentally. I'll create a new branch with the desired _final_ name, start work on it, realize that some other approach might be better, so set that aside and create a new branch with some junk name, work on that for a while, possibly repeat, creating more branches with more junk names, etc. Sometimes the result is that bits a pieces of the various junk-named branches can be pulled together into a final product, in which case I may "git checkout -B <final>", overwriting the first branch I created, and then pull a few things from other junk-named branches to reach the end. (More often, though, one of the junk-named branches ends up being what I want to go with, so I just "git branch -M <final>", throwing away the original branch, and keeping the junk-named one but with the _final_ name). Sorry for the too long-winded explanation.

I think --force-create is a better name because "force" implies strongly that you're doing something potentially dangerous. Also, every option in git-branch which deletes or overwrites a branch name requires "force", so it's good precedent to follow.

Show 8 quoted lines
> > Again, not much of a datapoint, but I do use --orphan periodically.
> > The idea of "fixing" the behavior so that --orphan starts with a clean
> > slate is certainly appealing (since it matches how I've used orphan
> > branches in each case).
>
> The only three people who have commented on --orphan in this thread
> all apparently feel the same way: the current behavior is wrong.
> Maybe we can switch it to start with an empty index after all?

Starting empty may match intuition better. (More importantly, perhaps, it's harder to come up with a use-case for --orphan which doesn't involve starting with a clean slate.)

Junio C Hamano· Mar 13, 2019, 01:55 UTC · re: Eric Sunshine · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

Eric Sunshine <sunshine@sunshineco.com> writes:
Show 8 quoted lines
>> Do you use checkout -B only when checkout -b fails, or do you use it
>> pre-emptively?  The former would suggest we should use a name like
>> --recreate, while the latter would suggest a name more like
>> --force-create.
>
> It doesn't come up often, but I use "git checkout -B" when I know that
> I want to start an existing branch over from scratch to build upon
> some unrelated branch.

FWIW, I always use "checkout -B" at least twice during an integration cycle. "git checkout -B jch master" before rebuilding my private edition with select topics (including all the topics already in 'next'), plus "git checkout -B pu jch" before rebuilding the 'pu' branch with the other topics I happen to have looked at. These are similar to your use case---the branches have constant "purpose" and always exist, but they are rebuilt from scratch when they get updated.

Duy Nguyen· Mar 14, 2019, 09:17 UTC · re: Eric Sunshine · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Tue, Mar 12, 2019 at 01:28:35PM -0400, Eric Sunshine wrote:
Show 12 quoted lines
> > > Again, not much of a datapoint, but I do use --orphan periodically.
> > > The idea of "fixing" the behavior so that --orphan starts with a clean
> > > slate is certainly appealing (since it matches how I've used orphan
> > > branches in each case).
> >
> > The only three people who have commented on --orphan in this thread
> > all apparently feel the same way: the current behavior is wrong.
> > Maybe we can switch it to start with an empty index after all?
> 
> Starting empty may match intuition better. (More importantly, perhaps,
> it's harder to come up with a use-case for --orphan which doesn't
> involve starting with a clean slate.)
OK so the new --orphan description would be like this, right?
--8<--
--orphan <new-branch>::
	Create a new 'orphan' branch, named `<new-branch>`. If
	`<start-point>` is specified, the working tree is adjusted to
	match it. The index remains empty (i.e. no file is tracked).
-->8--

I was wondering if instead of the empty index, we mark on files from <start-point> as intent-to-add. That way "git commit -a" achieves the same as before, but you could still carefully craft the new index and "git commit". Dunno. Not going to implement it unless somebody says something, since I rarely (if ever?) use --orphan.

I may need someone to come up with a convincing commit message too. All I've got is "I've been told this is a good thing to do" :) -- Duy

Phillip Wood· Mar 14, 2019, 11:02 UTC · re: Duy Nguyen · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On 14/03/2019 09:17, Duy Nguyen wrote:
Show 22 quoted lines
> On Tue, Mar 12, 2019 at 01:28:35PM -0400, Eric Sunshine wrote:
>>>> Again, not much of a datapoint, but I do use --orphan periodically.
>>>> The idea of "fixing" the behavior so that --orphan starts with a clean
>>>> slate is certainly appealing (since it matches how I've used orphan
>>>> branches in each case).
>>>
>>> The only three people who have commented on --orphan in this thread
>>> all apparently feel the same way: the current behavior is wrong.
>>> Maybe we can switch it to start with an empty index after all?
>>
>> Starting empty may match intuition better. (More importantly, perhaps,
>> it's harder to come up with a use-case for --orphan which doesn't
>> involve starting with a clean slate.)
> 
> OK so the new --orphan description would be like this, right?
> 
> --8<--
> --orphan <new-branch>::
> 	Create a new 'orphan' branch, named `<new-branch>`. If
> 	`<start-point>` is specified, the working tree is adjusted to
> 	match it. The index remains empty (i.e. no file is tracked).
> -->8--

What happens if no <start-point> is given? Do you end up with an empty working tree or the current one? I'd lean towards an empty working tree (with a check that there are no uncommitted changes, users can use `restore` if they want some of the files back) but that is inconsistent with the implicit <start-point> of -c.

Best Wishes
Phillip
Show 11 quoted lines
> I was wondering if instead of the empty index, we mark on files from
> <start-point> as intent-to-add. That way "git commit -a" achieves the
> same as before, but you could still carefully craft the new index and
> "git commit". Dunno. Not going to implement it unless somebody says
> something, since I rarely (if ever?) use --orphan.
> 
> I may need someone to come up with a convincing commit message
> too. All I've got is "I've been told this is a good thing to do" :)
> --
> Duy
> 
Duy Nguyen· Mar 14, 2019, 12:56 UTC · re: Phillip Wood · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Thu, Mar 14, 2019 at 6:02 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 30 quoted lines
>
> On 14/03/2019 09:17, Duy Nguyen wrote:
> > On Tue, Mar 12, 2019 at 01:28:35PM -0400, Eric Sunshine wrote:
> >>>> Again, not much of a datapoint, but I do use --orphan periodically.
> >>>> The idea of "fixing" the behavior so that --orphan starts with a clean
> >>>> slate is certainly appealing (since it matches how I've used orphan
> >>>> branches in each case).
> >>>
> >>> The only three people who have commented on --orphan in this thread
> >>> all apparently feel the same way: the current behavior is wrong.
> >>> Maybe we can switch it to start with an empty index after all?
> >>
> >> Starting empty may match intuition better. (More importantly, perhaps,
> >> it's harder to come up with a use-case for --orphan which doesn't
> >> involve starting with a clean slate.)
> >
> > OK so the new --orphan description would be like this, right?
> >
> > --8<--
> > --orphan <new-branch>::
> >       Create a new 'orphan' branch, named `<new-branch>`. If
> >       `<start-point>` is specified, the working tree is adjusted to
> >       match it. The index remains empty (i.e. no file is tracked).
> > -->8--
>
> What happens if no <start-point> is given? Do you end up with an empty
> working tree or the current one? I'd lean towards an empty working tree
> (with a check that there are no uncommitted changes, users can use
> `restore` if they want some of the files back) but that is inconsistent
> with the implicit <start-point> of -c.

I was thinking default <start-point> is HEAD. But yeah empty tree makes more sense since you can always say "switch --orphan <branch> HEAD" but can't really say "give me an empty tree".

-- 
Duy
Elijah Newren· Mar 14, 2019, 14:46 UTC · re: Duy Nguyen · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Thu, Mar 14, 2019 at 2:18 AM Duy Nguyen <pclouds@gmail.com> wrote:
Show 29 quoted lines
>
> On Tue, Mar 12, 2019 at 01:28:35PM -0400, Eric Sunshine wrote:
> > > > Again, not much of a datapoint, but I do use --orphan periodically.
> > > > The idea of "fixing" the behavior so that --orphan starts with a clean
> > > > slate is certainly appealing (since it matches how I've used orphan
> > > > branches in each case).
> > >
> > > The only three people who have commented on --orphan in this thread
> > > all apparently feel the same way: the current behavior is wrong.
> > > Maybe we can switch it to start with an empty index after all?
> >
> > Starting empty may match intuition better. (More importantly, perhaps,
> > it's harder to come up with a use-case for --orphan which doesn't
> > involve starting with a clean slate.)
>
> OK so the new --orphan description would be like this, right?
>
> --8<--
> --orphan <new-branch>::
>         Create a new 'orphan' branch, named `<new-branch>`. If
>         `<start-point>` is specified, the working tree is adjusted to
>         match it. The index remains empty (i.e. no file is tracked).
> -->8--
>
> I was wondering if instead of the empty index, we mark on files from
> <start-point> as intent-to-add. That way "git commit -a" achieves the
> same as before, but you could still carefully craft the new index and
> "git commit". Dunno. Not going to implement it unless somebody says
> something, since I rarely (if ever?) use --orphan.
I don't see why <start-point> even makes sense to use with --orphan;
you should error if both are given, IMO.  The point of --orphan is to
create some entirely new history.  So, I'd expect "git switch --orphan
<new-branch>" to:
  * not create refs/heads/<new-branch>
  * set HEAD to refs/heads/<new-branch>
  * empty all tracked files from the working tree.
  * empty the index

Alternatively, you could allow <start-point> to be passed with --orphan, adjusting the above steps so that both the index and the working tree are switched to match <start-point>, but ONLY if <start-point> defaults to the empty tree when --orphan is passed.

> I may need someone to come up with a convincing commit message
> too. All I've got is "I've been told this is a good thing to do" :)
How about:

""" Switching and creating branches always involves knowing the <start-point> to begin the new branch from. Sometimes, people want to create a new branch that does not have any commits yet; --orphan is a flag to allow that. --orphan overrides the default of HEAD for <start-point> instead causing us to start from an empty history. The use of --orphan is incompatible with specifying a <start-point>. """

Junio C Hamano· Mar 18, 2019, 02:03 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

Elijah Newren <newren@gmail.com> writes:
Show 13 quoted lines
> I don't see why <start-point> even makes sense to use with --orphan;
> you should error if both are given, IMO.  The point of --orphan is to
> create some entirely new history.  So, I'd expect "git switch --orphan
> <new-branch>" to:
>   * not create refs/heads/<new-branch>
>   * set HEAD to refs/heads/<new-branch>
>   * empty all tracked files from the working tree.
>   * empty the index
>
> Alternatively, you could allow <start-point> to be passed with
> --orphan, adjusting the above steps so that both the index and the
> working tree are switched to match <start-point>, but ONLY if
> <start-point> defaults to the empty tree when --orphan is passed.

Do you mean that it's like <start-point> is not really a start-point but is an initial tree, i.e.

	switch --orphan --initial-tree=<tree-ish> <new-branch>
is a mere short-hand for
	switch --orhpan <new-branch> &&
	restore --from-tree=<tree-ish> .

I think that does make sense, but at the same time, I think a major reason why people say "checkout does too many things depending on the arguments and conext to be easily explained" is exactly due to its many "if you give X, it is like writing this longer command sequence" short hands, so...

Show 10 quoted lines
> How about:
>
> """
> Switching and creating branches always involves knowing the
> <start-point> to begin the new branch from.  Sometimes, people want to
> create a new branch that does not have any commits yet; --orphan is a
> flag to allow that.  --orphan overrides the default of HEAD for
> <start-point> instead causing us to start from an empty history.  The
> use of --orphan is incompatible with specifying a <start-point>.
> """

With or without the short-hand to say which initial tree to populate the index from, the above description makes sense to me.

Duy Nguyen· Mar 18, 2019, 12:06 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Mon, Mar 18, 2019 at 9:03 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 32 quoted lines
>
> Elijah Newren <newren@gmail.com> writes:
>
> > I don't see why <start-point> even makes sense to use with --orphan;
> > you should error if both are given, IMO.  The point of --orphan is to
> > create some entirely new history.  So, I'd expect "git switch --orphan
> > <new-branch>" to:
> >   * not create refs/heads/<new-branch>
> >   * set HEAD to refs/heads/<new-branch>
> >   * empty all tracked files from the working tree.
> >   * empty the index
> >
> > Alternatively, you could allow <start-point> to be passed with
> > --orphan, adjusting the above steps so that both the index and the
> > working tree are switched to match <start-point>, but ONLY if
> > <start-point> defaults to the empty tree when --orphan is passed.
>
> Do you mean that it's like <start-point> is not really a start-point
> but is an initial tree, i.e.
>
>         switch --orphan --initial-tree=<tree-ish> <new-branch>
>
> is a mere short-hand for
>
>         switch --orhpan <new-branch> &&
>         restore --from-tree=<tree-ish> .
>
> I think that does make sense, but at the same time, I think a major
> reason why people say "checkout does too many things depending on
> the arguments and conext to be easily explained" is exactly due to
> its many "if you give X, it is like writing this longer command
> sequence" short hands, so...

Yeah <start-point> is optional and probably only useful for people who do a lot of --orphan. I did think if there's any other command that could be a better host for this orphan branch creation. No such luck.

PS. There's one small difference between 'switch --orphan <new-branch> <tree-ish>' and your switch/restore combination: the latter refuses if you have any local changes because it has to remove all tracked files first, while the former only refuses when the local changes are outside <tree-ish>. If path A will be tracked by <tree-ish>, then whatever changes in there may stay.

-- 
Duy
Elijah Newren· Mar 18, 2019, 19:10 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Sun, Mar 17, 2019 at 7:03 PM Junio C Hamano <gitster@pobox.com> wrote:
Show 26 quoted lines
>
> Elijah Newren <newren@gmail.com> writes:
>
> > I don't see why <start-point> even makes sense to use with --orphan;
> > you should error if both are given, IMO.  The point of --orphan is to
> > create some entirely new history.  So, I'd expect "git switch --orphan
> > <new-branch>" to:
> >   * not create refs/heads/<new-branch>
> >   * set HEAD to refs/heads/<new-branch>
> >   * empty all tracked files from the working tree.
> >   * empty the index
> >
> > Alternatively, you could allow <start-point> to be passed with
> > --orphan, adjusting the above steps so that both the index and the
> > working tree are switched to match <start-point>, but ONLY if
> > <start-point> defaults to the empty tree when --orphan is passed.
>
> Do you mean that it's like <start-point> is not really a start-point
> but is an initial tree, i.e.
>
>         switch --orphan --initial-tree=<tree-ish> <new-branch>
>
> is a mere short-hand for
>
>         switch --orphan <new-branch> &&
>         restore --from-tree=<tree-ish> .
Yes.
Show 5 quoted lines
> I think that does make sense, but at the same time, I think a major
> reason why people say "checkout does too many things depending on
> the arguments and conext to be easily explained" is exactly due to
> its many "if you give X, it is like writing this longer command
> sequence" short hands, so...

Yes, this is a concern for me too. I would be happier if we made --orphan and <start-point> incompatible and avoided the need to explain how they worked together. Besides, as you point out, the wording is bad and should instead be a separate option named --initial-tree=<tree-ish> which people will then start asking us to allow them to specify even in cases when --orphan isn't (e.g. `git switch --initial-tree=HEAD maint`), which is a weird/esoteric usecase that is probably better served by using separate commands.

Show 13 quoted lines
> > How about:
> >
> > """
> > Switching and creating branches always involves knowing the
> > <start-point> to begin the new branch from.  Sometimes, people want to
> > create a new branch that does not have any commits yet; --orphan is a
> > flag to allow that.  --orphan overrides the default of HEAD for
> > <start-point> instead causing us to start from an empty history.  The
> > use of --orphan is incompatible with specifying a <start-point>.
> > """
>
> With or without the short-hand to say which initial tree to populate
> the index from, the above description makes sense to me.

Yeah, perhaps "...an empty history with all tracked files removed from the index and working tree. The use of..."

Duy Nguyen· Mar 20, 2019, 12:04 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Tue, Mar 19, 2019 at 2:10 AM Elijah Newren <newren@gmail.com> wrote:
Show 45 quoted lines
>
> On Sun, Mar 17, 2019 at 7:03 PM Junio C Hamano <gitster@pobox.com> wrote:
> >
> > Elijah Newren <newren@gmail.com> writes:
> >
> > > I don't see why <start-point> even makes sense to use with --orphan;
> > > you should error if both are given, IMO.  The point of --orphan is to
> > > create some entirely new history.  So, I'd expect "git switch --orphan
> > > <new-branch>" to:
> > >   * not create refs/heads/<new-branch>
> > >   * set HEAD to refs/heads/<new-branch>
> > >   * empty all tracked files from the working tree.
> > >   * empty the index
> > >
> > > Alternatively, you could allow <start-point> to be passed with
> > > --orphan, adjusting the above steps so that both the index and the
> > > working tree are switched to match <start-point>, but ONLY if
> > > <start-point> defaults to the empty tree when --orphan is passed.
> >
> > Do you mean that it's like <start-point> is not really a start-point
> > but is an initial tree, i.e.
> >
> >         switch --orphan --initial-tree=<tree-ish> <new-branch>
> >
> > is a mere short-hand for
> >
> >         switch --orphan <new-branch> &&
> >         restore --from-tree=<tree-ish> .
>
> Yes.
>
> > I think that does make sense, but at the same time, I think a major
> > reason why people say "checkout does too many things depending on
> > the arguments and conext to be easily explained" is exactly due to
> > its many "if you give X, it is like writing this longer command
> > sequence" short hands, so...
>
> Yes, this is a concern for me too.  I would be happier if we made
> --orphan and <start-point> incompatible and avoided the need to
> explain how they worked together.  Besides, as you point out, the
> wording is bad and should instead be a separate option named
> --initial-tree=<tree-ish> which people will then start asking us to
> allow them to specify even in cases when --orphan isn't (e.g. `git
> switch --initial-tree=HEAD maint`), which is a weird/esoteric usecase
> that is probably better served by using separate commands.

OK let's reject '--orphan <new-branch> <initial-tree>' then. It's not much work and when people come back complaining about it not working, we'll know more and may reconsider then.

-- 
Duy
Jacob Keller· Mar 15, 2019, 06:11 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Tue, Mar 12, 2019 at 9:52 AM Elijah Newren <newren@gmail.com> wrote:
Show 30 quoted lines
>
> On Tue, Mar 12, 2019 at 8:37 AM Eric Sunshine <sunshine@sunshineco.com> wrote:
> >
> > On Tue, Mar 12, 2019 at 8:19 AM Duy Nguyen <pclouds@gmail.com> wrote:
> > > On Tue, Mar 12, 2019 at 3:51 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
> > > > I tend to agree with this but that's probably because I don't really use
> > > > checkout -B. I'm not sure if it's widely used or not. I do find checkout
> > > > -b convenient though.
> > >
> > > Yeah I think both -b and -B are about convenience.
> > >
> > > But I would not mind dropping -C for now, if people think it's not
> > > that useful. We can bring it back in incremental updates if we realize
> > > we miss it so much. I'll keep it unless somebody says something.
> >
> > It's not much of a datapoint, but I do use "git checkout -B" (and
> > therefore would use "git switch -C") periodically (in addition to
> > -b/-c, which I use all the time). And, convenience is important,
> > especially considering that "git switch" is already more painful in
> > some ways than "git checkout", due to having to trigger and spell out
> > certain things explicitly (such as detaching).
>
> Ooh, interesting.  I haven't used it and didn't know who did, but
> since you do you can probably answer the question surrounding the
> long-name for the -C option from earlier in the thread:
>
> Do you use checkout -B only when checkout -b fails, or do you use it
> pre-emptively?  The former would suggest we should use a name like
> --recreate, while the latter would suggest a name more like
> --force-create.
I use it periodically sometimes like this:
git checkout -B master origin/master

Essentially, I use it as a way to quickly and forcefully re-create a branch that tracks the remote branch.

For example, I might have made a commit or two on master, and realized I should be doing a separate branch, and I want to quickly make that a branch by doing

git checkout -b some-branch.

Then, later I want to switch back to master, and I really just want master to be exactly what the remote had. Sure I can do that in a lot of ways, but it's nice to have a somewhat convenient shortcut to do it in one command.

So, for me, it's natural to think of this as a "--force-create", but use of either --recreate or --force- create work with my setup.

Basically, for *me*, I almost always use it when I know there's a branch that I want to re-setup there, and rarely verify that fact using a "oh, -b failed, I guess I need -B"

Regards, Jake

Duy Nguyen· Mar 12, 2019, 11:58 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Tue, Mar 12, 2019 at 12:25 AM Elijah Newren <newren@gmail.com> wrote:
Show 31 quoted lines
>
> On Mon, Mar 11, 2019 at 4:47 AM Duy Nguyen <pclouds@gmail.com> wrote:
> >
> > On Mon, Mar 11, 2019 at 6:16 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
> > >
> > > Hi Duy
> > >
> > > On 08/03/2019 09:57, Nguyễn Thái Ngọc Duy wrote:
> > > > "git checkout" doing too many things is a source of confusion for many
> > > > users (and it even bites old timers sometimes). To remedy that, the
> > > > command will be split into two new ones: switch and
> > > > something-to-checkout-paths.
> > >
> > > I think this is a good idea, thanks for working on it. I wonder if it
> > > would be a good idea to have the new command refuse to checkout a new
> > > branch if there is a cherry-pick/revert/merge/rebase in progress (with
> > > an option to override the check) as switching branches in the middle of
> > > one of those is likely to be confusing to users (if I do it it is
> > > normally because I've forgotten that I've not run 'git whatever
> > > --continue').
> >
> > Interesting. I think this would be a good default if we have an escape
> > hatch (which could even come later). I often wander off to some other
> > branch and go back. But then half the time I end up forgetting I'm in
> > a middle of something and just "git rebase --quit" :P
> >
> > Of course with git-stash (*) and git-worktree, I guess there's little
> > reason to just switch away from a something-in-progress worktree. I'll
> > try to implement this in the next round, unless someone objects.
>
> No objection here; I like this idea.

One last thing. What about --detach? Should it have the same protection or should we let the user doing --detach (experiments) take the responsibity to not screw themselves up?

-- 
Duy
Elijah Newren· Mar 12, 2019, 17:05 UTC · re: Duy Nguyen · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Tue, Mar 12, 2019 at 4:58 AM Duy Nguyen <pclouds@gmail.com> wrote:
> On Tue, Mar 12, 2019 at 12:25 AM Elijah Newren <newren@gmail.com> wrote:
> > On Mon, Mar 11, 2019 at 4:47 AM Duy Nguyen <pclouds@gmail.com> wrote:
> > > On Mon, Mar 11, 2019 at 6:16 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 28 quoted lines
> > > > On 08/03/2019 09:57, Nguyễn Thái Ngọc Duy wrote:
> > > > > "git checkout" doing too many things is a source of confusion for many
> > > > > users (and it even bites old timers sometimes). To remedy that, the
> > > > > command will be split into two new ones: switch and
> > > > > something-to-checkout-paths.
> > > >
> > > > I think this is a good idea, thanks for working on it. I wonder if it
> > > > would be a good idea to have the new command refuse to checkout a new
> > > > branch if there is a cherry-pick/revert/merge/rebase in progress (with
> > > > an option to override the check) as switching branches in the middle of
> > > > one of those is likely to be confusing to users (if I do it it is
> > > > normally because I've forgotten that I've not run 'git whatever
> > > > --continue').
> > >
> > > Interesting. I think this would be a good default if we have an escape
> > > hatch (which could even come later). I often wander off to some other
> > > branch and go back. But then half the time I end up forgetting I'm in
> > > a middle of something and just "git rebase --quit" :P
> > >
> > > Of course with git-stash (*) and git-worktree, I guess there's little
> > > reason to just switch away from a something-in-progress worktree. I'll
> > > try to implement this in the next round, unless someone objects.
> >
> > No objection here; I like this idea.
>
> One last thing. What about --detach? Should it have the same
> protection or should we let the user doing --detach (experiments) take
> the responsibity to not screw themselves up?

My intuition here is a bit weaker; it would be nice to hear others' opinions. My best guess thinking it over a bit is that while someone doing --detach is more likely to know what they are doing than other users of the 'switch' command, they may also be the kind of person who is more likely to run interactive rebases and thus had more opportunities to forget that they are still in the middle of one. I think I've been guilty of that a few times. So, while this would be less critical for this case, I lean towards saying that it may still be helpful anyway, and if nothing else the consistency of handling all switching cases the same seems beneficial.

Phillip Wood· Mar 14, 2019, 10:42 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On 12/03/2019 17:05, Elijah Newren wrote:
Show 45 quoted lines
> On Tue, Mar 12, 2019 at 4:58 AM Duy Nguyen <pclouds@gmail.com> wrote:
>> On Tue, Mar 12, 2019 at 12:25 AM Elijah Newren <newren@gmail.com> wrote:
>>> On Mon, Mar 11, 2019 at 4:47 AM Duy Nguyen <pclouds@gmail.com> wrote:
>>>> On Mon, Mar 11, 2019 at 6:16 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
> 
>>>>> On 08/03/2019 09:57, Nguyễn Thái Ngọc Duy wrote:
>>>>>> "git checkout" doing too many things is a source of confusion for many
>>>>>> users (and it even bites old timers sometimes). To remedy that, the
>>>>>> command will be split into two new ones: switch and
>>>>>> something-to-checkout-paths.
>>>>>
>>>>> I think this is a good idea, thanks for working on it. I wonder if it
>>>>> would be a good idea to have the new command refuse to checkout a new
>>>>> branch if there is a cherry-pick/revert/merge/rebase in progress (with
>>>>> an option to override the check) as switching branches in the middle of
>>>>> one of those is likely to be confusing to users (if I do it it is
>>>>> normally because I've forgotten that I've not run 'git whatever
>>>>> --continue').
>>>>
>>>> Interesting. I think this would be a good default if we have an escape
>>>> hatch (which could even come later). I often wander off to some other
>>>> branch and go back. But then half the time I end up forgetting I'm in
>>>> a middle of something and just "git rebase --quit" :P
>>>>
>>>> Of course with git-stash (*) and git-worktree, I guess there's little
>>>> reason to just switch away from a something-in-progress worktree. I'll
>>>> try to implement this in the next round, unless someone objects.
>>>
>>> No objection here; I like this idea.
>>
>> One last thing. What about --detach? Should it have the same
>> protection or should we let the user doing --detach (experiments) take
>> the responsibity to not screw themselves up?
> 
> My intuition here is a bit weaker; it would be nice to hear others'
> opinions.  My best guess thinking it over a bit is that while someone
> doing --detach is more likely to know what they are doing than other
> users of the 'switch' command, they may also be the kind of person who
> is more likely to run interactive rebases and thus had more
> opportunities to forget that they are still in the middle of one.  I
> think I've been guilty of that a few times.  So, while this would be
> less critical for this case, I lean towards saying that it may still
> be helpful anyway, and if nothing else the consistency of handling all
> switching cases the same seems beneficial.
> I think that having all the switching cases consistent makes sense as it

is easy to understand and avoids complications if we end up adding a config option to allow an implicit --detach in the future as suggested in one of the later patches.

Best Wishes
Phillip
Duy Nguyen· Mar 14, 2019, 03:29 UTC · re: Phillip Wood · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Mon, Mar 11, 2019 at 6:16 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 15 quoted lines
> > +-m::
> > +--merge::
> > +     If you have local modifications to one or more files that are
> > +     different between the current branch and the branch to which
> > +     you are switching, the command refuses to switch branches in
> > +     order to preserve your modifications in context.  However,
> > +     with this option, a three-way merge between the current
> > +     branch, your working tree contents, and the new branch is
> > +     done, and you will be on the new branch.
>
> I was wondering what people felt about making this the default for the
> new command. If I'm carrying changes over to the new branch then I want
> them to be merged in, it's annoying to have them carried over most of
> the time but then sometimes have the checkout fail without specifying
> --merge.

If my worktree has local changes and I accidentally switch to a different branch, I could switch back without losing any local changes and the tree I have is exactly what I had before the switch. Is this still true if -m is made default?

I think sometimes a 3-way merge creates marker conflicts in file, and this does not look easy to reverse when switching back. If it's true and we can detect it, we can still abort this case (i.e. requiring -m to continue) while allowing succesful 3-way merges by default. But are successful 3-way merges reversible?

-- 
Duy
Elijah Newren· Mar 14, 2019, 05:59 UTC · re: Duy Nguyen · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Wed, Mar 13, 2019 at 8:29 PM Duy Nguyen <pclouds@gmail.com> wrote:
Show 26 quoted lines
>
> On Mon, Mar 11, 2019 at 6:16 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
> > > +-m::
> > > +--merge::
> > > +     If you have local modifications to one or more files that are
> > > +     different between the current branch and the branch to which
> > > +     you are switching, the command refuses to switch branches in
> > > +     order to preserve your modifications in context.  However,
> > > +     with this option, a three-way merge between the current
> > > +     branch, your working tree contents, and the new branch is
> > > +     done, and you will be on the new branch.
> >
> > I was wondering what people felt about making this the default for the
> > new command. If I'm carrying changes over to the new branch then I want
> > them to be merged in, it's annoying to have them carried over most of
> > the time but then sometimes have the checkout fail without specifying
> > --merge.
>
> If my worktree has local changes and I accidentally switch to a
> different branch, I could switch back without losing any local changes
> and the tree I have is exactly what I had before the switch. Is this
> still true if -m is made default?
>
> I think sometimes a 3-way merge creates marker conflicts in file, and
> this does not look easy to reverse when switching back. If it's true
> and we can detect it, we can still abort this case (i.e. requiring -m

You cannot yet do this with merge_recursive; it writes conflicts to the worktree as it goes and doesn't tell you whether the merge was successful or had conflicts until its done. So this would be very dangerous. We'd first need a way to do an in-memory merge that doesn't touch the working tree or index and which gives you the opportunity to check whether that worked before proceeding to write out any updates. That is work I plan to do (for other reasons), but not in progress currently.

> to continue) while allowing succesful 3-way merges by default. But are
> successful 3-way merges reversible?

Hmm, interesting. So, switching from branch A to B, you start with local changes on top of A that could represent a virtual commit C. So, you three-way merge C & B using A as the base. It's clean, so we have a new endpoint, call it D. Switching back to A with the --merge flag would mean a three-way merge of D & A using B as the base.

But it might be easier if I re-labelled all of these with different terms. Let's say we squash all changes from A to B into a single commit on top of A that we call commit B. Check out C (a commit representing your local changes on top of A), and cherry-pick B; that will three-way merge C & B using A as the base. It's clean, so we have a new commit, call it D. Now we tell git to revert B, which will do a three-way merge of D (current HEAD) & A (parent of B), using B as the base. So, essentially, your question about reversibility boils down to: if you can cleanly cherry-pick a commit to your current branch, can you then immediately revert that same commit on top to return to where you were before?

(Off the cuff, I _think_ the answer to that is yes due to the assumption that the cherry-pick was without conflicts, but I'm not immediately sure.)

Junio C Hamano· Mar 14, 2019, 07:23 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

Elijah Newren <newren@gmail.com> writes:
>> I think sometimes a 3-way merge creates marker conflicts in file, and
>> this does not look easy to reverse when switching back. If it's true
>> and we can detect it, we can still abort this case (i.e. requiring -m

Paths with actual conflicts are much easier to recover from than paths that cleanly merge. You have your original in stage #2, so you should be able to "restore-path --stage=2 --from-stage path..." them. Once the contents are auto-resolved cleanly, however, the cached contents are automaticaly updated to the auto-resolved result, and it needs more work to reverse the effect of the merge (it is doable, of course, as you know exactly the contents of the switched-to branch and the contents of the switching-from branch, so it is just the matter of running 3-way merge in the right direction to recover what used to be in the working tree).

Elijah Newren· Mar 14, 2019, 15:48 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Thu, Mar 14, 2019 at 12:23 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 17 quoted lines
>
> Elijah Newren <newren@gmail.com> writes:
>
> >> I think sometimes a 3-way merge creates marker conflicts in file, and
> >> this does not look easy to reverse when switching back. If it's true
> >> and we can detect it, we can still abort this case (i.e. requiring -m
>
> Paths with actual conflicts are much easier to recover from than
> paths that cleanly merge.   You have your original in stage #2, so
> you should be able to "restore-path --stage=2 --from-stage path..."
> them.  Once the contents are auto-resolved cleanly, however, the
> cached contents are automaticaly updated to the auto-resolved result,
> and it needs more work to reverse the effect of the merge (it is
> doable, of course, as you know exactly the contents of the
> switched-to branch and the contents of the switching-from branch, so
> it is just the matter of running 3-way merge in the right direction
> to recover what used to be in the working tree).

I agree that mixtures of conflicts and clean merges would be the most difficult to reverse. However, recovering from conflict cases is actually harder than you mention, at least when renames are involved. If foo is modified on both sides of history in conflicting ways AND renamed to bar in the other branch, then all three staged will be stored under the path bar. Using your method to restore would cause you to get a file named bar (though it would have the contents of the original foo).

It can unfortunately get even worse. With rename/rename(2to1) conflicts; there are up to six values that needed to be shoved into the normal three slots in the index, which merge-recursive achieves by first content merging three at a time and shoving the (possibly conflict-marker containing) results from that into two slots of the index and then two-way merging from there to get the worktree contents (possibly resulting in nested conflict markers). Checking something out of stage 2 thus not only might get the file path wrong but can also get you contents with conflict markers.

However, this whole exercise gave me an idea that answers Duy's
original question definitively: you cannot necessarily reverse a
successful (i.e. no conflicts present) three-way merge with another
three-way merge.  Here's an example to demonstrate that...let's say
you are on branch A, and you have two identical files:
  A: foo, bar
and you want to switch to branch B which renamed foo but didn't modify it:
  B: baz, bar
locally on A you had renamed bar but didn't modify it:
  C: foo, baz
(C isn't an actual commit; I just wanted a label to refer to it)
doing a "git switch -m B" will result in merge-recursive noting that
both foo and bar were renamed to baz but that both versions of baz
where identical, so we'd end up with:
  D: baz
(D isn't an actual commit; I just wanted a label to refer to it)
Now, if we were to run "git switch -m A" to go back to A,
merge-recursive would need to do a three-way merge of D & A using B as
the base.  merge-recursive would note that A renamed baz->foo, and
that D deleted bar.  So, you'd end up with:
  E: foo
Unfortunately, C != E, so our reversing was unsuccessful.

In summary, this is yet another reason that making --merge the default for either checkout or switch would be unsafe.

Junio C Hamano· Mar 14, 2019, 04:39 UTC · re: Phillip Wood · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

Phillip Wood <phillip.wood123@gmail.com> writes:
Show 12 quoted lines
>> +-m::
>> +--merge::
>> +	If you have local modifications to one or more files that are
>> +	different between the current branch and the branch to which
>> +	you are switching, the command refuses to switch branches in
>> +	order to preserve your modifications in context.  However,
>> +	with this option, a three-way merge between the current
>> +	branch, your working tree contents, and the new branch is
>> +	done, and you will be on the new branch.
>
> I was wondering what people felt about making this the default for the
> new command.

Even its inventor (me) never runs "-m" without first seeing checkout without "-m" fail, and seeing how much can potentially be lost if the branch switch conflicts by asking "git diff" (no other options). It is very unadvisable to make it the default.

Duy Nguyen· Mar 14, 2019, 14:13 UTC · re: Phillip Wood · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Mon, Mar 11, 2019 at 6:16 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 13 quoted lines
> > +-f::
> > +--force::
> > +     Proceed even if the index or the working tree differs from
> > +     HEAD. Both the index and working tree are restored to match
> > +     the switching target. This is used to throw away local
> > +     changes.
>
> I'd always thought that --force meant "throw away my local changes if
> they conflict with the new branch" not "throw them away regardless"
> (which is better as it is deterministic). Maybe we can come up with a
> clearer name here --discard-changes? At the moment --force does not
> throw away conflicts properly (see the script below in my comments about
> --merge).

First victim of --discard-changes (or maybe I misread your comment), it's too much to type even with completion and I'm so used to the short and sweet "switch -[d]f".

Unless people object, I'm going to keep --force as an alias for --discard-changes. -f could be extended later to cover more --ignore-stuff when it makes sense.

-- 
Duy
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 06:00 UTC · re: Phillip Wood · lore

[PATCH] unpack-trees: fix oneway_merge accidentally carry over stage index

One-way merge is supposed to take stat info from the index and everything else from the given tree. This implies stage 0 because trees can't have non-zero stages. The add_entry(.., old, ...) call however will keep stage index from the index.

This is normally not a problem if the entry from the index is normal (stage #0). But if there is a conflict, we'll get stage #1 entry as "old" and it gets recorded in the final index. Fix it by clearing stage mask.

This bug probably comes from b5b425074e (git-read-tree: make one-way merge also honor the "update" flag, 2005-06-07). Before this commit, we may create the final ("dst") index entry from the one in index, but we do clear CE_STAGEMASK.

I briefly checked two- and three-way merge functions. I think we don't have the same problem in those.

Reported-by: Phillip Wood <phillip.wood123@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 This is one of the two bugs reported by Phillip. It's not tangled with
 nd/switch-and-restore code changes and I'm sending it separately.
 t/t2026-checkout-force.sh (new +x) | 26 ++++++++++++++++++++++++++
 unpack-trees.c                     |  2 +-
 2 files changed, 27 insertions(+), 1 deletion(-)
Show changes to 2 files +27 −1

t/t2026-checkout-force.sh, unpack-trees.c

diff --git a/t/t2026-checkout-force.sh b/t/t2026-checkout-force.sh
new file mode 100755
index 0000000000..272ccf533a
--- /dev/null
+++ b/t/t2026-checkout-force.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+test_description='checkout --force'
+. ./test-lib.sh
+
+test_expect_success 'force checking out a conflict' '
+	echo a >a &&
+	git add a &&
+	git commit -ama &&
+	A_OBJ=$(git rev-parse :a) &&
+	git branch topic &&
+	echo b >a &&
+	git commit -amb &&
+	B_OBJ=$(git rev-parse :a) &&
+	git checkout topic &&
+	echo c >a &&
+	C_OBJ=$(git hash-object a) &&
+	git checkout -m master &&
+	test_cmp_rev :1:a $A_OBJ &&
+	test_cmp_rev :2:a $B_OBJ &&
+	test_cmp_rev :3:a $C_OBJ &&
+	git checkout -f topic &&
+	test_cmp_rev :a $A_OBJ
+'
+
+test_done
diff --git a/unpack-trees.c b/unpack-trees.c
index 22c41a3ba8..1ccd343cad 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -2386,7 +2386,7 @@ int oneway_merge(const struct cache_entry * const *src,
 		if (o->update && S_ISGITLINK(old->ce_mode) &&
 		    should_update_submodules() && !verify_uptodate(old, o))
 			update |= CE_UPDATE;
-		add_entry(o, old, update, 0);
+		add_entry(o, old, update, CE_STAGEMASK);
 		return 0;
 	}
 	return merged_entry(a, old, o);
-- 
2.21.0.548.gd3c7d92dc2
Junio C Hamano· Mar 18, 2019, 03:58 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH] unpack-trees: fix oneway_merge accidentally carry over stage index

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
Show 14 quoted lines
> One-way merge is supposed to take stat info from the index and
> everything else from the given tree. This implies stage 0 because trees
> can't have non-zero stages. The add_entry(.., old, ...) call however
> will keep stage index from the index.
>
> This is normally not a problem if the entry from the index is
> normal (stage #0). But if there is a conflict, we'll get stage #1 entry
> as "old" and it gets recorded in the final index. Fix it by clearing
> stage mask.
>
> This bug probably comes from b5b425074e (git-read-tree: make one-way
> merge also honor the "update" flag, 2005-06-07). Before this commit, we
> may create the final ("dst") index entry from the one in index, but we
> do clear CE_STAGEMASK.
Wow, good find.  That's an old one.
Show 8 quoted lines
> I briefly checked two- and three-way merge functions. I think we don't
> have the same problem in those.
>
> Reported-by: Phillip Wood <phillip.wood123@gmail.com>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  This is one of the two bugs reported by Phillip. It's not tangled with
>  nd/switch-and-restore code changes and I'm sending it separately.
Thanks.
>
>  t/t2026-checkout-force.sh (new +x) | 26 ++++++++++++++++++++++++++

This makes it cumbersome to have the same fix in the maintenance track as t2026 is already in use over there. Do we need an entirely new test just to house this new single test?

By the way, I am beginning to like these "in-line" summaries (as opposed to the --summary at the end), although I admit that it has been quite a while since its introduction. Good job, again.

Show 32 quoted lines
>  unpack-trees.c                     |  2 +-
>  2 files changed, 27 insertions(+), 1 deletion(-)
>
> diff --git a/t/t2026-checkout-force.sh b/t/t2026-checkout-force.sh
> new file mode 100755
> index 0000000000..272ccf533a
> --- /dev/null
> +++ b/t/t2026-checkout-force.sh
> @@ -0,0 +1,26 @@
> +#!/bin/sh
> +
> +test_description='checkout --force'
> +. ./test-lib.sh
> +
> +test_expect_success 'force checking out a conflict' '
> +	echo a >a &&
> +	git add a &&
> +	git commit -ama &&
> +	A_OBJ=$(git rev-parse :a) &&
> +	git branch topic &&
> +	echo b >a &&
> +	git commit -amb &&
> +	B_OBJ=$(git rev-parse :a) &&
> +	git checkout topic &&
> +	echo c >a &&
> +	C_OBJ=$(git hash-object a) &&
> +	git checkout -m master &&
> +	test_cmp_rev :1:a $A_OBJ &&
> +	test_cmp_rev :2:a $B_OBJ &&
> +	test_cmp_rev :3:a $C_OBJ &&
> +	git checkout -f topic &&
> +	test_cmp_rev :a $A_OBJ

So in short, "checkout -f" should have given us an entry for path "a", taken from the tip of the 'topic' branch, at stage #0 while switching to that branch, but it didn't? That would be a nice summary to have at the beginning of the log message before going into the implementation detail of how that happens.

Show 13 quoted lines
> +'
> +
> +test_done
> diff --git a/unpack-trees.c b/unpack-trees.c
> index 22c41a3ba8..1ccd343cad 100644
> --- a/unpack-trees.c
> +++ b/unpack-trees.c
> @@ -2386,7 +2386,7 @@ int oneway_merge(const struct cache_entry * const *src,
>  		if (o->update && S_ISGITLINK(old->ce_mode) &&
>  		    should_update_submodules() && !verify_uptodate(old, o))
>  			update |= CE_UPDATE;
> -		add_entry(o, old, update, 0);
> +		add_entry(o, old, update, CE_STAGEMASK);

And the fix is obvious, makes sense and is in line with the observation you made in the proposed log message.

Nicely done.
Thanks.
>  		return 0;
>  	}
>  	return merged_entry(a, old, o);
Duy Nguyen· Mar 18, 2019, 09:24 UTC · re: Junio C Hamano · lore

Re: [PATCH] unpack-trees: fix oneway_merge accidentally carry over stage index

On Mon, Mar 18, 2019 at 10:58 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 19 quoted lines
>
> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>
> > One-way merge is supposed to take stat info from the index and
> > everything else from the given tree. This implies stage 0 because trees
> > can't have non-zero stages. The add_entry(.., old, ...) call however
> > will keep stage index from the index.
> >
> > This is normally not a problem if the entry from the index is
> > normal (stage #0). But if there is a conflict, we'll get stage #1 entry
> > as "old" and it gets recorded in the final index. Fix it by clearing
> > stage mask.
> >
> > This bug probably comes from b5b425074e (git-read-tree: make one-way
> > merge also honor the "update" flag, 2005-06-07). Before this commit, we
> > may create the final ("dst") index entry from the one in index, but we
> > do clear CE_STAGEMASK.
>
> Wow, good find.  That's an old one.

Credit goes to Phillip for such attention to detail. If I tested this, I would have stopped after seeing conflict stages collapsed into one and missed the stage index. In fact I was swearing "what the hell did he complain about" when looking at his test script's result, until I realized stage #1 was indeed wrong.

Show 6 quoted lines
> >
> >  t/t2026-checkout-force.sh (new +x) | 26 ++++++++++++++++++++++++++
>
> This makes it cumbersome to have the same fix in the maintenance track
> as t2026 is already in use over there.  Do we need an entirely new test
> just to house this new single test?

I could not find any right file to put it in. I guess I could stick it in t2023-checkout-m.sh

Show 24 quoted lines
> > +test_expect_success 'force checking out a conflict' '
> > +     echo a >a &&
> > +     git add a &&
> > +     git commit -ama &&
> > +     A_OBJ=$(git rev-parse :a) &&
> > +     git branch topic &&
> > +     echo b >a &&
> > +     git commit -amb &&
> > +     B_OBJ=$(git rev-parse :a) &&
> > +     git checkout topic &&
> > +     echo c >a &&
> > +     C_OBJ=$(git hash-object a) &&
> > +     git checkout -m master &&
> > +     test_cmp_rev :1:a $A_OBJ &&
> > +     test_cmp_rev :2:a $B_OBJ &&
> > +     test_cmp_rev :3:a $C_OBJ &&
> > +     git checkout -f topic &&
> > +     test_cmp_rev :a $A_OBJ
>
> So in short, "checkout -f" should have given us an entry for path
> "a", taken from the tip of the 'topic' branch, at stage #0 while
> switching to that branch, but it didn't?  That would be a nice
> summary to have at the beginning of the log message before going
> into the implementation detail of how that happens.

OK. And the last line probably should be :0:a to make it clear we're looking for stage #0.

-- 
Duy
Junio C Hamano· Mar 18, 2019, 09:40 UTC · re: Duy Nguyen · lore

Re: [PATCH] unpack-trees: fix oneway_merge accidentally carry over stage index

Duy Nguyen <pclouds@gmail.com> writes:
Show 14 quoted lines
>> > +     test_cmp_rev :1:a $A_OBJ &&
>> > +     test_cmp_rev :2:a $B_OBJ &&
>> > +     test_cmp_rev :3:a $C_OBJ &&
>> > +     git checkout -f topic &&
>> > +     test_cmp_rev :a $A_OBJ
>>
>> So in short, "checkout -f" should have given us an entry for path
>> "a", taken from the tip of the 'topic' branch, at stage #0 while
>> switching to that branch, but it didn't?  That would be a nice
>> summary to have at the beginning of the log message before going
>> into the implementation detail of how that happens.
>
> OK. And the last line probably should be :0:a to make it clear we're
> looking for stage #0.

I would say ":a" is plenty clear that it is looking for a merged entry without getting replaced with an overly explicit ":0:a", but ":0:a" would not hurt, either.

Nguyễn Thái Ngọc Duy· Mar 18, 2019, 11:38 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v2] unpack-trees: fix oneway_merge accidentally carry over stage index

Phillip found out that 'git checkout -f <branch>' does not restore conflict/unmerged files correctly. All tracked files should be taken from <branch> and all non-zero stages removed. Most of this is true, except that the final file could be in stage one instead of zero.

"checkout -f" (among other commands) does this with one-way merge, which is supposed to take stat info from the index and everything else from the given tree. The add_entry(.., old, ...) call in oneway_merge() though will keep stage index from the index.

This is normally not a problem if the entry from the index is normal (stage #0). But if there is a conflict, stage #0 does not exist and we'll get stage #1 entry as "old" variable, which gets recorded in the final index. Fix it by clearing stage mask.

This bug probably comes from b5b425074e (git-read-tree: make one-way merge also honor the "update" flag, 2005-06-07). Before this commit, we may create the final ("dst") index entry from the one in index, but we do clear CE_STAGEMASK.

I briefly checked two- and three-way merge functions. I think we don't have the same problem in those.

Reported-by: Phillip Wood <phillip.wood123@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 v2 updates log message to describe the problem and moves the test to
 t2023-checkout.m.sh
 t/t2023-checkout-m.sh | 24 ++++++++++++++++++++++++
 unpack-trees.c        |  2 +-
 2 files changed, 25 insertions(+), 1 deletion(-)
Show changes to 2 files +25 −1

t/t2023-checkout-m.sh, unpack-trees.c

diff --git a/t/t2023-checkout-m.sh b/t/t2023-checkout-m.sh
index 7e18985134..fca3f85824 100755
--- a/t/t2023-checkout-m.sh
+++ b/t/t2023-checkout-m.sh
@@ -46,4 +46,28 @@ test_expect_success '-m restores 3-way conflicted+resolved file' '
 	test_cmp both.txt.conflicted.cleaned both.txt.cleaned
 '
 
+test_expect_success 'force checkout a conflict file creates stage zero entry' '
+	git init co-force &&
+	(
+		cd co-force &&
+		echo a >a &&
+		git add a &&
+		git commit -ama &&
+		A_OBJ=$(git rev-parse :a) &&
+		git branch topic &&
+		echo b >a &&
+		git commit -amb &&
+		B_OBJ=$(git rev-parse :a) &&
+		git checkout topic &&
+		echo c >a &&
+		C_OBJ=$(git hash-object a) &&
+		git checkout -m master &&
+		test_cmp_rev :1:a $A_OBJ &&
+		test_cmp_rev :2:a $B_OBJ &&
+		test_cmp_rev :3:a $C_OBJ &&
+		git checkout -f topic &&
+		test_cmp_rev :0:a $A_OBJ
+	)
+'
+
 test_done
diff --git a/unpack-trees.c b/unpack-trees.c
index 22c41a3ba8..1ccd343cad 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -2386,7 +2386,7 @@ int oneway_merge(const struct cache_entry * const *src,
 		if (o->update && S_ISGITLINK(old->ce_mode) &&
 		    should_update_submodules() && !verify_uptodate(old, o))
 			update |= CE_UPDATE;
-		add_entry(o, old, update, 0);
+		add_entry(o, old, update, CE_STAGEMASK);
 		return 0;
 	}
 	return merged_entry(a, old, o);
-- 
2.21.0.548.gd3c7d92dc2
Elijah Newren· Mar 18, 2019, 15:40 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v2] unpack-trees: fix oneway_merge accidentally carry over stage index

On Mon, Mar 18, 2019 at 4:38 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 26 quoted lines
>
> Phillip found out that 'git checkout -f <branch>' does not restore
> conflict/unmerged files correctly. All tracked files should be taken
> from <branch> and all non-zero stages removed. Most of this is true,
> except that the final file could be in stage one instead of zero.
>
> "checkout -f" (among other commands) does this with one-way merge, which
> is supposed to take stat info from the index and everything else from
> the given tree. The add_entry(.., old, ...) call in oneway_merge()
> though will keep stage index from the index.
>
> This is normally not a problem if the entry from the index is
> normal (stage #0). But if there is a conflict, stage #0 does not exist
> and we'll get stage #1 entry as "old" variable, which gets recorded in
> the final index. Fix it by clearing stage mask.
>
> This bug probably comes from b5b425074e (git-read-tree: make one-way
> merge also honor the "update" flag, 2005-06-07). Before this commit, we
> may create the final ("dst") index entry from the one in index, but we
> do clear CE_STAGEMASK.
>
> I briefly checked two- and three-way merge functions. I think we don't
> have the same problem in those.
>
> Reported-by: Phillip Wood <phillip.wood123@gmail.com>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
This is awesome; thanks for tracking it down and fixing.
Phillip Wood· Mar 19, 2019, 14:06 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v2] unpack-trees: fix oneway_merge accidentally carry over stage index

Hi Duy

Thanks for doing this, one minor comment - I try to use phillip.wood@dunelm.org.uk for git as it wont change if I change my email provider.

Best Wishes
Phillip
On 18/03/2019 11:38, Nguyễn Thái Ngọc Duy wrote:
Show 80 quoted lines
> Phillip found out that 'git checkout -f <branch>' does not restore
> conflict/unmerged files correctly. All tracked files should be taken
> from <branch> and all non-zero stages removed. Most of this is true,
> except that the final file could be in stage one instead of zero.
> 
> "checkout -f" (among other commands) does this with one-way merge, which
> is supposed to take stat info from the index and everything else from
> the given tree. The add_entry(.., old, ...) call in oneway_merge()
> though will keep stage index from the index.
> 
> This is normally not a problem if the entry from the index is
> normal (stage #0). But if there is a conflict, stage #0 does not exist
> and we'll get stage #1 entry as "old" variable, which gets recorded in
> the final index. Fix it by clearing stage mask.
> 
> This bug probably comes from b5b425074e (git-read-tree: make one-way
> merge also honor the "update" flag, 2005-06-07). Before this commit, we
> may create the final ("dst") index entry from the one in index, but we
> do clear CE_STAGEMASK.
> 
> I briefly checked two- and three-way merge functions. I think we don't
> have the same problem in those.
> 
> Reported-by: Phillip Wood <phillip.wood123@gmail.com>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>   v2 updates log message to describe the problem and moves the test to
>   t2023-checkout.m.sh
> 
>   t/t2023-checkout-m.sh | 24 ++++++++++++++++++++++++
>   unpack-trees.c        |  2 +-
>   2 files changed, 25 insertions(+), 1 deletion(-)
> 
> diff --git a/t/t2023-checkout-m.sh b/t/t2023-checkout-m.sh
> index 7e18985134..fca3f85824 100755
> --- a/t/t2023-checkout-m.sh
> +++ b/t/t2023-checkout-m.sh
> @@ -46,4 +46,28 @@ test_expect_success '-m restores 3-way conflicted+resolved file' '
>   	test_cmp both.txt.conflicted.cleaned both.txt.cleaned
>   '
>   
> +test_expect_success 'force checkout a conflict file creates stage zero entry' '
> +	git init co-force &&
> +	(
> +		cd co-force &&
> +		echo a >a &&
> +		git add a &&
> +		git commit -ama &&
> +		A_OBJ=$(git rev-parse :a) &&
> +		git branch topic &&
> +		echo b >a &&
> +		git commit -amb &&
> +		B_OBJ=$(git rev-parse :a) &&
> +		git checkout topic &&
> +		echo c >a &&
> +		C_OBJ=$(git hash-object a) &&
> +		git checkout -m master &&
> +		test_cmp_rev :1:a $A_OBJ &&
> +		test_cmp_rev :2:a $B_OBJ &&
> +		test_cmp_rev :3:a $C_OBJ &&
> +		git checkout -f topic &&
> +		test_cmp_rev :0:a $A_OBJ
> +	)
> +'
> +
>   test_done
> diff --git a/unpack-trees.c b/unpack-trees.c
> index 22c41a3ba8..1ccd343cad 100644
> --- a/unpack-trees.c
> +++ b/unpack-trees.c
> @@ -2386,7 +2386,7 @@ int oneway_merge(const struct cache_entry * const *src,
>   		if (o->update && S_ISGITLINK(old->ce_mode) &&
>   		    should_update_submodules() && !verify_uptodate(old, o))
>   			update |= CE_UPDATE;
> -		add_entry(o, old, update, 0);
> +		add_entry(o, old, update, CE_STAGEMASK);
>   		return 0;
>   	}
>   	return merged_entry(a, old, o);
> 
Junio C Hamano· Mar 20, 2019, 00:41 UTC · re: Phillip Wood · lore

Re: [PATCH v2] unpack-trees: fix oneway_merge accidentally carry over stage index

Phillip Wood <phillip.wood123@gmail.com> writes:
> Thanks for doing this, one minor comment - I try to use
> phillip.wood@dunelm.org.uk for git as it wont change if I change my
> email provider.
You mean something like this?
 .mailmap | 1 +
 1 file changed, 1 insertion(+)
Show changes to .mailmap +1 −0
diff --git a/.mailmap b/.mailmap
index 82cd0569d5..6e137576ec 100644
--- a/.mailmap
+++ b/.mailmap
@@ -212,6 +212,7 @@ Phil Hord <hordp@cisco.com> <phil.hord@gmail.com>
 Philip Jägenstedt <philip@foolip.org> <philip.jagenstedt@gmail.com>
 Philipp A. Hartmann <pah@qo.cx> <ph@sorgh.de>
 Philippe Bruhat <book@cpan.org>
+Phillip Wood <phillip.wood@dunelm.org.uk> <phillip.wood123@gmail.com>
 Ralf Thielow <ralf.thielow@gmail.com> <ralf.thielow@googlemail.com>
 Ramsay Jones <ramsay@ramsayjones.plus.com> <ramsay@ramsay1.demon.co.uk>
 Randall S. Becker <randall.becker@nexbridge.ca> <rsbecker@nexbridge.com>
Duy Nguyen· Mar 20, 2019, 09:50 UTC · re: Junio C Hamano · lore

Re: [PATCH v2] unpack-trees: fix oneway_merge accidentally carry over stage index

On Wed, Mar 20, 2019 at 7:41 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 8 quoted lines
>
> Phillip Wood <phillip.wood123@gmail.com> writes:
>
> > Thanks for doing this, one minor comment - I try to use
> > phillip.wood@dunelm.org.uk for git as it wont change if I change my
> > email provider.
>
> You mean something like this?
I think he meant fixing the Reported-by: line. But since there's at
least another commit in 'next' with the 123@gmail address, it's
probably best to update .mailmap too (and maybe still fix the
Reported-by: in this patch).
Show 15 quoted lines
>  .mailmap | 1 +
>  1 file changed, 1 insertion(+)
>
> diff --git a/.mailmap b/.mailmap
> index 82cd0569d5..6e137576ec 100644
> --- a/.mailmap
> +++ b/.mailmap
> @@ -212,6 +212,7 @@ Phil Hord <hordp@cisco.com> <phil.hord@gmail.com>
>  Philip Jägenstedt <philip@foolip.org> <philip.jagenstedt@gmail.com>
>  Philipp A. Hartmann <pah@qo.cx> <ph@sorgh.de>
>  Philippe Bruhat <book@cpan.org>
> +Phillip Wood <phillip.wood@dunelm.org.uk> <phillip.wood123@gmail.com>
>  Ralf Thielow <ralf.thielow@gmail.com> <ralf.thielow@googlemail.com>
>  Ramsay Jones <ramsay@ramsayjones.plus.com> <ramsay@ramsay1.demon.co.uk>
>  Randall S. Becker <randall.becker@nexbridge.ca> <rsbecker@nexbridge.com>
-- 
Duy
Duy Nguyen· Mar 26, 2019, 12:50 UTC · re: Phillip Wood · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Mon, Mar 11, 2019 at 6:16 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 13 quoted lines
> On 08/03/2019 09:57, Nguyễn Thái Ngọc Duy wrote:
> > "git checkout" doing too many things is a source of confusion for many
> > users (and it even bites old timers sometimes). To remedy that, the
> > command will be split into two new ones: switch and
> > something-to-checkout-paths.
>
> I think this is a good idea, thanks for working on it. I wonder if it
> would be a good idea to have the new command refuse to checkout a new
> branch if there is a cherry-pick/revert/merge/rebase in progress (with
> an option to override the check) as switching branches in the middle of
> one of those is likely to be confusing to users (if I do it it is
> normally because I've forgotten that I've not run 'git whatever
> --continue').

Guys, I'm sorry for bringing this up again. Apparently I'm not quite done with 'git switch' yet (not sure if I will ever be).

There is an interesting behavior in git-checkout (and of couse git-switch). When you do a successful switch, CHERRY_PICK_HEAD, REVERT_HEAD, MERGE_HEAD, MERGE_RR, MERGE_MSG, MERGE_MODE and SQUAHS_MSG, if exists, will be removed.

This basically means that if you switch away, any cherry-pick, merge or revert in progress is destroyed (in the sense of "--quit" not "--abort" of course). All of this, I believe, involves merge conflicts so you can't easily switch away unless you allow to destroy unmerged entries. So it's still quite safe.

However, it leaves me a funny feeling because some "work-in-progress" operations are destroyed, but some others (bisect, rebase) are not. This is git-checkout behavior and I will not change that. But do we want the same behavior in git-switch? Or do we want no-destroy-in-progress-whatsoever? Or destroy-all-commands-in-progress?

It may also be a good idea to attempt to describe the behavior we want in git-switch.txt. I think if the description gets too complicated, we're heading a wrong way. The current behavior so far could still be described as "work-in-progress operations related to merge conflicts are destroyed", or something along that line. But I'm not quite convinced it's easily understood.

PS. git-reset shares the same behavior, but it's in a different boat, I think. Or maybe I should scrap/replace that one as well.

PPS. It may get trickier with cherry-pick which can pick a range of commits now, not just one. But I never used that feature much to know what I'm talking about.

-- 
Duy
Elijah Newren· Mar 26, 2019, 15:01 UTC · re: Duy Nguyen · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Tue, Mar 26, 2019 at 5:50 AM Duy Nguyen <pclouds@gmail.com> wrote:
Show 35 quoted lines
> On Mon, Mar 11, 2019 at 6:16 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
> > On 08/03/2019 09:57, Nguyễn Thái Ngọc Duy wrote:
> > > "git checkout" doing too many things is a source of confusion for many
> > > users (and it even bites old timers sometimes). To remedy that, the
> > > command will be split into two new ones: switch and
> > > something-to-checkout-paths.
> >
> > I think this is a good idea, thanks for working on it. I wonder if it
> > would be a good idea to have the new command refuse to checkout a new
> > branch if there is a cherry-pick/revert/merge/rebase in progress (with
> > an option to override the check) as switching branches in the middle of
> > one of those is likely to be confusing to users (if I do it it is
> > normally because I've forgotten that I've not run 'git whatever
> > --continue').
>
> Guys, I'm sorry for bringing this up again. Apparently I'm not quite
> done with 'git switch' yet (not sure if I will ever be).
>
> There is an interesting behavior in git-checkout (and of couse
> git-switch). When you do a successful switch, CHERRY_PICK_HEAD,
> REVERT_HEAD, MERGE_HEAD, MERGE_RR, MERGE_MSG, MERGE_MODE and
> SQUAHS_MSG, if exists, will be removed.
>
> This basically means that if you switch away, any cherry-pick, merge
> or revert in progress is destroyed (in the sense of "--quit" not
> "--abort" of course). All of this, I believe, involves merge conflicts
> so you can't easily switch away unless you allow to destroy unmerged
> entries. So it's still quite safe.
>
> However, it leaves me a funny feeling because some "work-in-progress"
> operations are destroyed, but some others (bisect, rebase) are not.
> This is git-checkout behavior and I will not change that. But do we
> want the same behavior in git-switch? Or do we want
> no-destroy-in-progress-whatsoever? Or
> destroy-all-commands-in-progress?

I thought we already decided that we'd abort the switch if there was any operation-in-progress state? Or are you asking what should we do if the user explicitly overrides this error with e.g. --ignore-in-progress? In that case, I'd say that the reasonable thing to do would be to leave all the state files alone. If we make it clear out the state, we're simply combining uncommon commands for the user (<operation> --quit + git switch), which seems like a bad UI path to go down. Allowing them to switch to some other branch while keeping all state files is something they can't do with any other command, and while I hope people wouldn't want to do that much, switching while keeping state files is something that can't be done with combining other commands and thus at least makes sense as something to consider providing.

Show 9 quoted lines
> It may also be a good idea to attempt to describe the behavior we want
> in git-switch.txt. I think if the description gets too complicated,
> we're heading a wrong way. The current behavior so far could still be
> described as "work-in-progress operations related to merge conflicts
> are destroyed", or something along that line. But I'm not quite
> convinced it's easily understood.
>
> PS. git-reset shares the same behavior, but it's in a different boat,
> I think. Or maybe I should scrap/replace that one as well.

reset has traditionally been the home of how-to-clear-in-progress-state. e.g. aborting a merge or cherry-pick or revert was 'reset --hard' (or later 'reset --merge'), skipping a become-empty cherry-pick or rebase is still 'reset', etc. So it's not that surprising to me that it clears out state. As to whether it still should, we can address that along with...

> PPS. It may get trickier with cherry-pick which can pick a range of
> commits now, not just one. But I never used that feature much to know
> what I'm talking about.

Yeah, cherry-pick and revert and rebase all allow working on several items. It seems bad to me to abort/quit such operations without an explicit '(cherry-pick|rebase|revert) --(abort|quit)'. Back when cherry-pick and revert only worked on a single commit, allowing 'reset --hard' to clear state didn't seem that unreasonable even if it wasn't the best UI. So it all went there until people got used to 'rebase --abort' and realized it was a better way and started spreading --abort to other commands. And you could kind of see us overlooking that someone could cherry-pick --no-commit and then afterwards checkout --force to put those changes somewhere else but I think it was even dangerous/weird back then. Once cherry-pick and revert commands were extended to work with multiple commits like rebase does, I just don't think it makes sense to allow reset to completely clear out state files anymore. Also, I don't want to check for one or multiple commits and make them behave differently; I want git to consistently require an explicit command-specific abort or quit (or continue) to clear out the state associated with those possibly-multi-step operations. Maybe 'reset --hard' can continue clearing out merge state just by force of habit, though I'd still rather encourage 'merge --abort'. But I don't think it makes sense to let switch, reset, etc. remove multi-step progress state.

Duy Nguyen· Mar 26, 2019, 15:24 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Tue, Mar 26, 2019 at 10:01 PM Elijah Newren <newren@gmail.com> wrote:
Show 51 quoted lines
>
> On Tue, Mar 26, 2019 at 5:50 AM Duy Nguyen <pclouds@gmail.com> wrote:
> > On Mon, Mar 11, 2019 at 6:16 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
> > > On 08/03/2019 09:57, Nguyễn Thái Ngọc Duy wrote:
> > > > "git checkout" doing too many things is a source of confusion for many
> > > > users (and it even bites old timers sometimes). To remedy that, the
> > > > command will be split into two new ones: switch and
> > > > something-to-checkout-paths.
> > >
> > > I think this is a good idea, thanks for working on it. I wonder if it
> > > would be a good idea to have the new command refuse to checkout a new
> > > branch if there is a cherry-pick/revert/merge/rebase in progress (with
> > > an option to override the check) as switching branches in the middle of
> > > one of those is likely to be confusing to users (if I do it it is
> > > normally because I've forgotten that I've not run 'git whatever
> > > --continue').
> >
> > Guys, I'm sorry for bringing this up again. Apparently I'm not quite
> > done with 'git switch' yet (not sure if I will ever be).
> >
> > There is an interesting behavior in git-checkout (and of couse
> > git-switch). When you do a successful switch, CHERRY_PICK_HEAD,
> > REVERT_HEAD, MERGE_HEAD, MERGE_RR, MERGE_MSG, MERGE_MODE and
> > SQUAHS_MSG, if exists, will be removed.
> >
> > This basically means that if you switch away, any cherry-pick, merge
> > or revert in progress is destroyed (in the sense of "--quit" not
> > "--abort" of course). All of this, I believe, involves merge conflicts
> > so you can't easily switch away unless you allow to destroy unmerged
> > entries. So it's still quite safe.
> >
> > However, it leaves me a funny feeling because some "work-in-progress"
> > operations are destroyed, but some others (bisect, rebase) are not.
> > This is git-checkout behavior and I will not change that. But do we
> > want the same behavior in git-switch? Or do we want
> > no-destroy-in-progress-whatsoever? Or
> > destroy-all-commands-in-progress?
>
> I thought we already decided that we'd abort the switch if there was
> any operation-in-progress state?  Or are you asking what should we do
> if the user explicitly overrides this error with e.g.
> --ignore-in-progress?  In that case, I'd say that the reasonable thing
> to do would be to leave all the state files alone.  If we make it
> clear out the state, we're simply combining uncommon commands for the
> user (<operation> --quit + git switch), which seems like a bad UI path
> to go down.  Allowing them to switch to some other branch while
> keeping all state files is something they can't do with any other
> command, and while I hope people wouldn't want to do that much,
> switching while keeping state files is something that can't be done
> with combining other commands and thus at least makes sense as
> something to consider providing.

Yeah.. --ignore-in-process does not necessarily mean aborting something when you just want to get out to examine some other commit. And I agree doing nothing seems like the best (or least confusing/surprising) option.

There will be some funny thing. Like if you commit after switching away and MERGE_HEAD is there, I think you will be creating a merge commit.

But I guess if you choose --ignore-in-progress, you're pretty much on your own. We could improve "git commit" and friends to realize that the current state is unlikely safe to resume whatever in-progress operations. But that's separate and I don't want to dig another hole to bury myself in while there's still 'git restore' to take care of.

Or maybe I'll just suggest "git <verb> --quit" when the switch is successful with --ignore-in-progress.

Will wait a while for people to comment (and work on that 'git switch --recurse-submodules', which seems also needs some work) before resubmitting.

Show 16 quoted lines
> > It may also be a good idea to attempt to describe the behavior we want
> > in git-switch.txt. I think if the description gets too complicated,
> > we're heading a wrong way. The current behavior so far could still be
> > described as "work-in-progress operations related to merge conflicts
> > are destroyed", or something along that line. But I'm not quite
> > convinced it's easily understood.
> >
> > PS. git-reset shares the same behavior, but it's in a different boat,
> > I think. Or maybe I should scrap/replace that one as well.
>
> reset has traditionally been the home of
> how-to-clear-in-progress-state.  e.g. aborting a merge or cherry-pick
> or revert was 'reset --hard' (or later 'reset --merge'), skipping a
> become-empty cherry-pick or rebase is still 'reset', etc.  So it's not
> that surprising to me that it clears out state.
> ...

Yeah but it was surprising to me that this is not even mentioned anywhere in git-reset.txt. You learn by examples basically, or by experience. But I digress.

-- 
Duy
Elijah Newren· Mar 26, 2019, 15:48 UTC · re: Duy Nguyen · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Tue, Mar 26, 2019 at 8:24 AM Duy Nguyen <pclouds@gmail.com> wrote:
> On Tue, Mar 26, 2019 at 10:01 PM Elijah Newren <newren@gmail.com> wrote:
Show 8 quoted lines
> Yeah.. --ignore-in-process does not necessarily mean aborting
> something when you just want to get out to examine some other commit.
> And I agree doing nothing seems like the best (or least
> confusing/surprising) option.
>
> There will be some funny thing. Like if you commit after switching
> away and MERGE_HEAD is there, I think you will be creating a merge
> commit.

Yes, and in the middle of a cherry-pick with a range you've added some commits to one branch and some to another. In the middle of a revert you're doing similar. It sounds like crazytown to me (and maybe we shouldn't provide the --ignore-in-process flag unless users clamor for it -- or maybe we should print a big warning whenever people use it much like we've traditionally done when people checkout a commit rather than a branch), but it at least makes sense what is happening and users had to explicitly state they wanted the in-progress state to just be ignored and switch anyway.

Show 8 quoted lines
> But I guess if you choose --ignore-in-progress, you're pretty much on
> your own. We could improve "git commit" and friends to realize that
> the current state is unlikely safe to resume whatever in-progress
> operations. But that's separate and I don't want to dig another hole
> to bury myself in while there's still 'git restore' to take care of.
>
> Or maybe I'll just suggest "git <verb> --quit" when the switch is
> successful with --ignore-in-progress.

Combining that with some kind of warning about weird effects of applying a mid-operation process across branches seems like a good idea to me if we're going to have that flag.

Show 13 quoted lines
> > > PS. git-reset shares the same behavior, but it's in a different boat,
> > > I think. Or maybe I should scrap/replace that one as well.
> >
> > reset has traditionally been the home of
> > how-to-clear-in-progress-state.  e.g. aborting a merge or cherry-pick
> > or revert was 'reset --hard' (or later 'reset --merge'), skipping a
> > become-empty cherry-pick or rebase is still 'reset', etc.  So it's not
> > that surprising to me that it clears out state.
> > ...
>
> Yeah but it was surprising to me that this is not even mentioned
> anywhere in git-reset.txt. You learn by examples basically, or by
> experience. But I digress.

Yeah that is slightly odd -- but that at least provides a small silver lining: it makes it easier to decide to change it and move all the mid-operation-state-clearing to other commands. :-)

Duy Nguyen· Mar 26, 2019, 15:57 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Tue, Mar 26, 2019 at 10:48 PM Elijah Newren <newren@gmail.com> wrote:
Show 17 quoted lines
> > > > PS. git-reset shares the same behavior, but it's in a different boat,
> > > > I think. Or maybe I should scrap/replace that one as well.
> > >
> > > reset has traditionally been the home of
> > > how-to-clear-in-progress-state.  e.g. aborting a merge or cherry-pick
> > > or revert was 'reset --hard' (or later 'reset --merge'), skipping a
> > > become-empty cherry-pick or rebase is still 'reset', etc.  So it's not
> > > that surprising to me that it clears out state.
> > > ...
> >
> > Yeah but it was surprising to me that this is not even mentioned
> > anywhere in git-reset.txt. You learn by examples basically, or by
> > experience. But I digress.
>
> Yeah that is slightly odd -- but that at least provides a small silver
> lining: it makes it easier to decide to change it and move all the
> mid-operation-state-clearing to other commands.  :-)

Don't tempt me. Elsewhere in some discussion with Ævar I wrote it's better to add "git-ng" instead of going through the deprecation process to change behavior of current commands, which also means that you better design git-ng well because you can't just go "oops, i did it again" and add "git-nng". And I'm slowly realizing that 'switch' and 'restore' are just "git-ng checkout" in disguise. That already increases my stress level a bit.

-- 
Duy
Phillip Wood· Mar 27, 2019, 10:24 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On 26/03/2019 15:48, Elijah Newren wrote:
Show 17 quoted lines
> On Tue, Mar 26, 2019 at 8:24 AM Duy Nguyen <pclouds@gmail.com> wrote:
>> On Tue, Mar 26, 2019 at 10:01 PM Elijah Newren <newren@gmail.com> wrote:
> 
>> Yeah.. --ignore-in-process does not necessarily mean aborting
>> something when you just want to get out to examine some other commit.
>> And I agree doing nothing seems like the best (or least
>> confusing/surprising) option.
>>
>> There will be some funny thing. Like if you commit after switching
>> away and MERGE_HEAD is there, I think you will be creating a merge
>> commit.
> 
> Yes, and in the middle of a cherry-pick with a range you've added some
> commits to one branch and some to another.  In the middle of a revert
> you're doing similar.  It sounds like crazytown to me (and maybe we
> shouldn't provide the --ignore-in-process flag unless users clamor for
> it 

I think that could be the way to go for merges and cherry-picks, or possibly require --discard-changes as well. The only time I use checkout like this is during a rebase if I want to rewind it - I edit the todo list with the output of 'git log --pretty="pick %h %s" --reverse' and do 'git checkout' followed by 'git rebase --continue' Though these days I could add a 'reset' line to the todo list and skip the checkout.

Show 36 quoted lines
> -- or maybe we should print a big warning whenever people use it
> much like we've traditionally done when people checkout a commit
> rather than a branch), but it at least makes sense what is happening
> and users had to explicitly state they wanted the in-progress state to
> just be ignored and switch anyway.
> 
>> But I guess if you choose --ignore-in-progress, you're pretty much on
>> your own. We could improve "git commit" and friends to realize that
>> the current state is unlikely safe to resume whatever in-progress
>> operations. But that's separate and I don't want to dig another hole
>> to bury myself in while there's still 'git restore' to take care of.
>>
>> Or maybe I'll just suggest "git <verb> --quit" when the switch is
>> successful with --ignore-in-progress.
> 
> Combining that with some kind of warning about weird effects of
> applying a mid-operation process across branches seems like a good
> idea to me if we're going to have that flag.
> 
>>>> PS. git-reset shares the same behavior, but it's in a different boat,
>>>> I think. Or maybe I should scrap/replace that one as well.
>>>
>>> reset has traditionally been the home of
>>> how-to-clear-in-progress-state.  e.g. aborting a merge or cherry-pick
>>> or revert was 'reset --hard' (or later 'reset --merge'), skipping a
>>> become-empty cherry-pick or rebase is still 'reset', etc.  So it's not
>>> that surprising to me that it clears out state.
>>> ...
>>
>> Yeah but it was surprising to me that this is not even mentioned
>> anywhere in git-reset.txt. You learn by examples basically, or by
>> experience. But I digress.
> 
> Yeah that is slightly odd -- but that at least provides a small silver
> lining: it makes it easier to decide to change it and move all the
> mid-operation-state-clearing to other commands.  :-)

The missing documentation is odd but I think it makes sense. 'reset --hard' throws away all the changes in the index and worktree so it would be confusing if MERGE_HEAD was left behind. 'reset --mixed' throws away the changes in the index and you cannot do a partial commit if MERGE_HEAD or CHERRY_PICK_HEAD exist so it probably makes sense there as well (although if you do 'git reset && git commit -a' it wont remember the merge/cherry-pick). Some of the other reset modes (--soft and --keep I think) detect on in progress merge and refuse to run, I've got a patch somewhere that extends that to cherry-picks. I should probably extend it to handle --merge as well (are there any other modes, I only ever use --hard/--mixed/--soft as I'm never sure what the others will do)

Best Wishes
Phillip
Duy Nguyen· Mar 28, 2019, 11:04 UTC · re: Phillip Wood · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Wed, Mar 27, 2019 at 5:24 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 19 quoted lines
>
> On 26/03/2019 15:48, Elijah Newren wrote:
> > On Tue, Mar 26, 2019 at 8:24 AM Duy Nguyen <pclouds@gmail.com> wrote:
> >> On Tue, Mar 26, 2019 at 10:01 PM Elijah Newren <newren@gmail.com> wrote:
> >
> >> Yeah.. --ignore-in-process does not necessarily mean aborting
> >> something when you just want to get out to examine some other commit.
> >> And I agree doing nothing seems like the best (or least
> >> confusing/surprising) option.
> >>
> >> There will be some funny thing. Like if you commit after switching
> >> away and MERGE_HEAD is there, I think you will be creating a merge
> >> commit.
> >
> > Yes, and in the middle of a cherry-pick with a range you've added some
> > commits to one branch and some to another.  In the middle of a revert
> > you're doing similar.  It sounds like crazytown to me (and maybe we
> > shouldn't provide the --ignore-in-process flag unless users clamor for
> > it

I missed this part in my last reading. I think if we could safely switch away and get back to resume, then --ignore-in-process could still be useful. I sometimes switch to another commit to check out stuff then back. For interactive rebase with "edit" command for example, it's quite safe to do so. (yes the other option is "git worktree add", but that could be a heavy hammer sometimes)

> I think that could be the way to go for merges and cherry-picks, or

Just so we're clear, what is your "the way" to go? to remove CHERRY_HEAD_PICK and MERGE_HEAD (and other MERGE_* as well) if --ignore-in-process is specified? Or to leave MERGE_* and CHERRY_PICK_HEAD alone and delete other stuff?

Show 5 quoted lines
> possibly require --discard-changes as well. The only time I use checkout
> like this is during a rebase if I want to rewind it - I edit the todo
> list with the output of 'git log --pretty="pick %h %s" --reverse' and do
> 'git checkout' followed by 'git rebase --continue' Though these days I
> could add a 'reset' line to the todo list and skip the checkout.
-- 
Duy
Elijah Newren· Mar 28, 2019, 15:05 UTC · re: Duy Nguyen · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Thu, Mar 28, 2019 at 4:04 AM Duy Nguyen <pclouds@gmail.com> wrote:
Show 28 quoted lines
>
> On Wed, Mar 27, 2019 at 5:24 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
> >
> > On 26/03/2019 15:48, Elijah Newren wrote:
> > > On Tue, Mar 26, 2019 at 8:24 AM Duy Nguyen <pclouds@gmail.com> wrote:
> > >> On Tue, Mar 26, 2019 at 10:01 PM Elijah Newren <newren@gmail.com> wrote:
> > >
> > >> Yeah.. --ignore-in-process does not necessarily mean aborting
> > >> something when you just want to get out to examine some other commit.
> > >> And I agree doing nothing seems like the best (or least
> > >> confusing/surprising) option.
> > >>
> > >> There will be some funny thing. Like if you commit after switching
> > >> away and MERGE_HEAD is there, I think you will be creating a merge
> > >> commit.
> > >
> > > Yes, and in the middle of a cherry-pick with a range you've added some
> > > commits to one branch and some to another.  In the middle of a revert
> > > you're doing similar.  It sounds like crazytown to me (and maybe we
> > > shouldn't provide the --ignore-in-process flag unless users clamor for
> > > it
>
> I missed this part in my last reading. I think if we could safely
> switch away and get back to resume, then --ignore-in-process could
> still be useful. I sometimes switch to another commit to check out
> stuff then back. For interactive rebase with "edit" command for
> example, it's quite safe to do so. (yes the other option is "git
> worktree add", but that could be a heavy hammer sometimes)

Ah, switching not for the purpose of continuing the operation elsewhere, but just to check things out with the intent of coming back before continuing. That does make sense. Please do add a warning when users do it, though.

Show 6 quoted lines
> > I think that could be the way to go for merges and cherry-picks, or
>
> Just so we're clear, what is your "the way" to go? to remove
> CHERRY_HEAD_PICK and MERGE_HEAD (and other MERGE_* as well) if
> --ignore-in-process is specified? Or to leave MERGE_* and
> CHERRY_PICK_HEAD alone and delete other stuff?

Personally, I don't think `switch` should modify in-process state of other operations ever. Not merge state, not cherry-pick state, not rebase state, not bisect state, etc. And default should be for it to error out if any such state exists. In-process state should be updated almost exclusively by `<operation> --(continue|abort|skip|quit)` (or in bisect's case, the bad/good/skip/reset commands). They only reason I put the 'almost' qualifier in there is that I'm not opposed to the _reset_ command continuing to do some things with state because of backward compatibility (and I might begrudgingly admit the same for other commands if they touch it though I'm tempted to say any command other than reset and the operation itself that touches in-process state represents a bug that should be fixed). However, I very much want to steer users away from using reset (or other commands) to change state and guide them to instead use `<operation> --(continue|abort|skip|quit)`. I think we make things confusing and do users a disservice by not having in-process state be a totally orthogonal thing that is only updated by that process's subcommand.

If people use --ignore-in-process, I think the state should remain and we should give a big warning to the user about how the operation is still in progress and likely to give very confusing results and suggest either switching back to the current commit or running `<operation> --quit` for them to clear it.

Also, if --ignore-in-process still fails because they need --discard-changes too, then we could give a really obnoxious warning about how they should probably consider --quit first if they really want to switch.

Show 5 quoted lines
> > possibly require --discard-changes as well. The only time I use checkout
> > like this is during a rebase if I want to rewind it - I edit the todo
> > list with the output of 'git log --pretty="pick %h %s" --reverse' and do
> > 'git checkout' followed by 'git rebase --continue' Though these days I
> > could add a 'reset' line to the todo list and skip the checkout.

I think Phillip's usecase is entirely consistent with the don't-have-switch-touch-process-state view.

Hope that helps, Elijah

Phillip Wood· Mar 28, 2019, 16:23 UTC · re: Duy Nguyen · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On 28/03/2019 11:04, Duy Nguyen wrote:
Show 24 quoted lines
> On Wed, Mar 27, 2019 at 5:24 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
>>
>> On 26/03/2019 15:48, Elijah Newren wrote:
>>> On Tue, Mar 26, 2019 at 8:24 AM Duy Nguyen <pclouds@gmail.com> wrote:
>>>> On Tue, Mar 26, 2019 at 10:01 PM Elijah Newren <newren@gmail.com> wrote:
>>>
>>>> Yeah.. --ignore-in-process does not necessarily mean aborting
>>>> something when you just want to get out to examine some other commit.
>>>> And I agree doing nothing seems like the best (or least
>>>> confusing/surprising) option.
>>>>
>>>> There will be some funny thing. Like if you commit after switching
>>>> away and MERGE_HEAD is there, I think you will be creating a merge
>>>> commit.
>>>
>>> Yes, and in the middle of a cherry-pick with a range you've added some
>>> commits to one branch and some to another.  In the middle of a revert
>>> you're doing similar.  It sounds like crazytown to me (and maybe we
>>> shouldn't provide the --ignore-in-process flag unless users clamor for
>>> it
> 
> I missed this part in my last reading. I think if we could safely
> switch away and get back to resume, then --ignore-in-process could
> still be useful.

If we can get back safely then that makes sense, I'm not sure about switching while there are conflicts or staged changes though, it feels like there's more potential for things to go wrong there.

Show 10 quoted lines
> I sometimes switch to another commit to check out
> stuff then back. For interactive rebase with "edit" command for
> example, it's quite safe to do so. (yes the other option is "git
> worktree add", but that could be a heavy hammer sometimes) >
>> I think that could be the way to go for merges and cherry-picks, or
> 
> Just so we're clear, what is your "the way" to go? to remove
> CHERRY_HEAD_PICK and MERGE_HEAD (and other MERGE_* as well) if
> --ignore-in-process is specified? Or to leave MERGE_* and
> CHERRY_PICK_HEAD alone and delete other stuff?

I was agreeing with Elijah about dropping --ignore-in-progress unless there's a demand for it or at least restricting it so that it requires --discard-changes and aborts in-progress merges and single in-progress cherry-picks/reverts. (I'm worried about people switching branches when cherry-picking more than one commit, though as you say it can make sense during a rebase.)

Best Wishes
Phillip
Show 5 quoted lines
>> possibly require --discard-changes as well. The only time I use checkout
>> like this is during a rebase if I want to rewind it - I edit the todo
>> list with the output of 'git log --pretty="pick %h %s" --reverse' and do
>> 'git checkout' followed by 'git rebase --continue' Though these days I
>> could add a 'reset' line to the todo list and skip the checkout.
Elijah Newren· Mar 28, 2019, 17:39 UTC · re: Phillip Wood · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

Hi Phillip,
On Thu, Mar 28, 2019 at 9:23 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 5 quoted lines
> On 28/03/2019 11:04, Duy Nguyen wrote:
> > On Wed, Mar 27, 2019 at 5:24 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
> >> On 26/03/2019 15:48, Elijah Newren wrote:
> >>> On Tue, Mar 26, 2019 at 8:24 AM Duy Nguyen <pclouds@gmail.com> wrote:
> >>>> On Tue, Mar 26, 2019 at 10:01 PM Elijah Newren <newren@gmail.com> wrote:
Show 13 quoted lines
> >>> Yes, and in the middle of a cherry-pick with a range you've added some
> >>> commits to one branch and some to another.  In the middle of a revert
> >>> you're doing similar.  It sounds like crazytown to me (and maybe we
> >>> shouldn't provide the --ignore-in-process flag unless users clamor for
> >>> it
> >
> > I missed this part in my last reading. I think if we could safely
> > switch away and get back to resume, then --ignore-in-process could
> > still be useful.
>
> If we can get back safely then that makes sense, I'm not sure about
> switching while there are conflicts or staged changes though, it feels
> like there's more potential for things to go wrong there.

I really like that way of putting it; I think that makes it much clearer. Note, though that staged changes and conflicts could happen with any of rebase, merge, cherry-pick, or revert, so this problem is not limited to a subset of those operations.

Show 18 quoted lines
> > I sometimes switch to another commit to check out
> > stuff then back. For interactive rebase with "edit" command for
> > example, it's quite safe to do so. (yes the other option is "git
> > worktree add", but that could be a heavy hammer sometimes)
>
> >> I think that could be the way to go for merges and cherry-picks, or
> >
> > Just so we're clear, what is your "the way" to go? to remove
> > CHERRY_HEAD_PICK and MERGE_HEAD (and other MERGE_* as well) if
> > --ignore-in-process is specified? Or to leave MERGE_* and
> > CHERRY_PICK_HEAD alone and delete other stuff?
>
> I was agreeing with Elijah about dropping --ignore-in-progress unless
> there's a demand for it or at least restricting it so that it requires
> --discard-changes and aborts in-progress merges and single in-progress
> cherry-picks/reverts. (I'm worried about people switching branches when
> cherry-picking more than one commit, though as you say it can make sense
> during a rebase.)

I understand the desire to prevent mis-uses, and I agree that if there are staged changes or conflicts it's really likely things will go sideways. But I think we should instead check for those situations rather than use e.g. rebase vs. merge as a proxy for whether those problems could be present. I am especially concerned with the idea of having something like "git switch --ignore-in-progress --discard-changes" being used to quit merges or cherry-picks or reverts or even rebases. In my opinion, doing so is creating flags to combine uncommon pairs of git commands (git <operation> --quit + git switch) in a way that is far less clear. I think that's a bad route to go down, and we should keep the commands orthogonal (if I could start all over, I'd also make reset and checkout and everything else stop modifying any in-progress state).

Instead, I would either:
  * Drop `--ignore-in-progress` for now.  (Although Duy had a
meaningful usecase)
OR
  * Make `git switch --ignore-in-progress <branch>` leave all process
state in place and switch branches, if we would otherwise be able to
switch branches (i.e. there isn't dirty or conflicted changes in the
way).  BUT, make sure to also:
  * Make '--ignore-in-progress' incompatible with both '-m' and
'--discard-changes'; if folks try to use either of those additional
options with --ignore-in-progress, tell people to use `<operation>
--quit` first.  Do NOT provide an override. (Alternatively, refer to
`<operation> --quit` as the override, since it is).
Elijah
Phillip Wood· Mar 29, 2019, 11:04 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

Hi Elijah
On 28/03/2019 17:39, Elijah Newren wrote:
Show 27 quoted lines
> Hi Phillip,
> 
> On Thu, Mar 28, 2019 at 9:23 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>> On 28/03/2019 11:04, Duy Nguyen wrote:
>>> On Wed, Mar 27, 2019 at 5:24 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
>>>> On 26/03/2019 15:48, Elijah Newren wrote:
>>>>> On Tue, Mar 26, 2019 at 8:24 AM Duy Nguyen <pclouds@gmail.com> wrote:
>>>>>> On Tue, Mar 26, 2019 at 10:01 PM Elijah Newren <newren@gmail.com> wrote:
> 
>>>>> Yes, and in the middle of a cherry-pick with a range you've added some
>>>>> commits to one branch and some to another.  In the middle of a revert
>>>>> you're doing similar.  It sounds like crazytown to me (and maybe we
>>>>> shouldn't provide the --ignore-in-process flag unless users clamor for
>>>>> it
>>>
>>> I missed this part in my last reading. I think if we could safely
>>> switch away and get back to resume, then --ignore-in-process could
>>> still be useful.
>>
>> If we can get back safely then that makes sense, I'm not sure about
>> switching while there are conflicts or staged changes though, it feels
>> like there's more potential for things to go wrong there.
> 
> I really like that way of putting it; I think that makes it much
> clearer.  Note, though that staged changes and conflicts could happen
> with any of rebase, merge, cherry-pick, or revert, so this problem is
> not limited to a subset of those operations.
Indeed
Show 25 quoted lines
> 
>>> I sometimes switch to another commit to check out
>>> stuff then back. For interactive rebase with "edit" command for
>>> example, it's quite safe to do so. (yes the other option is "git
>>> worktree add", but that could be a heavy hammer sometimes)
>>
>>>> I think that could be the way to go for merges and cherry-picks, or
>>>
>>> Just so we're clear, what is your "the way" to go? to remove
>>> CHERRY_HEAD_PICK and MERGE_HEAD (and other MERGE_* as well) if
>>> --ignore-in-process is specified? Or to leave MERGE_* and
>>> CHERRY_PICK_HEAD alone and delete other stuff?
>>
>> I was agreeing with Elijah about dropping --ignore-in-progress unless
>> there's a demand for it or at least restricting it so that it requires
>> --discard-changes and aborts in-progress merges and single in-progress
>> cherry-picks/reverts. (I'm worried about people switching branches when
>> cherry-picking more than one commit, though as you say it can make sense
>> during a rebase.)
> 
> I understand the desire to prevent mis-uses, and I agree that if there
> are staged changes or conflicts it's really likely things will go
> sideways.  But I think we should instead check for those situations
> rather than use e.g. rebase vs. merge as a proxy for whether those
> problems could be present.

When cherry-picking multiple commits if the user commits the conflict resolution with 'git commit' then the presence of .git/sequencer is the only sign that a cherry-pick is in progress (wt-status.c fails to detect this, I've got a fix but no tests yet). rebase can also stop without having conflicts or staged changes so I think we need to check for in progress commands as well as conflicts (what do we want to do if someone tries to switch in the middle of a bisect? - I don't have a strong opinion). I agree switch should fail if there are conflicts, but I think it is fine to switch with staged or unstaged changes if there isn't a merge etc in progress (I quite often start working on something and then realize I haven't started a new branch just before I commit). I could possibly be convinced that silently switching with staged changes is always a bad idea though.

Show 6 quoted lines
> I am especially concerned with the idea of
> having something like "git switch --ignore-in-progress
> --discard-changes" being used to quit merges or cherry-picks or
> reverts or even rebases. In my opinion, doing so is creating flags to > combine uncommon pairs of git commands (git <operation> --quit + git
> switch) in a way that is far less clear.  I think that's a bad route
> to go down, and we should keep the commands orthogonal 

keeping commands orthogonal is certainly clearer, if less convenient - lets do it (assuming Duy agrees).

Show 8 quoted lines
> (if I could
> start all over, I'd also make reset and checkout and everything else
> stop modifying any in-progress state).
> 
> Instead, I would either:
> 
>    * Drop `--ignore-in-progress` for now.  (Although Duy had a
> meaningful usecase)

I think it could be useful during a rebase, I'm not sure about any of the other operations though.

Show 7 quoted lines
> 
> OR
> 
>    * Make `git switch --ignore-in-progress <branch>` leave all process
> state in place and switch branches, if we would otherwise be able to
> switch branches (i.e. there isn't dirty or conflicted changes in the
> way).

I thought we allowed branch switches when there are staged or unstaged changes, I don't think that is a problem unless we're in the middle of a merge etc. I'm still not sure it's a good idea to switch branches in the middle of a multiple cherry-pick, maybe we should print a warning.

Show 5 quoted lines
>  BUT, make sure to also:
>    * Make '--ignore-in-progress' incompatible with both '-m' and
> '--discard-changes'; if folks try to use either of those additional
> options with --ignore-in-progress, tell people to use `<operation>
> --quit` first.

I think of --discard-changes like --abort. --quit only removes the state dir so would pair with -m, as it does not reset the index or working tree.

Overall I think we're more or less in agreement modulo the treatment of staged changes when there is no merge etc in progress.

Best Wishes
Phillip
Show 6 quoted lines
>  Do NOT provide an override. (Alternatively, refer to
> `<operation> --quit` as the override, since it is).
> 
> 
> Elijah
> 
Duy Nguyen· Mar 29, 2019, 11:27 UTC · re: Phillip Wood · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Fri, Mar 29, 2019 at 6:04 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 9 quoted lines
> > I am especially concerned with the idea of
> > having something like "git switch --ignore-in-progress
> > --discard-changes" being used to quit merges or cherry-picks or
> > reverts or even rebases. In my opinion, doing so is creating flags to > combine uncommon pairs of git commands (git <operation> --quit + git
> > switch) in a way that is far less clear.  I think that's a bad route
> > to go down, and we should keep the commands orthogonal
>
> keeping commands orthogonal is certainly clearer, if less convenient -
> lets do it (assuming Duy agrees).

I sent out v6 a bit too early I guess. But removing --ignore-in-progress also makes it orthogonal. You could cancel the in-progress operation and resume switching, or find another way and forget about switching.

-- 
Duy
Elijah Newren· Mar 29, 2019, 15:35 UTC · re: Phillip Wood · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

Hi Phillip,
On Fri, Mar 29, 2019 at 4:04 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
> On 28/03/2019 17:39, Elijah Newren wrote:
> > On Thu, Mar 28, 2019 at 9:23 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
> >> On 28/03/2019 11:04, Duy Nguyen wrote:
Show 24 quoted lines
> >>> Just so we're clear, what is your "the way" to go? to remove
> >>> CHERRY_HEAD_PICK and MERGE_HEAD (and other MERGE_* as well) if
> >>> --ignore-in-process is specified? Or to leave MERGE_* and
> >>> CHERRY_PICK_HEAD alone and delete other stuff?
> >>
> >> I was agreeing with Elijah about dropping --ignore-in-progress unless
> >> there's a demand for it or at least restricting it so that it requires
> >> --discard-changes and aborts in-progress merges and single in-progress
> >> cherry-picks/reverts. (I'm worried about people switching branches when
> >> cherry-picking more than one commit, though as you say it can make sense
> >> during a rebase.)
> >
> > I understand the desire to prevent mis-uses, and I agree that if there
> > are staged changes or conflicts it's really likely things will go
> > sideways.  But I think we should instead check for those situations
> > rather than use e.g. rebase vs. merge as a proxy for whether those
> > problems could be present.
>
> When cherry-picking multiple commits if the user commits the conflict
> resolution with 'git commit' then the presence of .git/sequencer is the
> only sign that a cherry-pick is in progress (wt-status.c fails to detect
> this, I've got a fix but no tests yet). rebase can also stop without
> having conflicts or staged changes so I think we need to check for in
> progress commands as well as conflicts (what do we want to do if someone

This whole discussion is about "--ignore-in-progress" which implicitly implies we are checking for in progress commands and choosing whether to override it. So I don't understand what you mean by saying we need to check for it; isn't that a given?

Show 7 quoted lines
> tries to switch in the middle of a bisect? - I don't have a strong
> opinion). I agree switch should fail if there are conflicts, but I think
> it is fine to switch with staged or unstaged changes if there isn't a
> merge etc in progress (I quite often start working on something and
> then realize I haven't started a new branch just before I commit). I
> could possibly be convinced that silently switching with staged changes
> is always a bad idea though.

I think we might be missing the big picture by trying to discuss things in terms of in-progress operations or conflicts or staged changes or unstaged changes. Allow me to attempt to reframe the discussion: We have identified at least one case where allowing the --ignore-in-progress flag would be unsafe, and we've identified one where we think it would be safe and useful, thus we need a rule of thumb for when it is safe to use and when it isn't. Here's my attempt:

  --ignore-in-progress is safe enough for usage if we can switch to
another branch and back with no net overall changes to either the
index or the working tree after the two switches.

This rule could allow for the presence of both staged and unstaged changes (or maybe even conflicts in some alternate world where checkout/switch didn't necessarily error out on those), depending on if switch/checkout can operate without touching those particular files as part of switching.

Show 9 quoted lines
> > I am especially concerned with the idea of
> > having something like "git switch --ignore-in-progress
> > --discard-changes" being used to quit merges or cherry-picks or
> > reverts or even rebases. In my opinion, doing so is creating flags to > combine uncommon pairs of git commands (git <operation> --quit + git
> > switch) in a way that is far less clear.  I think that's a bad route
> > to go down, and we should keep the commands orthogonal
>
> keeping commands orthogonal is certainly clearer, if less convenient -
> lets do it (assuming Duy agrees).
Yaay!
Show 11 quoted lines
> > (if I could
> > start all over, I'd also make reset and checkout and everything else
> > stop modifying any in-progress state).
> >
> > Instead, I would either:
> >
> >    * Drop `--ignore-in-progress` for now.  (Although Duy had a
> > meaningful usecase)
>
> I think it could be useful during a rebase, I'm not sure about any of
> the other operations though.

I think it could be useful during some rebases, but it should not be allowed if the user can't switch back to the current commit with no net overall changes to the index or working tree.

Also, I don't see how rebase is unique here. Rebase, cherry-pick, merge, and revert can all stop with conflicts, staged changes, and unstaged changes. All of them can also stop without any one of those (e.g. cherry-pick'ing a commit which has been piecemeal applied already, merging a branch whose individual changes have already been cherry-picked and when the user has specified --no-commit, or reverting a commit whose changes have already been unapplied). Thus, I continue to believe that which operation is in progress is irrelevant. Either we shouldn't allow switching during any in-progress operation, or we should determine some other criteria for when it is safe to allow --ignore-in-progress. Basing it on the operation would sometimes allow --ignore-in-progress to be used when it shouldn't be, and disallow it sometimes when it shouldn't. I'm a fan of the rule I mentioned up above ("if we can switch and switch back with no net changes then it's safe enough to allow")

Show 12 quoted lines
> >
> > OR
> >
> >    * Make `git switch --ignore-in-progress <branch>` leave all process
> > state in place and switch branches, if we would otherwise be able to
> > switch branches (i.e. there isn't dirty or conflicted changes in the
> > way).
>
> I thought we allowed branch switches when there are staged or unstaged
> changes, I don't think that is a problem unless we're in the middle of a
> merge etc. I'm still not sure it's a good idea to switch branches in the
> middle of a multiple cherry-pick, maybe we should print a warning.

I didn't say to disallow it if there were dirty or conflicted changes, I said to disallow it if there were dirty or conflicted changes *in the way*. We don't allow branch switches when dirty changes would be overwritten or need to be merged, as that can't easily be reversed. I think --ignore-in-progress should only be allowed when it can be easily reversed to get the user back to the right branch/commit.

This "no net changes" rule also reinforces (or is reinforced by) the other suggestion I made of having --ignore-in-progress be made incompatible with both -m and --discard-changes.

But I totally agree that switching branches during the middle of some operation should print a warning -- not just for cherry-pick, but for merge or rebase or revert too. In all cases it'll be important to tell the user both that they could really mess things up if they try to resume the operation without switching back, and telling the user how to get back to where they used to be (in rebase's case, that'd be "git switch --ignore-in-progress <previous-commit>" while for the other three it'd be "git switch --ignore-in-progress <previous-branch>").

Phillip Wood· Apr 25, 2019, 10:20 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

Hi Elijah

Sorry it's taken me so long to reply to this - I'm not sure where the last few weeks have gone.

On 29/03/2019 15:35, Elijah Newren wrote:
Show 36 quoted lines
> Hi Phillip,
> 
> On Fri, Mar 29, 2019 at 4:04 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>> On 28/03/2019 17:39, Elijah Newren wrote:
>>> On Thu, Mar 28, 2019 at 9:23 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>>>> On 28/03/2019 11:04, Duy Nguyen wrote:
> 
>>>>> Just so we're clear, what is your "the way" to go? to remove
>>>>> CHERRY_HEAD_PICK and MERGE_HEAD (and other MERGE_* as well) if
>>>>> --ignore-in-process is specified? Or to leave MERGE_* and
>>>>> CHERRY_PICK_HEAD alone and delete other stuff?
>>>>
>>>> I was agreeing with Elijah about dropping --ignore-in-progress unless
>>>> there's a demand for it or at least restricting it so that it requires
>>>> --discard-changes and aborts in-progress merges and single in-progress
>>>> cherry-picks/reverts. (I'm worried about people switching branches when
>>>> cherry-picking more than one commit, though as you say it can make sense
>>>> during a rebase.)
>>>
>>> I understand the desire to prevent mis-uses, and I agree that if there
>>> are staged changes or conflicts it's really likely things will go
>>> sideways.  But I think we should instead check for those situations
>>> rather than use e.g. rebase vs. merge as a proxy for whether those
>>> problems could be present.
>>
>> When cherry-picking multiple commits if the user commits the conflict
>> resolution with 'git commit' then the presence of .git/sequencer is the
>> only sign that a cherry-pick is in progress (wt-status.c fails to detect
>> this, I've got a fix but no tests yet). rebase can also stop without
>> having conflicts or staged changes so I think we need to check for in
>> progress commands as well as conflicts (what do we want to do if someone
> 
> This whole discussion is about "--ignore-in-progress" which implicitly
> implies we are checking for in progress commands and choosing whether
> to override it.  So I don't understand what you mean by saying we need
> to check for it; isn't that a given?

I think I was confused "instead" above. Anyway we both agree that those checks are in addition to the in-progress ones.

Show 20 quoted lines
>> tries to switch in the middle of a bisect? - I don't have a strong
>> opinion). I agree switch should fail if there are conflicts, but I think
>> it is fine to switch with staged or unstaged changes if there isn't a
>> merge etc in progress (I quite often start working on something and
>> then realize I haven't started a new branch just before I commit). I
>> could possibly be convinced that silently switching with staged changes
>> is always a bad idea though.
> 
> I think we might be missing the big picture by trying to discuss
> things in terms of in-progress operations or conflicts or staged
> changes or unstaged changes.  Allow me to attempt to reframe the
> discussion:  We have identified at least one case where allowing the
> --ignore-in-progress flag would be unsafe, and we've identified one
> where we think it would be safe and useful, thus we need a rule of
> thumb for when it is safe to use and when it isn't.  Here's my
> attempt:
> 
>   --ignore-in-progress is safe enough for usage if we can switch to
> another branch and back with no net overall changes to either the
> index or the working tree after the two switches.

My worry is that someone will switch branches and commit by accident before switching back again. Having said that we require them to specify --ignore-in-progress so we have a safe default. I think your rule is probably a good balance between safety and convenience, though it is potentially confusing as it will allow --ignore-in-progress to switch sometimes but not others.

Show 49 quoted lines
> This rule could allow for the presence of both staged and unstaged
> changes (or maybe even conflicts in some alternate world where
> checkout/switch didn't necessarily error out on those), depending on
> if switch/checkout can operate without touching those particular files
> as part of switching.
> 
>>> I am especially concerned with the idea of
>>> having something like "git switch --ignore-in-progress
>>> --discard-changes" being used to quit merges or cherry-picks or
>>> reverts or even rebases. In my opinion, doing so is creating flags to > combine uncommon pairs of git commands (git <operation> --quit + git
>>> switch) in a way that is far less clear.  I think that's a bad route
>>> to go down, and we should keep the commands orthogonal
>>
>> keeping commands orthogonal is certainly clearer, if less convenient -
>> lets do it (assuming Duy agrees).
> 
> Yaay!
> 
>>> (if I could
>>> start all over, I'd also make reset and checkout and everything else
>>> stop modifying any in-progress state).
>>>
>>> Instead, I would either:
>>>
>>>    * Drop `--ignore-in-progress` for now.  (Although Duy had a
>>> meaningful usecase)
>>
>> I think it could be useful during a rebase, I'm not sure about any of
>> the other operations though.
> 
> I think it could be useful during some rebases, but it should not be
> allowed if the user can't switch back to the current commit with no
> net overall changes to the index or working tree.
> 
> Also, I don't see how rebase is unique here.  Rebase, cherry-pick,
> merge, and revert can all stop with conflicts, staged changes, and
> unstaged changes.  All of them can also stop without any one of those
> (e.g. cherry-pick'ing a commit which has been piecemeal applied
> already, merging a branch whose individual changes have already been
> cherry-picked and when the user has specified --no-commit, or
> reverting a commit whose changes have already been unapplied).  Thus,
> I continue to believe that which operation is in progress is
> irrelevant.  Either we shouldn't allow switching during any
> in-progress operation, or we should determine some other criteria for
> when it is safe to allow --ignore-in-progress.  Basing it on the
> operation would sometimes allow --ignore-in-progress to be used when
> it shouldn't be, and disallow it sometimes when it shouldn't.  I'm a
> fan of the rule I mentioned up above ("if we can switch and switch
> back with no net changes then it's safe enough to allow")

You're right that there is nothing intrinsically special about rebases, it's just that in practice I think it is more useful in that case (I don't remember wanting to switch branches while cherry-picking etc but maybe that because I use them less). Having a consistent rule as you suggest would be less confusing.

Show 32 quoted lines
>>> OR
>>>
>>>    * Make `git switch --ignore-in-progress <branch>` leave all process
>>> state in place and switch branches, if we would otherwise be able to
>>> switch branches (i.e. there isn't dirty or conflicted changes in the
>>> way).
>>
>> I thought we allowed branch switches when there are staged or unstaged
>> changes, I don't think that is a problem unless we're in the middle of a
>> merge etc. I'm still not sure it's a good idea to switch branches in the
>> middle of a multiple cherry-pick, maybe we should print a warning.
> 
> I didn't say to disallow it if there were dirty or conflicted changes,
> I said to disallow it if there were dirty or conflicted changes *in
> the way*.  We don't allow branch switches when dirty changes would be
> overwritten or need to be merged, as that can't easily be reversed.  I
> think --ignore-in-progress should only be allowed when it can be
> easily reversed to get the user back to the right branch/commit.
> 
> This "no net changes" rule also reinforces (or is reinforced by) the
> other suggestion I made of having --ignore-in-progress be made
> incompatible with both -m and --discard-changes.
> 
> But I totally agree that switching branches during the middle of some
> operation should print a warning -- not just for cherry-pick, but for
> merge or rebase or revert too.  In all cases it'll be important to
> tell the user both that they could really mess things up if they try
> to resume the operation without switching back, and telling the user
> how to get back to where they used to be (in rebase's case, that'd be
> "git switch --ignore-in-progress <previous-commit>" while for the
> other three it'd be "git switch --ignore-in-progress
> <previous-branch>").
Giving the user instructions for how to switch back is a really good idea.
Best Wishes
Phillip
> 
Junio C Hamano· Apr 1, 2019, 09:29 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

Elijah Newren <newren@gmail.com> writes:
Show 13 quoted lines
> On Tue, Mar 26, 2019 at 8:24 AM Duy Nguyen <pclouds@gmail.com> wrote:
>> On Tue, Mar 26, 2019 at 10:01 PM Elijah Newren <newren@gmail.com> wrote:
>
>> Yeah.. --ignore-in-process does not necessarily mean aborting
>> something when you just want to get out to examine some other commit.
>> And I agree doing nothing seems like the best (or least
>> confusing/surprising) option.
> ...
> Yes, and in the middle of a cherry-pick with a range you've added some
> commits to one branch and some to another.  In the middle of a revert
> you're doing similar.  It sounds like crazytown to me (and maybe we
> shouldn't provide the --ignore-in-process flag unless users clamor for
> it ...

It really sounds like --ignore-in-process that _leaves_ the state files around which are known to be unrelated to the new commit pointed out by HEAD is asking for trouble.

We just should tell the users "if you want to go examine something else during an in-progress operation, do it elsewhere, or cancel the in-progress operation with 'cmd --abort' (or perhaps 'reset --hard') which will allow 'switch' to move away".

The thing is, "switch temporarily to another commit, do some sightseeing and then switch back to the original commit--we did not touch the in-progress state files while doing so, so we ought to come back to exactly the same state to be able to continue" is brittle. Temporarily switching to another commit or branch would need to touch the index (in some irrevocable way) and with future versions of Git, underlying machineries (like UNDO data) other than the index as well. What the user would do after (temporarily) switching out of the in-process state (e.g. controlled by the sequencer) may be destructive (e.g. "switch away temporarily, forget that you've switched away and try to run cherry-pick or rebase").

Elijah Newren· Mar 11, 2019, 17:54 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

A few other comments that I thought of while responding elsewhere in the thread that didn't make sense to include elsewhere...

On Fri, Mar 8, 2019 at 2:00 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 5 quoted lines
>
> "git checkout" doing too many things is a source of confusion for many
> users (and it even bites old timers sometimes). To remedy that, the
> command will be split into two new ones: switch and
> something-to-checkout-paths. The good old "git checkout" command is
s/something-to-checkout-paths/restore/ ?
Show 9 quoted lines
> +-g::
> +--guess::
> +       If `<branch>` is not found but there does exist a tracking
> +       branch in exactly one remote (call it `<remote>`) with a
> +       matching name, treat as equivalent to
> ++
> +------------
> +$ git switch -c <branch> --track <remote>/<branch>
> +------------
I'm not sure if it'd be better or worse to simplify this to
   git switch --track <remote>/<branch>
Thoughts?
Show 12 quoted lines
> ++
> +If the branch exists in multiple remotes and one of them is named by
> +the `checkout.defaultRemote` configuration variable, we'll use that
> +one for the purposes of disambiguation, even if the `<branch>` isn't
> +unique across all remotes. Set it to e.g. `checkout.defaultRemote=origin`
> +to always checkout remote branches from there if `<branch>` is
> +ambiguous but exists on the 'origin' remote. See also
> +`checkout.defaultRemote` in linkgit:git-config[1].
> +
> +-q::
> +--quiet::
> +       Quiet, suppress feedback messages.

--quiet and --progress should be adjacent in the man-page since they are touching upon the same concept. It'd be nice if we had a way to group similar items like this besides simply putting them together, but we can at least do that.

Show 7 quoted lines
> +
> +-f::
> +--force::
> +       Proceed even if the index or the working tree differs from
> +       HEAD. Both the index and working tree are restored to match
> +       the switching target. This is used to throw away local
> +       changes.

--force (or --discard-changes as mentioned elsewhere in this thread), -m/--merge, and --conflict are also similar options and should be adjacent in the man-page; they all reflect on how to handle switching when local changes affect the same paths that different between HEAD and the other branch.

Show 6 quoted lines
> +--progress::
> +--no-progress::
> +       Progress status is reported on the standard error stream
> +       by default when it is attached to a terminal, unless `--quiet`
> +       is specified. This flag enables progress reporting even if not
> +       attached to a terminal, regardless of `--quiet`.

This again makes me curious what --quiet actually supresses; in the case of branch switching, are there any non-warning informational messages other than progress reports that are printed? If not, the extra sentence at the end of the description for --progress can (and probably should) be stripped.

Show 16 quoted lines
> +
> +-t::
> +--track::
> +       When creating a new branch, set up "upstream" configuration.
> +       `-c` is implied. See "--track" in linkgit:git-branch[1] for
> +       details.
> ++
> +If no `-c` option is given, the name of the new branch will be derived
> +from the remote-tracking branch, by looking at the local part of the
> +refspec configured for the corresponding remote, and then stripping
> +the initial part up to the "*".  This would tell us to use "hack" as
> +the local branch when branching off of "origin/hack" (or
> +"remotes/origin/hack", or even "refs/remotes/origin/hack").  If the
> +given name has no slash, or the above guessing results in an empty
> +name, the guessing is aborted.  You can explicitly give a name with
> +`-c` in such a case.

Slightly disappointed that we couldn't remove --track; it has always seemed a bit complicated. But I don't see how to do so, since people do want upstream branches to be tracked with their new branches.

Show 19 quoted lines
> +
> +--no-track::
> +       Do not set up "upstream" configuration, even if the
> +       branch.autoSetupMerge configuration variable is true.
> +
> +-m::
> +--merge::
> +       If you have local modifications to one or more files that are
> +       different between the current branch and the branch to which
> +       you are switching, the command refuses to switch branches in
> +       order to preserve your modifications in context.  However,
> +       with this option, a three-way merge between the current
> +       branch, your working tree contents, and the new branch is
> +       done, and you will be on the new branch.
> ++
> +When a merge conflict happens, the index entries for conflicting
> +paths are left unmerged, and you need to resolve the conflicts
> +and mark the resolved paths with `git add` (or `git rm` if the merge
> +should result in deletion of the path).

Now that Phillip highlighted issues with -m and -f, it's hard not to wonder about other corner cases. For example, what if the user made some changes, staged them, then made more changes, then tried to 'git checkout -m <other branch>'? That's no longer a three-way merge, but four way. How does that work? Does it just rely on merge-recursive's (poorly defined) choice of when to bail out and when to permit such craziness?

Show 11 quoted lines
> +--conflict=<style>::
> +       The same as --merge option above, but changes the way the
> +       conflicting hunks are presented, overriding the
> +       merge.conflictStyle configuration variable.  Possible values are
> +       "merge" (default) and "diff3" (in addition to what is shown by
> +       "merge" style, shows the original contents).
> +
> +--orphan <new-branch>::
> +       Create a new 'orphan' branch, named `<new-branch>`, started from
> +       `<start-point>` and switch to it. See explanation of the same
> +       option in linkgit:git-checkout[1] for details.

Sigh...does this mean --orphan will remain broken? It has always driven me crazy that it leaves you with a fully populated rather than an empty index. It seemed broken to me before I figured out the special usecase, though it still seemed like the wrong default (an empty index wouldn't surprise due to the "orphan" name, but a full one does to those without the special usecase in mind). Oh well, that's a much smaller battle than the big picture of getting switch and restore in place, and I don't want to derail the bigger picture; anything using --orphan is a somewhat special case anyway.

Show 11 quoted lines
> +You can give the `-m` flag to the command, which would try a three-way
> +merge:
> +
> +------------
> +$ git switch -m mytopic
> +Auto-merging frotz
> +------------
> +
> +After this three-way merge, the local modifications are _not_
> +registered in your index file, so `git diff` would show you what
> +changes you made since the tip of the new branch.

...even if the local modifications were registered in the index file before? Is this why Phillip's "x" was missing and how it avoided doing a four-way merge? I guess it kinda makes sense, view from this angle, but I'm not so sure I like it. Hmm....

Phillip Wood· Mar 12, 2019, 11:06 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

Hi Elijah
On 11/03/2019 17:54, Elijah Newren wrote:
Show 27 quoted lines
> A few other comments that I thought of while responding elsewhere in
> the thread that didn't make sense to include elsewhere...
> 
> On Fri, Mar 8, 2019 at 2:00 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
>>
>> +-m::
>> +--merge::
>> +       If you have local modifications to one or more files that are
>> +       different between the current branch and the branch to which
>> +       you are switching, the command refuses to switch branches in
>> +       order to preserve your modifications in context.  However,
>> +       with this option, a three-way merge between the current
>> +       branch, your working tree contents, and the new branch is
>> +       done, and you will be on the new branch.
>> ++
>> +When a merge conflict happens, the index entries for conflicting
>> +paths are left unmerged, and you need to resolve the conflicts
>> +and mark the resolved paths with `git add` (or `git rm` if the merge
>> +should result in deletion of the path).
> 
> Now that Phillip highlighted issues with -m and -f, it's hard not to
> wonder about other corner cases.  For example, what if the user made
> some changes, staged them, then made more changes, then tried to 'git
> checkout -m <other branch>'?  That's no longer a three-way merge, but
> four way.  How does that work?  Does it just rely on merge-recursive's
> (poorly defined) choice of when to bail out and when to permit such
> craziness?

If the two-way merge fails then it does 'git add -u' before calling merge_recursive(), then any merged paths are reset to the new HEAD (which throws away newly added files, it should keep anything that is not in HEAD or HEAD@{1}). So any staged changes are lost.

Show 8 quoted lines
>> +--orphan <new-branch>::
>> +       Create a new 'orphan' branch, named `<new-branch>`, started from
>> +       `<start-point>` and switch to it. See explanation of the same
>> +       option in linkgit:git-checkout[1] for details.
> 
> Sigh...does this mean --orphan will remain broken?  It has always
> driven me crazy that it leaves you with a fully populated rather than
> an empty index.
I've always thought that was weird.
> It seemed broken to me before I figured out the
> special usecase,
I haven't figured it out yet - what is it?
Best Wishes
Phillip
Show 24 quoted lines
> though it still seemed like the wrong default (an
> empty index wouldn't surprise due to the "orphan" name, but a full one
> does to those without the special usecase in mind).  Oh well, that's a
> much smaller battle than the big picture of getting switch and restore
> in place, and I don't want to derail the bigger picture; anything
> using --orphan is a somewhat special case anyway.
> 
>> +You can give the `-m` flag to the command, which would try a three-way
>> +merge:
>> +
>> +------------
>> +$ git switch -m mytopic
>> +Auto-merging frotz
>> +------------
>> +
>> +After this three-way merge, the local modifications are _not_
>> +registered in your index file, so `git diff` would show you what
>> +changes you made since the tip of the new branch.
> 
> ...even if the local modifications were registered in the index file
> before?  Is this why Phillip's "x" was missing and how it avoided
> doing a four-way merge?  I guess it kinda makes sense, view from this
> angle, but I'm not so sure I like it.  Hmm....
> 
Elijah Newren· Mar 12, 2019, 16:43 UTC · re: Phillip Wood · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Tue, Mar 12, 2019 at 4:06 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 36 quoted lines
>
> Hi Elijah
>
> On 11/03/2019 17:54, Elijah Newren wrote:
> > A few other comments that I thought of while responding elsewhere in
> > the thread that didn't make sense to include elsewhere...
> >
> > On Fri, Mar 8, 2019 at 2:00 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> >>
> >> +-m::
> >> +--merge::
> >> +       If you have local modifications to one or more files that are
> >> +       different between the current branch and the branch to which
> >> +       you are switching, the command refuses to switch branches in
> >> +       order to preserve your modifications in context.  However,
> >> +       with this option, a three-way merge between the current
> >> +       branch, your working tree contents, and the new branch is
> >> +       done, and you will be on the new branch.
> >> ++
> >> +When a merge conflict happens, the index entries for conflicting
> >> +paths are left unmerged, and you need to resolve the conflicts
> >> +and mark the resolved paths with `git add` (or `git rm` if the merge
> >> +should result in deletion of the path).
> >
> > Now that Phillip highlighted issues with -m and -f, it's hard not to
> > wonder about other corner cases.  For example, what if the user made
> > some changes, staged them, then made more changes, then tried to 'git
> > checkout -m <other branch>'?  That's no longer a three-way merge, but
> > four way.  How does that work?  Does it just rely on merge-recursive's
> > (poorly defined) choice of when to bail out and when to permit such
> > craziness?
>
> If the two-way merge fails then it does 'git add -u' before calling
> merge_recursive(), then any merged paths are reset to the new HEAD
> (which throws away newly added files, it should keep anything that is
> not in HEAD or HEAD@{1}). So any staged changes are lost.
Ah, so roughly
  * git add -u
  * uncommitted_tree=$(git write-tree)
  * git reset --hard
  * git checkout $other_branch
  * git merge-recursive $old_branch -- $other_branch $uncommitted_tree
  * git reset --mixed HEAD

This at least gives well defined behavior, even if somewhat suboptimal in relation to losing staged changes (especially when those staged changes were new files).

I wonder if it'd be nicer, after I get my don't-touch-the-working-tree
merge rewrite done, to instead do something like:
  * Write the beginning index to a tree; call it $tree_0
  * Note whether any working tree files differ from the index, add
these all to a temporary index and write to to a tree; call it
$tree_1.
  * Do a three way in-memory merge of $old_branch with $other_branch
and $tree_0; call it $merged_tree if there are no conflicts
  * If $tree_0 == $tree_1, checkout the new branch and update the
index and working tree to reflect the merge result.
  * If $tree_0 != $tree_1 and there were any conflicts, abort telling
the user they need to either unstage or stage changes first (we don't
want to confuse users with a merge of a merge).
  * Switch to the new branch, and update the index to match $merged_tree
  * Do a three way in-memory merge of $old_branch with $merged_tree
and $tree_1, writing the results (including any conflicts) to the
working tree afterward.
Pros of this method:
  * We don't lose newly staged files
  * We don't lose user's carefully staged entries for existing files either
Cons of this method:
  * It may abort with an error if the user has a mix of both staged
and unstaged changes (in particular, it will do so if the user's
staged changes conflict with some difference in the new branch)
Thoughts?
Show 16 quoted lines
>
> >> +--orphan <new-branch>::
> >> +       Create a new 'orphan' branch, named `<new-branch>`, started from
> >> +       `<start-point>` and switch to it. See explanation of the same
> >> +       option in linkgit:git-checkout[1] for details.
> >
> > Sigh...does this mean --orphan will remain broken?  It has always
> > driven me crazy that it leaves you with a fully populated rather than
> > an empty index.
>
> I've always thought that was weird.
>
> > It seemed broken to me before I figured out the
> > special usecase,
>
> I haven't figured it out yet - what is it?

It's a presumption that despite the fact that you want a new branch, and one with no history to boot, that for some reason you want all the previous branch's current contents. In particular, you can think of it as a way to squash all the history of an existing branch into a single commit in a new branch.

Knowing of this usecase doesn't make it bother me any less when I want to create a new unrelated empty branch; it seems like it took the esoteric usecase over the common one to me, but I'm biased. It makes me feel better than neither you nor Eric could understand this behavior of --orphan either.

Phillip Wood· Mar 14, 2019, 11:00 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On 12/03/2019 16:43, Elijah Newren wrote:
Show 45 quoted lines
> On Tue, Mar 12, 2019 at 4:06 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>>
>> Hi Elijah
>>
>> On 11/03/2019 17:54, Elijah Newren wrote:
>>> A few other comments that I thought of while responding elsewhere in
>>> the thread that didn't make sense to include elsewhere...
>>>
>>> On Fri, Mar 8, 2019 at 2:00 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
>>>>
>>>> +-m::
>>>> +--merge::
>>>> +       If you have local modifications to one or more files that are
>>>> +       different between the current branch and the branch to which
>>>> +       you are switching, the command refuses to switch branches in
>>>> +       order to preserve your modifications in context.  However,
>>>> +       with this option, a three-way merge between the current
>>>> +       branch, your working tree contents, and the new branch is
>>>> +       done, and you will be on the new branch.
>>>> ++
>>>> +When a merge conflict happens, the index entries for conflicting
>>>> +paths are left unmerged, and you need to resolve the conflicts
>>>> +and mark the resolved paths with `git add` (or `git rm` if the merge
>>>> +should result in deletion of the path).
>>>
>>> Now that Phillip highlighted issues with -m and -f, it's hard not to
>>> wonder about other corner cases.  For example, what if the user made
>>> some changes, staged them, then made more changes, then tried to 'git
>>> checkout -m <other branch>'?  That's no longer a three-way merge, but
>>> four way.  How does that work?  Does it just rely on merge-recursive's
>>> (poorly defined) choice of when to bail out and when to permit such
>>> craziness?
>>
>> If the two-way merge fails then it does 'git add -u' before calling
>> merge_recursive(), then any merged paths are reset to the new HEAD
>> (which throws away newly added files, it should keep anything that is
>> not in HEAD or HEAD@{1}). So any staged changes are lost.
> 
> Ah, so roughly
>   * git add -u
>   * uncommitted_tree=$(git write-tree)
>   * git reset --hard
>   * git checkout $other_branch
>   * git merge-recursive $old_branch -- $other_branch $uncommitted_tree
>   * git reset --mixed HEAD

Something like that (I think it skips the reset and checkout and does git merge-recursive $old_branch -- $uncommitted_tree $other_branch and then updating HEAD)

Show 21 quoted lines
> This at least gives well defined behavior, even if somewhat suboptimal
> in relation to losing staged changes (especially when those staged
> changes were new files).
> 
> I wonder if it'd be nicer, after I get my don't-touch-the-working-tree
> merge rewrite done, to instead do something like:
>   * Write the beginning index to a tree; call it $tree_0
>   * Note whether any working tree files differ from the index, add
> these all to a temporary index and write to to a tree; call it
> $tree_1.
>   * Do a three way in-memory merge of $old_branch with $other_branch
> and $tree_0; call it $merged_tree if there are no conflicts
>   * If $tree_0 == $tree_1, checkout the new branch and update the
> index and working tree to reflect the merge result.
>   * If $tree_0 != $tree_1 and there were any conflicts, abort telling
> the user they need to either unstage or stage changes first (we don't
> want to confuse users with a merge of a merge).
>   * Switch to the new branch, and update the index to match $merged_tree
>   * Do a three way in-memory merge of $old_branch with $merged_tree
> and $tree_1, writing the results (including any conflicts) to the
> working tree afterward.

As much as it annoys me to have to clear conflicts from the index after a `checkout` or `stash pop` I'm wary of updating the working tree with conflicts without marking those paths as unmerged in the index. Marking them prevents the user from accidentally committing files with unresolved conflicts. It is also easier for the user to find the conflicts if they're marked as unmerged in the index, they can use `diff --cc` and recreate them if they need to start over with the resolution.

Show 8 quoted lines
> 
> Pros of this method:
>   * We don't lose newly staged files
>   * We don't lose user's carefully staged entries for existing files either
> Cons of this method:
>   * It may abort with an error if the user has a mix of both staged
> and unstaged changes (in particular, it will do so if the user's
> staged changes conflict with some difference in the new branch)

I think it's a good way of preserving any unstaged changes, it is probably good that the user is warned ahead of time that their staged changes would be lost.

Show 30 quoted lines
> Thoughts?
> 
>>
>>>> +--orphan <new-branch>::
>>>> +       Create a new 'orphan' branch, named `<new-branch>`, started from
>>>> +       `<start-point>` and switch to it. See explanation of the same
>>>> +       option in linkgit:git-checkout[1] for details.
>>>
>>> Sigh...does this mean --orphan will remain broken?  It has always
>>> driven me crazy that it leaves you with a fully populated rather than
>>> an empty index.
>>
>> I've always thought that was weird.
>>
>>> It seemed broken to me before I figured out the
>>> special usecase,
>>
>> I haven't figured it out yet - what is it?
> 
> It's a presumption that despite the fact that you want a new branch,
> and one with no history to boot, that for some reason you want all the
> previous branch's current contents.  In particular, you can think of
> it as a way to squash all the history of an existing branch into a
> single commit in a new branch.
> 
> Knowing of this usecase doesn't make it bother me any less when I want
> to create a new unrelated empty branch; it seems like it took the
> esoteric usecase over the common one to me, but I'm biased.  It makes
> me feel better than neither you nor Eric could understand this
> behavior of --orphan either.
Thanks for the explanation, I agree it seems like a more esoteric use case.
Best Wishes
Phillip
Nguyễn Thái Ngọc Duy· Mar 19, 2019, 09:39 UTC · re: Phillip Wood · lore

[PATCH] checkout.txt: note about losing staged changes with --merge

If you have staged changes in path A and perform 'checkout --merge' (which could result in conflicts in a totally unrelated path B), changes in A will be gone. Which is unexpected. We are supposed to keep all changes, or kick and scream otherwise.

This is the result of how --merge is implemented, from the very first day in 1be0659efc (checkout: merge local modifications while switching branches., 2006-01-12):

1. a merge is done, unmerged entries are collected
2. a hard switch to a new branch is done, then unmerged entries added
   back

There is no trivial fix for this. Going with 3-way merge one file at a time loses rename detection. Going with 3-way merge by trees requires teaching the algorithm to pick up staged changes. And even if we detect staged changes with --merge and abort for safety, an option to continue --merge is very weird. Such an option would keep worktree changes, but drop staged changes.

Because the problem has been with us since the introduction of --merge and everybody has been pretty happy (except Phillip, who found this problem), I'll just take a note here to acknowledge it and wait for merge wizards to come in and work their magic. There may be a way forward [1].

[1] CABPp-BFoL_U=bzON4SEMaQSKU2TKwnOgNqjt5MUaOejTKGUJxw@mail.gmail.com
Reported-by: Phillip Wood <phillip.wood123@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 This is my "fix" for Phillip's second problem. I chose to reply here
 because this is where an actual fix was discussed. The test script to
 demonstate it is here
 https://public-inbox.org/git/7d3742d6-73e4-2750-6ecb-9edf761d96dd@gmail.com/
 Documentation/git-checkout.txt | 2 ++
 builtin/checkout.c             | 9 +++++++++
 2 files changed, 11 insertions(+)
Show changes to 2 files +11 −0

Documentation/git-checkout.txt, builtin/checkout.c

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index f179b43732..877e5f503a 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -242,6 +242,8 @@ should result in deletion of the path).
 +
 When checking out paths from the index, this option lets you recreate
 the conflicted merge in the specified paths.
++
+When switching branches with `--merge`, staged changes may be lost.
 
 --conflict=<style>::
 	The same as --merge option above, but changes the way the
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 0e6037b296..f95e7975f7 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -726,6 +726,8 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			struct tree *result;
 			struct tree *work;
 			struct merge_options o;
+			struct strbuf sb = STRBUF_INIT;
+
 			if (!opts->merge)
 				return 1;
 
@@ -736,6 +738,13 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			if (!old_branch_info->commit)
 				return 1;
 
+			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 */
 
 			/*
-- 
2.21.0.548.gd3c7d92dc2
Phillip Wood· Mar 19, 2019, 11:24 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH] checkout.txt: note about losing staged changes with --merge

Hi Duy
On 19/03/2019 09:39, Nguyễn Thái Ngọc Duy wrote:
Show 29 quoted lines
> If you have staged changes in path A and perform 'checkout
> --merge' (which could result in conflicts in a totally unrelated path
> B), changes in A will be gone. Which is unexpected. We are supposed
> to keep all changes, or kick and scream otherwise.
> 
> This is the result of how --merge is implemented, from the very first
> day in 1be0659efc (checkout: merge local modifications while switching
> branches., 2006-01-12):
> 
> 1. a merge is done, unmerged entries are collected
> 2. a hard switch to a new branch is done, then unmerged entries added
>     back
> 
> There is no trivial fix for this. Going with 3-way merge one file at a
> time loses rename detection. Going with 3-way merge by trees requires
> teaching the algorithm to pick up staged changes. And even if we detect
> staged changes with --merge and abort for safety, an option to continue
> --merge is very weird. Such an option would keep worktree changes, but
> drop staged changes.
> 
> Because the problem has been with us since the introduction of --merge
> and everybody has been pretty happy (except Phillip, who found this
> problem), I'll just take a note here to acknowledge it and wait for
> merge wizards to come in and work their magic. There may be a way
> forward [1].
> 
> [1] CABPp-BFoL_U=bzON4SEMaQSKU2TKwnOgNqjt5MUaOejTKGUJxw@mail.gmail.com
> 
> Reported-by: Phillip Wood <phillip.wood123@gmail.com>

I try to use phillip.wood@dunelm.org.uk for git stuff as it shouldn't change in the future.

Show 48 quoted lines
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>   This is my "fix" for Phillip's second problem. I chose to reply here
>   because this is where an actual fix was discussed. The test script to
>   demonstate it is here
> 
>   https://public-inbox.org/git/7d3742d6-73e4-2750-6ecb-9edf761d96dd@gmail.com/
> 
>   Documentation/git-checkout.txt | 2 ++
>   builtin/checkout.c             | 9 +++++++++
>   2 files changed, 11 insertions(+)
> 
> diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
> index f179b43732..877e5f503a 100644
> --- a/Documentation/git-checkout.txt
> +++ b/Documentation/git-checkout.txt
> @@ -242,6 +242,8 @@ should result in deletion of the path).
>   +
>   When checking out paths from the index, this option lets you recreate
>   the conflicted merge in the specified paths.
> ++
> +When switching branches with `--merge`, staged changes may be lost.
>   
>   --conflict=<style>::
>   	The same as --merge option above, but changes the way the
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index 0e6037b296..f95e7975f7 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -726,6 +726,8 @@ static int merge_working_tree(const struct checkout_opts *opts,
>   			struct tree *result;
>   			struct tree *work;
>   			struct merge_options o;
> +			struct strbuf sb = STRBUF_INIT;
> +
>   			if (!opts->merge)
>   				return 1;
>   
> @@ -736,6 +738,13 @@ static int merge_working_tree(const struct checkout_opts *opts,
>   			if (!old_branch_info->commit)
>   				return 1;
>   
> +			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);

Thanks for doing this, I think having some sort of warning is a good idea, I wonder if this could be quite noisy though. I guess it depends on how many staged changes people have that don't match the new index. If we diff against the new tree and only print names that are in both lists does that give a definitive list of what will be lost? If it does then if there are a lot of files affected then it will still be noisy (using columns may help) but at least it will not contain false positives. It is more work though, maybe we should just say "staged changes may be lost" and leave it at that.

Best Wishes
Phillip
Show 5 quoted lines
> +
>   			/* Do more real merge */
>   
>   			/*
> 
Junio C Hamano· Mar 20, 2019, 00:23 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH] checkout.txt: note about losing staged changes with --merge

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
Show 19 quoted lines
> If you have staged changes in path A and perform 'checkout
> --merge' (which could result in conflicts in a totally unrelated path
> B), changes in A will be gone. Which is unexpected. We are supposed
> to keep all changes, or kick and scream otherwise.
>
> This is the result of how --merge is implemented, from the very first
> day in 1be0659efc (checkout: merge local modifications while switching
> branches., 2006-01-12):
>
> 1. a merge is done, unmerged entries are collected
> 2. a hard switch to a new branch is done, then unmerged entries added
>    back
>
> There is no trivial fix for this. Going with 3-way merge one file at a
> time loses rename detection. Going with 3-way merge by trees requires
> teaching the algorithm to pick up staged changes. And even if we detect
> staged changes with --merge and abort for safety, an option to continue
> --merge is very weird. Such an option would keep worktree changes, but
> drop staged changes.

I think "checkout -m <otherbranch>" with a dirty index should refuse to run; there is nothing to "continue" after such a failure, so I am not sure what you mean by "an option to continue" (iow, I do not see a need for such an option, and if that option makes the whole notion strange, we can just decide not to have it, can't we?).

Duy Nguyen· Mar 20, 2019, 00:40 UTC · re: Junio C Hamano · lore

Re: [PATCH] checkout.txt: note about losing staged changes with --merge

On Wed, Mar 20, 2019 at 7:24 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 28 quoted lines
>
> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>
> > If you have staged changes in path A and perform 'checkout
> > --merge' (which could result in conflicts in a totally unrelated path
> > B), changes in A will be gone. Which is unexpected. We are supposed
> > to keep all changes, or kick and scream otherwise.
> >
> > This is the result of how --merge is implemented, from the very first
> > day in 1be0659efc (checkout: merge local modifications while switching
> > branches., 2006-01-12):
> >
> > 1. a merge is done, unmerged entries are collected
> > 2. a hard switch to a new branch is done, then unmerged entries added
> >    back
> >
> > There is no trivial fix for this. Going with 3-way merge one file at a
> > time loses rename detection. Going with 3-way merge by trees requires
> > teaching the algorithm to pick up staged changes. And even if we detect
> > staged changes with --merge and abort for safety, an option to continue
> > --merge is very weird. Such an option would keep worktree changes, but
> > drop staged changes.
>
> I think "checkout -m <otherbranch>" with a dirty index should refuse
> to run; there is nothing to "continue" after such a failure, so I am
> not sure what you mean by "an option to continue" (iow, I do not see
> a need for such an option, and if that option makes the whole notion
> strange, we can just decide not to have it, can't we?).

We have --force to continue even when we have local changes, which will be overwritten. I was thinking a similar option which gives us permission to destroy staged changes.

Refusing to run fails the test suite though (I tried that even before this patch), in t7201.10, "switch to another branch while carrying a deletion", because of this line

    git rm two
-- 
Duy
Junio C Hamano· Mar 20, 2019, 01:19 UTC · re: Duy Nguyen · lore

Re: [PATCH] checkout.txt: note about losing staged changes with --merge

Duy Nguyen <pclouds@gmail.com> writes:
Show 9 quoted lines
>> I think "checkout -m <otherbranch>" with a dirty index should refuse
>> to run; there is nothing to "continue" after such a failure, so I am
>> not sure what you mean by "an option to continue" (iow, I do not see
>> a need for such an option, and if that option makes the whole notion
>> strange, we can just decide not to have it, can't we?).
>
> We have --force to continue even when we have local changes, which
> will be overwritten. I was thinking a similar option which gives us
> permission to destroy staged changes.

Ah, then that is not "checkout --continue", but "checkout --force -m"? That sounds sensible, and should behave as if "checkout -f HEAD && checkout -m <otherbranch>" was done, with respect to local changes, I would think.

Duy Nguyen· Mar 20, 2019, 01:22 UTC · re: Junio C Hamano · lore

Re: [PATCH] checkout.txt: note about losing staged changes with --merge

On Wed, Mar 20, 2019 at 8:19 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 17 quoted lines
>
> Duy Nguyen <pclouds@gmail.com> writes:
>
> >> I think "checkout -m <otherbranch>" with a dirty index should refuse
> >> to run; there is nothing to "continue" after such a failure, so I am
> >> not sure what you mean by "an option to continue" (iow, I do not see
> >> a need for such an option, and if that option makes the whole notion
> >> strange, we can just decide not to have it, can't we?).
> >
> > We have --force to continue even when we have local changes, which
> > will be overwritten. I was thinking a similar option which gives us
> > permission to destroy staged changes.
>
> Ah, then that is not "checkout --continue", but "checkout --force
> -m"?  That sounds sensible, and should behave as if "checkout -f
> HEAD && checkout -m <otherbranch>" was done, with respect to local
> changes, I would think.

Kinda. But "--force --merge" makes no sense. --force discards all local changes by definition, which means you can't have conflicts and will not need --merge. I think this is the reason why we die() out when both are specified. So we need something like --discard-staged-changes-only...

-- 
Duy
Junio C Hamano· Mar 20, 2019, 01:50 UTC · re: Duy Nguyen · lore

Re: [PATCH] checkout.txt: note about losing staged changes with --merge

Duy Nguyen <pclouds@gmail.com> writes:
Show 5 quoted lines
> Kinda. But "--force --merge" makes no sense. --force discards all
> local changes by definition, which means you can't have conflicts and
> will not need --merge. I think this is the reason why we die() out
> when both are specified. So we need something like
> --discard-staged-changes-only...

At that point, I would have to say that we do not need anything. The use case is already covered with "git reset && git checkout -m", isn't it?

Thanks.
Elijah Newren· Mar 20, 2019, 13:53 UTC · re: Junio C Hamano · lore

Re: [PATCH] checkout.txt: note about losing staged changes with --merge

On Tue, Mar 19, 2019 at 7:50 PM Junio C Hamano <gitster@pobox.com> wrote:
Show 12 quoted lines
>
> Duy Nguyen <pclouds@gmail.com> writes:
>
> > Kinda. But "--force --merge" makes no sense. --force discards all
> > local changes by definition, which means you can't have conflicts and
> > will not need --merge. I think this is the reason why we die() out
> > when both are specified. So we need something like
> > --discard-staged-changes-only...
>
> At that point, I would have to say that we do not need anything.
> The use case is already covered with "git reset && git checkout -m",
> isn't it?

I guess the problem is just that 'git checkout -m' has not refused to run with either a dirty index or a dirty working tree, and if both are dirty (making us require more of a four-way merge), then our three-way merge has to have some kind of casualty in the implementation for at least some case. The current casualty as highlighted by Philip is that newly staged files before the 'checkout -m' become untracked and any carefully staged pieces before that command are lost amongst the unstaged changes again even if there weren't any conflicts.

One solution is to just accept and document or warn about this shortcoming for now as Duy did in his patch. Another is to do as you mentioned earlier in this thread when you stated 'I think "checkout -m <otherbranch>" with a dirty index should refuse to run'. Duy linked to a third option that I outlined in his commit message, though it'd require a bit more capability from merge-recursive than we have today.

So, I think we do need something (eventually at least). Would you prefer we dropped this patch from Duy and instead made 'checkout -m' abort when the index is dirty?

Duy Nguyen· Mar 20, 2019, 13:57 UTC · re: Elijah Newren · lore

Re: [PATCH] checkout.txt: note about losing staged changes with --merge

On Wed, Mar 20, 2019 at 8:53 PM Elijah Newren <newren@gmail.com> wrote:
> So, I think we do need something (eventually at least).  Would you
> prefer we dropped this patch from Duy and instead made 'checkout -m'
> abort when the index is dirty?

I have no problem with this. Still scratching my head wondering if t7201-co.sh has a slightly incorrect setup, or aborting is actually wrong. You're probably a better person to understand that test case ;-)

-- 
Duy
Elijah Newren· Mar 21, 2019, 13:46 UTC · re: Duy Nguyen · lore

Re: [PATCH] checkout.txt: note about losing staged changes with --merge

On Wed, Mar 20, 2019 at 7:58 AM Duy Nguyen <pclouds@gmail.com> wrote:
Show 10 quoted lines
>
> On Wed, Mar 20, 2019 at 8:53 PM Elijah Newren <newren@gmail.com> wrote:
> > So, I think we do need something (eventually at least).  Would you
> > prefer we dropped this patch from Duy and instead made 'checkout -m'
> > abort when the index is dirty?
>
> I have no problem with this. Still scratching my head wondering if
> t7201-co.sh has a slightly incorrect setup, or aborting is actually
> wrong. You're probably a better person to understand that test case
> ;-)

It doesn't surprise me at all that some testcases would fail with this change; it's a change of behavior from the previous implementation. However, taking a look at that testcase, it looks like it's not a simple change to make it do something similar because there's at least one other bug that we need to fix first. I'll dig in...though I really want to get my directory-rename-detection-defaults-to-reporting-conflict series updated and sent out first. Since Junio seems to be okay with just taking your doc update for now, hopefully that's not a problem. :-)

Junio C Hamano· Mar 21, 2019, 00:38 UTC · re: Elijah Newren · lore

Re: [PATCH] checkout.txt: note about losing staged changes with --merge

Elijah Newren <newren@gmail.com> writes:
Show 18 quoted lines
> On Tue, Mar 19, 2019 at 7:50 PM Junio C Hamano <gitster@pobox.com> wrote:
>>
>> Duy Nguyen <pclouds@gmail.com> writes:
>>
>> > Kinda. But "--force --merge" makes no sense. --force discards all
>> > local changes by definition, which means you can't have conflicts and
>> > will not need --merge. I think this is the reason why we die() out
>> > when both are specified. So we need something like
>> > --discard-staged-changes-only...
>>
>> At that point, I would have to say that we do not need anything.
>> The use case is already covered with "git reset && git checkout -m",
>> isn't it?
>
> I guess the problem is just that 'git checkout -m' has not refused to
> run with either a dirty index or a dirty working tree, and if both are
> dirty (making us require more of a four-way merge), then our three-way
> merge has to ...

I didn't actually mean "nothing to do here" relative to the current code; instead, I meant "nothing more than just stop when the index has updates" (which is hard to read from the above quoted part, as "Kinda." is a response in a discussion started with my "checkout -m should probably refuse to do anything when the index is dirty").

> So, I think we do need something (eventually at least).  Would you
> prefer we dropped this patch from Duy and instead made 'checkout -m'
> abort when the index is dirty?

Let's go with the doc update first, as the patch has already written. I think in the longer term, just aborting when the index is dirty would be a vast improvement over the status quo + a doc update and is a good place to stop.

Thanks.
Duy Nguyen· Mar 13, 2019, 11:05 UTC · re: Elijah Newren · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Tue, Mar 12, 2019 at 12:54 AM Elijah Newren <newren@gmail.com> wrote:
Show 10 quoted lines
> > +--progress::
> > +--no-progress::
> > +       Progress status is reported on the standard error stream
> > +       by default when it is attached to a terminal, unless `--quiet`
> > +       is specified. This flag enables progress reporting even if not
> > +       attached to a terminal, regardless of `--quiet`.
>
> This again makes me curious what --quiet actually supresses; in the
> case of branch switching, are there any non-warning informational
> messages other than progress reports that are printed?

One big thing git-checkout and git-switch will print when not --quiet is "git diff --name-status" to highlight local changes since we allow switching branches when the worktree is not clean. Should it be mentioned in --quiet description?

We could also occasionally print advice, branch tracking info, and one-liner summary like "Switched to (new) branch 'master'".

-- 
Duy
Elijah Newren· Mar 13, 2019, 14:36 UTC · re: Duy Nguyen · lore

Re: [PATCH v3 10/21] checkout: split part of it to new command 'switch'

On Wed, Mar 13, 2019 at 4:05 AM Duy Nguyen <pclouds@gmail.com> wrote:
Show 17 quoted lines
>
> On Tue, Mar 12, 2019 at 12:54 AM Elijah Newren <newren@gmail.com> wrote:
> > > +--progress::
> > > +--no-progress::
> > > +       Progress status is reported on the standard error stream
> > > +       by default when it is attached to a terminal, unless `--quiet`
> > > +       is specified. This flag enables progress reporting even if not
> > > +       attached to a terminal, regardless of `--quiet`.
> >
> > This again makes me curious what --quiet actually supresses; in the
> > case of branch switching, are there any non-warning informational
> > messages other than progress reports that are printed?
>
> One big thing git-checkout and git-switch will print when not --quiet
> is "git diff --name-status" to highlight local changes since we allow
> switching branches when the worktree is not clean. Should it be
> mentioned in --quiet description?

Nah, that all makes sense. It was more a curiosity (I tend to have a clean working tree most the time when switching branches, I guess; maybe I also filtered out messages I was used to seeing and couldn't remember seeing them) and also I had a small concern that perhaps this bit of documentation was relevant for one part of git-checkout and not the other, and thus shouldn't appear in both git-switch and git-restore.

> We could also occasionally print advice, branch tracking info, and
> one-liner summary like "Switched to (new) branch 'master'".

Thanks for the explanations. This all makes sense. However, all these answers are specific to git-switch; perhaps the detailed explanation of --progress can be shorted for git-restore? Or does it too have reasons to print non-progress informational messages?

Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 11/21] switch: better names for -b and -B

The shortcut of these options do not make much sense when used with
switch. And their descriptions are also tied to checkout. Move -b/-B
to cmd_checkout() and new -c/-C with the same functionality in
cmd_switch_branch()
---
 builtin/checkout.c | 32 +++++++++++++++++++++-----------
 1 file changed, 21 insertions(+), 11 deletions(-)
Show changes to builtin/checkout.c +21 −11
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 1eff10dbef..4c3f0f6ac7 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1368,14 +1368,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"),
@@ -1571,15 +1567,22 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
 	struct checkout_opts opts;
-	struct option *options = NULL;
+	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_END()
+	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
 
-	options = parse_options_dup(options);
+	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
-	options = add_switch_branch_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,
@@ -1592,14 +1595,21 @@ 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_END()
+	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
 
-	options = parse_options_dup(options);
+	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-	options = add_switch_branch_options(&opts, options);
+	options = add_common_switch_branch_options(&opts, options);
 
 	ret = checkout_main(argc, argv, prefix, &opts,
 			    options, switch_branch_usage);
-- 
2.21.0.rc1.337.gdf7f8d0522
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 12/21] switch: remove -l

This option is ancient. Nowadays reflog is enabled by default and
automatically created for new branches. Keep it in git-checkout only.
---
 builtin/checkout.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/checkout.c +1 −1
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 4c3f0f6ac7..a731f983c4 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1372,7 +1372,6 @@ static struct option *add_common_switch_branch_options(
 	struct checkout_opts *opts, struct option *prevopts)
 {
 	struct option options[] = {
-		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),
@@ -1573,6 +1572,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			   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;
-- 
2.21.0.rc1.337.gdf7f8d0522
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 13/21] switch: stop accepting pathspec

This command is about switching branch (or creating a new one) and
should not accept pathspec. This helps simplify ambiguation
handling. The other two ("git checkout" and "git restore") of
course do accept pathspec as before.
---
 builtin/checkout.c | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)
Show changes to builtin/checkout.c +15 −5
diff --git a/builtin/checkout.c b/builtin/checkout.c
index a731f983c4..1b1181b220 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -53,6 +53,7 @@ struct checkout_opts {
 	int count_checkout_paths;
 	int overlay_mode;
 	int no_dwim_new_local_branch;
+	int accept_pathspec;
 
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
@@ -1175,10 +1176,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;
 		}
@@ -1212,11 +1219,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) {
@@ -1261,7 +1269,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--;
@@ -1579,6 +1587,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
+	opts.accept_pathspec = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1606,6 +1615,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
+	opts.accept_pathspec = 0;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.21.0.rc1.337.gdf7f8d0522
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 14/21] switch: reject "do nothing" case

"git checkout" can be executed without any arguments. What it does is not exactly great: it switches from HEAD to HEAD and shows worktree modification as a side effect.

Make switch reject this case. Just use "git status" if you want that side effect. For switch, you have to either

- really switch a branch
- (explicitly) detach from the current branch
- create a new branch
---
 builtin/checkout.c | 9 +++++++++
 1 file changed, 9 insertions(+)
Show changes to builtin/checkout.c +9 −0
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 1b1181b220..f9f7ee2936 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -54,6 +54,7 @@ struct checkout_opts {
 	int overlay_mode;
 	int no_dwim_new_local_branch;
 	int accept_pathspec;
+	int switch_branch_doing_nothing_is_ok;
 
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
@@ -1334,6 +1335,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;
@@ -1587,6 +1594,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	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);
@@ -1616,6 +1624,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	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);
-- 
2.21.0.rc1.337.gdf7f8d0522
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 15/21] switch: only allow explicit detached HEAD

"git checkout <commit>" will checkout the commit in question and detach HEAD from the current branch. It is naturally a right thing to do once you get git references. But detached HEAD is a scary concept to new users because we show a lot of warnings and stuff, and it could be hard to get out of (until you know better).

To keep switch a bit more friendly to new users, we only allow
entering detached HEAD mode when --detach is given. "git
switch" must take a branch (unless you create a new branch,
then of course switch can take any commit-ish)
---
 builtin/checkout.c | 11 +++++++++++
 1 file changed, 11 insertions(+)
Show changes to builtin/checkout.c +11 −0
diff --git a/builtin/checkout.c b/builtin/checkout.c
index f9f7ee2936..2e150f0175 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -45,6 +45,7 @@ struct checkout_opts {
 	int merge;
 	int force;
 	int force_detach;
+	int implicit_detach;
 	int writeout_stage;
 	int overwrite_ignore;
 	int ignore_skipworktree;
@@ -1341,6 +1342,14 @@ static int checkout_branch(struct checkout_opts *opts,
 	    !opts->force_detach)
 		die(_("missing branch or commit argument"));
 
+	if (!opts->implicit_detach &&
+	    !opts->force_detach &&
+	    !opts->new_branch &&
+	    !opts->new_branch_force &&
+	    new_branch_info->name &&
+	    !new_branch_info->path)
+		die(_("a branch is expected, got %s"), new_branch_info->name);
+
 	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
 	    !opts->ignore_other_worktrees) {
 		int flag;
@@ -1596,6 +1605,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.no_dwim_new_local_branch = 0;
 	opts.switch_branch_doing_nothing_is_ok = 1;
 	opts.accept_pathspec = 1;
+	opts.implicit_detach = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1625,6 +1635,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	opts.no_dwim_new_local_branch = 0;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
+	opts.implicit_detach = 0;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.21.0.rc1.337.gdf7f8d0522
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 16/21] switch: add short option for --detach

"git checkout" automatically detaches branches and --detach is not
that useful (--no-detach is more likely). But for "switch", you
may want to use it more often once you're used to detached HEAD. This
of course adds -d to git-checkout but it does not harm (yet?) to do it.
---
 builtin/checkout.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/checkout.c +1 −1
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2e150f0175..0866aeba83 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1396,7 +1396,7 @@ static struct option *add_common_switch_branch_options(
 	struct checkout_opts *opts, struct option *prevopts)
 {
 	struct option options[] = {
-		OPT_BOOL(0, "detach", &opts->force_detach, N_("detach HEAD at named commit")),
+		OPT_BOOL('d', "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),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
-- 
2.21.0.rc1.337.gdf7f8d0522
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 17/21] switch: no implicit dwim, use --guess to dwim

Similar to automatic detach, this behavior could be confusing because it can sometimes create a new branch without a user asking it to, especially when the user is still not aware about this feature.

In the future, perhaps we could have a config key to disable these
safety nets and let 'switch' do automatic detach or dwim
again. But that will be opt-in after the user knows what is what. For
now give a short option if you want to use it often.
---
 Documentation/git-checkout.txt | 38 ++++++++++++++++++++--------------
 builtin/checkout.c             | 16 +++++++-------
 2 files changed, 31 insertions(+), 23 deletions(-)
Show changes to 2 files +31 −22

Documentation/git-checkout.txt, builtin/checkout.c

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index ac355dc3f3..2b776c1269 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -31,22 +31,13 @@ branch.
 	`<branch>`.
 +
 If `<branch>` is not found but there does exist a tracking branch in
-exactly one remote (call it `<remote>`) with a matching name, treat as
-equivalent to
+exactly one remote (call it `<remote>`) with a matching name and
+`--no-guess` is not specified, treat as equivalent to
 +
 ------------
 $ git checkout -b <branch> --track <remote>/<branch>
 ------------
 +
-If the branch exists in multiple remotes and one of them is named by
-the `checkout.defaultRemote` configuration variable, we'll use that
-one for the purposes of disambiguation, even if the `<branch>` isn't
-unique across all remotes. Set it to
-e.g. `checkout.defaultRemote=origin` to always checkout remote
-branches from there if `<branch>` is ambiguous but exists on the
-'origin' remote. See also `checkout.defaultRemote` in
-linkgit:git-config[1].
-+
 You could omit `<branch>`, in which case the command degenerates to
 "check out the current branch", which is a glorified no-op with
 rather expensive side-effects to show only the tracking information,
@@ -183,6 +174,27 @@ explicitly give a name with `-b` in such a case.
 	Do not set up "upstream" configuration, even if the
 	branch.autoSetupMerge configuration variable is true.
 
+--guess::
+--no-guess::
+	If `<branch>` is not found but there does exist a tracking
+	branch in exactly one remote (call it `<remote>`) with a
+	matching name, treat as equivalent to
++
+------------
+$ git checkout -b <branch> --track <remote>/<branch>
+------------
++
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to
+e.g. `checkout.defaultRemote=origin` to always checkout remote
+branches from there if `<branch>` is ambiguous but exists on the
+'origin' remote. See also `checkout.defaultRemote` in
+linkgit:git-config[1].
++
+Use `--no-guess` to disable this.
+
 -l::
 	Create the new branch's reflog; see linkgit:git-branch[1] for
 	details.
@@ -287,10 +299,6 @@ Note that this option uses the no overlay mode by default (see also
 	Just like linkgit:git-submodule[1], this will detach the
 	submodules HEAD.
 
---no-guess::
-	Do not attempt to create a branch if a remote tracking branch
-	of the same name exists.
-
 --overlay::
 --no-overlay::
 	In the default overlay mode, `git checkout` never
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 0866aeba83..8a89df4f36 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -53,7 +53,7 @@ struct checkout_opts {
 	int show_progress;
 	int count_checkout_paths;
 	int overlay_mode;
-	int no_dwim_new_local_branch;
+	int dwim_new_local_branch;
 	int accept_pathspec;
 	int switch_branch_doing_nothing_is_ok;
 
@@ -1403,8 +1403,6 @@ static struct option *add_common_switch_branch_options(
 		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
-			 N_("second guess 'git checkout <no-such-branch>'")),
 		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
 		OPT_END()
@@ -1441,7 +1439,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 {
 	struct branch_info new_branch_info;
 	int dwim_remotes_matched = 0;
-	int dwim_new_local_branch;
 
 	memset(&new_branch_info, 0, sizeof(new_branch_info));
 	opts->overwrite_ignore = 1;
@@ -1456,7 +1453,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 	argc = parse_options(argc, argv, prefix, options, usagestr,
 			     PARSE_OPT_KEEP_DASHDASH);
 
-	dwim_new_local_branch = !opts->no_dwim_new_local_branch;
 	if (opts->show_progress < 0) {
 		if (opts->quiet)
 			opts->show_progress = 0;
@@ -1516,7 +1512,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 		struct object_id rev;
 		int dwim_ok =
 			!opts->patch_mode &&
-			dwim_new_local_branch &&
+			opts->dwim_new_local_branch &&
 			opts->track == BRANCH_TRACK_UNSPECIFIED &&
 			!opts->new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
@@ -1597,12 +1593,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		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, "guess", &opts.dwim_new_local_branch,
+			 N_("second guess 'git checkout <no-such-branch>' (default)")),
 		OPT_END()
 	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
-	opts.no_dwim_new_local_branch = 0;
+	opts.dwim_new_local_branch = 1;
 	opts.switch_branch_doing_nothing_is_ok = 1;
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
@@ -1627,12 +1625,14 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 			   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('g', "guess", &opts.dwim_new_local_branch,
+			 N_("second guess 'git switch <no-such-branch>'")),
 		OPT_END()
 	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
-	opts.no_dwim_new_local_branch = 0;
+	opts.dwim_new_local_branch = 0;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
 	opts.implicit_detach = 0;
-- 
2.21.0.rc1.337.gdf7f8d0522
Eckhard Maaß· Mar 13, 2019, 18:36 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v3 17/21] switch: no implicit dwim, use --guess to dwim

On Fri, Mar 08, 2019 at 04:57:48PM +0700, Nguyễn Thái Ngọc Duy wrote:
Show 8 quoted lines
> Similar to automatic detach, this behavior could be confusing because
> it can sometimes create a new branch without a user asking it to,
> especially when the user is still not aware about this feature.
> 
> In the future, perhaps we could have a config key to disable these
> safety nets and let 'switch' do automatic detach or dwim
> again. But that will be opt-in after the user knows what is what. For
> now give a short option if you want to use it often.

As I am late to the patch series (sorry!), has there been already any discussion on that? In my experience, people get confused with detached HEAD state quite often, whereas the automatic creation of a local branch is no problem. So if it is deemed to be too confusing to dwim in this case, could we add a hint suggesting the command? Something like: "No suitable branch <foo> found, however there is a remote tracking branch <origin/foo> that you can siwtch and create with `git switch --guess foo`" (or maybe the one without guess)?

And while at it - what should happen, if:
- there is a tag named example
- no local branch example
- a branch at origin/example

... and we switch then? Right now it just gives "cannot find branch", should there be more information? Should it even create a branch example? With switch, switching a branch is unambiguous, even though there is a tag with that name. If I really want to --guess - should I be given more information?

Greetings, Eckhard

Eric Sunshine· Mar 15, 2019, 08:19 UTC · re: Eckhard Maaß · lore

Re: [PATCH v3 17/21] switch: no implicit dwim, use --guess to dwim

On Wed, Mar 13, 2019 at 2:36 PM Eckhard Maaß <eckhard.s.maass@googlemail.com> wrote:

Show 14 quoted lines
> On Fri, Mar 08, 2019 at 04:57:48PM +0700, Nguyễn Thái Ngọc Duy wrote:
> > Similar to automatic detach, this behavior could be confusing because
> > it can sometimes create a new branch without a user asking it to,
> > especially when the user is still not aware about this feature.
> >
> > In the future, perhaps we could have a config key to disable these
> > safety nets and let 'switch' do automatic detach or dwim
> > again. But that will be opt-in after the user knows what is what. For
> > now give a short option if you want to use it often.
>
> As I am late to the patch series (sorry!), has there been already any
> discussion on that? In my experience, people get confused with detached
> HEAD state quite often, whereas the automatic creation of a local branch
> is no problem.

This statement does a good job of articulating my (unspoken) response to this patch. Whereas a detached HEAD might be scary and confusing to newcomers, and difficult for them to recover from, automatic creation of a DWIM'd local branch doesn't seem so problematic (if at all).

With git-checkout, it's very easy to accidentally get into a detached HEAD state, so it makes some sense to protect newcomers, by default, from that accident in git-switch. However, auto-creation of a new local branch is not, for a couple reasons, nearly so weighty a matter. First, in many cases it may be less likely to happen since it requires presence of a corresponding remote tracking branch. Second, it's intuitively easy to recover from it: when git-switch reports that it created a new branch, though perhaps surprising, the user would naturally know to look for a command to "delete a branch".

And, unlike a detached HEAD, which newcomers may mistakenly believe lead to irretrievable loss of work, an unexpected branch creation carries no such penalty, perceived or real.

Show 5 quoted lines
> So if it is deemed to be too confusing to dwim in this
> case, could we add a hint suggesting the command? Something like:
> "No suitable branch <foo> found, however there is a remote tracking
> branch <origin/foo> that you can siwtch and create with `git switch
> --guess foo`" (or maybe the one without guess)?

That could be helpful if git-switch continues to make --no-guess the default, but making --guess the default (as it is in git-checkout) would likely be even better (for the reasons enumerated above).

Duy Nguyen· Mar 15, 2019, 09:29 UTC · re: Eric Sunshine · lore

Re: [PATCH v3 17/21] switch: no implicit dwim, use --guess to dwim

On Fri, Mar 15, 2019 at 3:19 PM Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 36 quoted lines
>
> On Wed, Mar 13, 2019 at 2:36 PM Eckhard Maaß
> <eckhard.s.maass@googlemail.com> wrote:
> > On Fri, Mar 08, 2019 at 04:57:48PM +0700, Nguyễn Thái Ngọc Duy wrote:
> > > Similar to automatic detach, this behavior could be confusing because
> > > it can sometimes create a new branch without a user asking it to,
> > > especially when the user is still not aware about this feature.
> > >
> > > In the future, perhaps we could have a config key to disable these
> > > safety nets and let 'switch' do automatic detach or dwim
> > > again. But that will be opt-in after the user knows what is what. For
> > > now give a short option if you want to use it often.
> >
> > As I am late to the patch series (sorry!), has there been already any
> > discussion on that? In my experience, people get confused with detached
> > HEAD state quite often, whereas the automatic creation of a local branch
> > is no problem.
>
> This statement does a good job of articulating my (unspoken) response
> to this patch. Whereas a detached HEAD might be scary and confusing to
> newcomers, and difficult for them to recover from, automatic creation
> of a DWIM'd local branch doesn't seem so problematic (if at all).
>
> With git-checkout, it's very easy to accidentally get into a detached
> HEAD state, so it makes some sense to protect newcomers, by default,
> from that accident in git-switch. However, auto-creation of a new
> local branch is not, for a couple reasons, nearly so weighty a matter.
> First, in many cases it may be less likely to happen since it requires
> presence of a corresponding remote tracking branch. Second, it's
> intuitively easy to recover from it: when git-switch reports that it
> created a new branch, though perhaps surprising, the user would
> naturally know to look for a command to "delete a branch".
>
> And, unlike a detached HEAD, which newcomers may mistakenly believe
> lead to irretrievable loss of work, an unexpected branch creation
> carries no such penalty, perceived or real.

I can't remember the last time it was discussed, but part of the reasons I chose to default --no-guess is because completion will be a lot less noisy.

But that's a very personal preference. I will switch to --guess as default no problem (unless someone jumps in and screams NOOO of course).

Please don't hold back when you find something not quite right. At least now I can fix it. Either that or by the time it's released, the Internet will blame me for adding yet another confusing git command :P

-- 
Duy
Junio C Hamano· Mar 18, 2019, 04:11 UTC · re: Eric Sunshine · lore

Re: [PATCH v3 17/21] switch: no implicit dwim, use --guess to dwim

Eric Sunshine <sunshine@sunshineco.com> writes:
Show 13 quoted lines
> With git-checkout, it's very easy to accidentally get into a detached
> HEAD state, so it makes some sense to protect newcomers, by default,
> from that accident in git-switch. However, auto-creation of a new
> local branch is not, for a couple reasons, nearly so weighty a matter.
> First, in many cases it may be less likely to happen since it requires
> presence of a corresponding remote tracking branch. Second, it's
> intuitively easy to recover from it: when git-switch reports that it
> created a new branch, though perhaps surprising, the user would
> naturally know to look for a command to "delete a branch".
>
> And, unlike a detached HEAD, which newcomers may mistakenly believe
> lead to irretrievable loss of work, an unexpected branch creation
> carries no such penalty, perceived or real.

I suspect that it actually cuts both ways. An auto-vivified local branch 'foo' that happens to have the same name as 'origin/foo' is still a different line of history UNLESS the user makes sure the divergence is reconciled with "push" and "pull" (possibly configured to "pull --rebase", but that is a secondary detail that depends on the workflow). Even when 'switch' reports that it created a new branch, a newcomer would probably not realize that it is "new", and different from the branch that the remote has.

Show 9 quoted lines
>> So if it is deemed to be too confusing to dwim in this
>> case, could we add a hint suggesting the command? Something like:
>> "No suitable branch <foo> found, however there is a remote tracking
>> branch <origin/foo> that you can siwtch and create with `git switch
>> --guess foo`" (or maybe the one without guess)?
>
> That could be helpful if git-switch continues to make --no-guess the
> default, but making --guess the default (as it is in git-checkout)
> would likely be even better (for the reasons enumerated above).

I do not have a huge issue with making "--guess" the default, but then the user may be entitled to help with an advice message when the command, especially with the implicit "--guess", auto-vivifies a new local branch based on a remote-tracking branch to stress that this is a new branch and different from the history the remote side has.

Duy Nguyen· Mar 16, 2019, 03:59 UTC · re: Eckhard Maaß · lore

Re: [PATCH v3 17/21] switch: no implicit dwim, use --guess to dwim

On Thu, Mar 14, 2019 at 1:36 AM Eckhard Maaß <eckhard.s.maass@googlemail.com> wrote:

Show 11 quoted lines
> And while at it - what should happen, if:
>
> - there is a tag named example
> - no local branch example
> - a branch at origin/example
>
> ... and we switch then? Right now it just gives "cannot find branch",
> should there be more information? Should it even create a branch
> example? With switch, switching a branch is unambiguous, even though
> there is a tag with that name. If I really want to --guess - should I be
> given more information?

There's checkout.defaultRemote that changes this behavior and it affects git-switch as well.

I like the idea of giving more information than just the terse (and technically correct) "cannot find branch". But I may have to delay implementing this a bit. There's a been a lot of updates in git-switch (which is great, don't get me wrong, I appreciate the feedback). I need to get v4 out for review soon then maybe handle the rest in v5 (and finally take a real hard look at git-restore comments).

-- 
Duy
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 18/21] switch: no worktree status unless real branch switch happens

When we switch from one branch to another, it makes sense to show a summary of local changes since there could be conflicts, or some files left modified.... When switch is used solely for creating a new branch (and "switch" to the same commit) or detaching, we don't really need to show anything.

"git checkout" does it anyway for historical reasons. But we can start with a clean slate with switch and don't have to.

This essentially reverts fa655d8411 (checkout: optimize "git checkout
-b <new_branch>" - 2018-08-16) and make it default for switch,
but also for -B and --detach. Users of big repos are encouraged to
move to switch.
---
 Documentation/config/checkout.txt |   8 --
 builtin/checkout.c                | 134 ++----------------------------
 t/t1090-sparse-checkout-scope.sh  |  14 ----
 3 files changed, 8 insertions(+), 148 deletions(-)
Show changes to 3 files +8 −148

Documentation/config/checkout.txt, builtin/checkout.c, t/t1090-sparse-checkout-scope.sh

diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt
index d6872ffa83..6b646813ab 100644
--- a/Documentation/config/checkout.txt
+++ b/Documentation/config/checkout.txt
@@ -16,11 +16,3 @@ will checkout the '<something>' branch on another remote,
 and by linkgit:git-worktree[1] when 'git worktree add' refers to a
 remote branch. This setting might be used for other checkout-like
 commands or functionality in the future.
-
-checkout.optimizeNewBranch::
-	Optimizes the performance of "git checkout -b <new_branch>" when
-	using sparse-checkout.  When set to true, git will not update the
-	repo based on the current sparse-checkout settings.  This means it
-	will not update the skip-worktree bit in the index nor add/remove
-	files in the working directory to reflect the current sparse checkout
-	settings nor will it show the local changes.
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 8a89df4f36..4903359b49 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -26,8 +26,6 @@
 #include "submodule.h"
 #include "advice.h"
 
-static int checkout_optimize_new_branch;
-
 static const char * const checkout_usage[] = {
 	N_("git checkout [<options>] <branch>"),
 	N_("git checkout [<options>] [<branch>] -- <file>..."),
@@ -56,11 +54,7 @@ struct checkout_opts {
 	int dwim_new_local_branch;
 	int accept_pathspec;
 	int switch_branch_doing_nothing_is_ok;
-
-	/*
-	 * If new checkout options are added, skip_merge_working_tree
-	 * should be updated accordingly.
-	 */
+	int only_merge_on_switching_branches;
 
 	const char *new_branch;
 	const char *new_branch_force;
@@ -564,112 +558,6 @@ static void setup_branch_path(struct branch_info *branch)
 	branch->path = strbuf_detach(&buf, NULL);
 }
 
-/*
- * Skip merging the trees, updating the index and working directory if and
- * only if we are creating a new branch via "git checkout -b <new_branch>."
- */
-static int skip_merge_working_tree(const struct checkout_opts *opts,
-	const struct branch_info *old_branch_info,
-	const struct branch_info *new_branch_info)
-{
-	/*
-	 * Do the merge if sparse checkout is on and the user has not opted in
-	 * to the optimized behavior
-	 */
-	if (core_apply_sparse_checkout && !checkout_optimize_new_branch)
-		return 0;
-
-	/*
-	 * We must do the merge if we are actually moving to a new commit.
-	 */
-	if (!old_branch_info->commit || !new_branch_info->commit ||
-		!oideq(&old_branch_info->commit->object.oid,
-		       &new_branch_info->commit->object.oid))
-		return 0;
-
-	/*
-	 * opts->patch_mode cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * opts->quiet only impacts output so doesn't require a merge
-	 */
-
-	/*
-	 * Honor the explicit request for a three-way merge or to throw away
-	 * local changes
-	 */
-	if (opts->merge || opts->force)
-		return 0;
-
-	/*
-	 * --detach is documented as "updating the index and the files in the
-	 * working tree" but this optimization skips those steps so fall through
-	 * to the regular code path.
-	 */
-	if (opts->force_detach)
-		return 0;
-
-	/*
-	 * opts->writeout_stage cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * Honor the explicit ignore requests
-	 */
-	if (!opts->overwrite_ignore || opts->ignore_skipworktree ||
-		opts->ignore_other_worktrees)
-		return 0;
-
-	/*
-	 * opts->show_progress only impacts output so doesn't require a merge
-	 */
-
-	/*
-	 * opts->overlay_mode cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * If we aren't creating a new branch any changes or updates will
-	 * happen in the existing branch.  Since that could only be updating
-	 * the index and working directory, we don't want to skip those steps
-	 * or we've defeated any purpose in running the command.
-	 */
-	if (!opts->new_branch)
-		return 0;
-
-	/*
-	 * new_branch_force is defined to "create/reset and checkout a branch"
-	 * so needs to go through the merge to do the reset
-	 */
-	if (opts->new_branch_force)
-		return 0;
-
-	/*
-	 * A new orphaned branch requrires the index and the working tree to be
-	 * adjusted to <start_point>
-	 */
-	if (opts->new_orphan_branch)
-		return 0;
-
-	/*
-	 * Remaining variables are not checkout options but used to track state
-	 */
-
-	 /*
-	  * Do the merge if this is the initial checkout. We cannot use
-	  * is_cache_unborn() here because the index hasn't been loaded yet
-	  * so cache_nr and timestamp.sec are always zero.
-	  */
-	if (!file_exists(get_index_file()))
-		return 0;
-
-	return 1;
-}
-
 static int merge_working_tree(const struct checkout_opts *opts,
 			      struct branch_info *old_branch_info,
 			      struct branch_info *new_branch_info,
@@ -1027,6 +915,7 @@ static int switch_branches(const struct checkout_opts *opts,
 	void *path_to_free;
 	struct object_id rev;
 	int flag, writeout_error = 0;
+	int do_merge = 1;
 
 	trace2_cmd_mode("branch");
 
@@ -1046,16 +935,12 @@ static int switch_branches(const struct checkout_opts *opts,
 		if (!new_branch_info->commit)
 			die(_("You are on a branch yet to be born"));
 		parse_commit_or_die(new_branch_info->commit);
+
+		if (opts->only_merge_on_switching_branches)
+			do_merge = 0;
 	}
 
-	/* optimize the "checkout -b <new_branch> path */
-	if (skip_merge_working_tree(opts, &old_branch_info, new_branch_info)) {
-		if (!checkout_optimize_new_branch && !opts->quiet) {
-			if (read_cache_preload(NULL) < 0)
-				return error(_("index file corrupt"));
-			show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
-		}
-	} else {
+	if (do_merge) {
 		ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
 		if (ret) {
 			free(path_to_free);
@@ -1075,11 +960,6 @@ static int switch_branches(const struct checkout_opts *opts,
 
 static int git_checkout_config(const char *var, const char *value, void *cb)
 {
-	if (!strcmp(var, "checkout.optimizenewbranch")) {
-		checkout_optimize_new_branch = git_config_bool(var, value);
-		return 0;
-	}
-
 	if (!strcmp(var, "diff.ignoresubmodules")) {
 		struct checkout_opts *opts = cb;
 		handle_ignore_submodules_arg(&opts->diff_options, value);
@@ -1602,6 +1482,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	memset(&opts, 0, sizeof(opts));
 	opts.dwim_new_local_branch = 1;
 	opts.switch_branch_doing_nothing_is_ok = 1;
+	opts.only_merge_on_switching_branches = 0;
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
 
@@ -1635,6 +1516,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	opts.dwim_new_local_branch = 0;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
+	opts.only_merge_on_switching_branches = 1;
 	opts.implicit_detach = 0;
 
 	options = parse_options_dup(switch_options);
diff --git a/t/t1090-sparse-checkout-scope.sh b/t/t1090-sparse-checkout-scope.sh
index 090b7fc3d3..40cc004326 100755
--- a/t/t1090-sparse-checkout-scope.sh
+++ b/t/t1090-sparse-checkout-scope.sh
@@ -31,20 +31,6 @@ test_expect_success 'perform sparse checkout of master' '
 	test_path_is_file c
 '
 
-test_expect_success 'checkout -b checkout.optimizeNewBranch interaction' '
-	cp .git/info/sparse-checkout .git/info/sparse-checkout.bak &&
-	test_when_finished "
-		mv -f .git/info/sparse-checkout.bak .git/info/sparse-checkout
-		git checkout master
-	" &&
-	echo "/b" >>.git/info/sparse-checkout &&
-	test "$(git ls-files -t b)" = "S b" &&
-	git -c checkout.optimizeNewBranch=true checkout -b fast &&
-	test "$(git ls-files -t b)" = "S b" &&
-	git checkout -b slow &&
-	test "$(git ls-files -t b)" = "H b"
-'
-
 test_expect_success 'merge feature branch into sparse checkout of master' '
 	git merge feature &&
 	test_path_is_file a &&
-- 
2.21.0.rc1.337.gdf7f8d0522
Eric Sunshine· Mar 10, 2019, 03:43 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v3 18/21] switch: no worktree status unless real branch switch happens

On Fri, Mar 8, 2019 at 4:59 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 13 quoted lines
> When we switch from one branch to another, it makes sense to show a
> summary of local changes since there could be conflicts, or some files
> left modified.... When switch is used solely for creating a new
> branch (and "switch" to the same commit) or detaching, we don't really
> need to show anything.
>
> "git checkout" does it anyway for historical reasons. But we can start
> with a clean slate with switch and don't have to.
>
> This essentially reverts fa655d8411 (checkout: optimize "git checkout
> -b <new_branch>" - 2018-08-16) and make it default for switch,
> but also for -B and --detach. Users of big repos are encouraged to
> move to switch.

I like this last bit. The skip_merge_working_tree() function which this removes was ugly, difficult to maintain, and difficult to get just right (and easy to break -- even by changing parts of the system which one might not expect to impact it).

Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 19/21] t: add tests for switch

---
 t/t2060-switch.sh | 87 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 87 insertions(+)
 create mode 100755 t/t2060-switch.sh
Show changes to t/t2060-switch.sh +87 −0
diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
new file mode 100755
index 0000000000..1e1e834c1b
--- /dev/null
+++ b/t/t2060-switch.sh
@@ -0,0 +1,87 @@
+#!/bin/sh
+
+test_description='switch basic functionality'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit first &&
+	git branch first-branch &&
+	test_commit second &&
+	test_commit third &&
+	git remote add origin nohost:/nopath &&
+	git update-ref refs/remotes/origin/foo first-branch
+'
+
+test_expect_success 'switch branch no arguments' '
+	test_must_fail git switch
+'
+
+test_expect_success 'switch branch' '
+	git switch first-branch &&
+	test_path_is_missing second.t
+'
+
+test_expect_success 'switch to a commit' '
+	test_must_fail git switch master^{commit}
+'
+
+test_expect_success 'switch and detach' '
+	test_when_finished git switch master &&
+	git switch --detach master^{commit} &&
+	test_must_fail git symbolic-ref HEAD
+'
+
+test_expect_success 'switch and detach current branch' '
+	test_when_finished git switch master &&
+	git switch master &&
+	git switch --detach &&
+	test_must_fail git symbolic-ref HEAD
+'
+
+test_expect_success 'switch and create branch' '
+	test_when_finished git switch master &&
+	git switch -c temp master^ &&
+	test_cmp_rev master^ refs/heads/temp &&
+	echo refs/heads/temp >expected-branch &&
+	git symbolic-ref HEAD >actual-branch &&
+	test_cmp expected-branch actual-branch
+'
+
+test_expect_success 'force create branch from HEAD' '
+	test_when_finished git switch master &&
+	git switch --detach master &&
+	git switch -C temp &&
+	test_cmp_rev master refs/heads/temp &&
+	echo refs/heads/temp >expected-branch &&
+	git symbolic-ref HEAD >actual-branch &&
+	test_cmp expected-branch actual-branch
+'
+
+test_expect_success 'new orphan branch' '
+	test_when_finished git switch master &&
+	git switch --orphan new-orphan master^ &&
+	test_commit orphan &&
+	git cat-file commit refs/heads/new-orphan >commit &&
+	! grep ^parent commit
+'
+
+test_expect_success 'switching ignores file of same branch name' '
+	test_when_finished git switch master &&
+	: >first-branch &&
+	git switch first-branch &&
+	echo refs/heads/first-branch >expected &&
+	git symbolic-ref HEAD >actual &&
+	test_commit expected actual
+'
+
+test_expect_success 'guess and create branch ' '
+	test_when_finished git switch master &&
+	test_must_fail git switch foo &&
+	git switch --guess foo &&
+	echo refs/heads/foo >expected &&
+	git symbolic-ref HEAD >actual &&
+	test_cmp expected actual
+'
+
+test_done
-- 
2.21.0.rc1.337.gdf7f8d0522
Eric Sunshine· Mar 10, 2019, 03:59 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v3 19/21] t: add tests for switch

On Fri, Mar 8, 2019 at 4:59 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 5 quoted lines
> diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
> @@ -0,0 +1,87 @@
> +test_expect_success 'switch to a commit' '
> +       test_must_fail git switch master^{commit}
> +'

The title of this test was a bit misleading for me; I would have found it easier to understand what's being tested had it said "switch without --detach" or something.

Show 5 quoted lines
> +test_expect_success 'switch and detach' '
> +       test_when_finished git switch master &&
> +       git switch --detach master^{commit} &&
> +       test_must_fail git symbolic-ref HEAD
> +'

In fact, if the two tests were combined, it would have been even clearer (for me):

    test_expect_success 'switch and detach' '
        test_when_finished git switch master &&
        test_must_fail git switch master^{commit} &&
        git switch --detach master^{commit} &&
        test_must_fail git symbolic-ref HEAD
    '
Not worth a re-roll.
Show 18 quoted lines
> +test_expect_success 'switch and create branch' '
> +       test_when_finished git switch master &&
> +       git switch -c temp master^ &&
> +       test_cmp_rev master^ refs/heads/temp &&
> +       echo refs/heads/temp >expected-branch &&
> +       git symbolic-ref HEAD >actual-branch &&
> +       test_cmp expected-branch actual-branch
> +'
> +
> +test_expect_success 'force create branch from HEAD' '
> +       test_when_finished git switch master &&
> +       git switch --detach master &&
> +       git switch -C temp &&
> +       test_cmp_rev master refs/heads/temp &&
> +       echo refs/heads/temp >expected-branch &&
> +       git symbolic-ref HEAD >actual-branch &&
> +       test_cmp expected-branch actual-branch
> +'

Maybe also demonstrate that -C is actually needed here by leading in with a failing -c:

    ...
    git switch --detach master &&
    test_must_fail git switch -c temp &&
    git switch -C temp &&
    ...
Not worth a re-roll.
Show 8 quoted lines
> +test_expect_success 'guess and create branch ' '
> +       test_when_finished git switch master &&
> +       test_must_fail git switch foo &&
> +       git switch --guess foo &&
> +       echo refs/heads/foo >expected &&
> +       git symbolic-ref HEAD >actual &&
> +       test_cmp expected actual
> +'

The above suggestions about --detach/-C reflect how you did it in this test, in which you first try "git switch foo" without the --guess option, expecting it to fail, and then repeat with the option, expecting it to succeed.

Andrei Rybak· Mar 10, 2019, 10:09 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v3 19/21] t: add tests for switch

On 3/8/19 10:57 AM, Nguyễn Thái Ngọc Duy wrote:
Show 13 quoted lines
> ---
>  t/t2060-switch.sh | 87 +++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 87 insertions(+)
>  create mode 100755 t/t2060-switch.sh
> 
> diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
> new file mode 100755
> index 0000000000..1e1e834c1b
> --- /dev/null
> +++ b/t/t2060-switch.sh
> @@ -0,0 +1,87 @@
> +#!/bin/sh
> +
[snip]
Show 8 quoted lines
> +
> +test_expect_success 'switching ignores file of same branch name' '
> +	test_when_finished git switch master &&
> +	: >first-branch &&
> +	git switch first-branch &&
> +	echo refs/heads/first-branch >expected &&
> +	git symbolic-ref HEAD >actual &&
> +	test_commit expected actual
s/commit/cmp/
Show 13 quoted lines
> +'
> +
> +test_expect_success 'guess and create branch ' '
> +	test_when_finished git switch master &&
> +	test_must_fail git switch foo &&
> +	git switch --guess foo &&
> +	echo refs/heads/foo >expected &&
> +	git symbolic-ref HEAD >actual &&
> +	test_cmp expected actual
> +'
> +
> +test_done
> 
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 20/21] completion: support switch

Completion support for --guess could be made better. If no --detach is
given, we should only provide a list of refs/heads/* and dwim ones,
not the entire ref space. But I still can't penetrate that
__git_refs() function yet.
---
 contrib/completion/git-completion.bash | 27 ++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)
Show changes to contrib/completion/git-completion.bash +27 −0
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 976e4a6548..7fcf28d437 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -2158,6 +2158,33 @@ _git_status ()
 	__git_complete_index_file "$complete_opt"
 }
 
+_git_switch ()
+{
+	case "$cur" in
+	--conflict=*)
+		__gitcomp "diff3 merge" "" "${cur##--conflict=}"
+		;;
+	--*)
+		__gitcomp_builtin switch
+		;;
+	*)
+		# check if ---guess was specified to enable DWIM mode
+		local track_opt= only_local_ref=n
+		if [ -n "$(__git_find_on_cmdline "-g --guess")" ]; then
+			track_opt='--track'
+		fi
+		if [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then
+			only_local_ref=y
+		fi
+		if [ $only_local_ref = y -a -n "$track_opt"]; then
+			__gitcomp_direct "$(__git_heads "" "$cur" " ")"
+		else
+			__git_complete_refs $track_opt
+		fi
+		;;
+	esac
+}
+
 __git_config_get_set_variables ()
 {
 	local prevword word config_file= c=$cword
-- 
2.21.0.rc1.337.gdf7f8d0522
Nguyễn Thái Ngọc Duy· Mar 8, 2019, 09:57 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v3 21/21] doc: promote "git switch"

The new command "git switch" is added to avoid the confusion of one-command-do-all "git checkout" for new users. They are also helpful to avoid ambiguation context.

For these reasons, promote it everywhere possible. This includes
documentation, suggestions/advice from other commands...
---
 Documentation/git-branch.txt           | 12 +++---
 Documentation/git-check-ref-format.txt |  3 +-
 Documentation/git-format-patch.txt     |  2 +-
 Documentation/git-merge-base.txt       |  2 +-
 Documentation/git-rebase.txt           |  2 +-
 Documentation/git-remote.txt           |  2 +-
 Documentation/git-rerere.txt           | 10 ++---
 Documentation/git-reset.txt            | 20 ++++-----
 Documentation/git-stash.txt            |  9 +++--
 Documentation/gitcore-tutorial.txt     | 19 +++++----
 Documentation/giteveryday.txt          | 24 +++++------
 Documentation/gittutorial.txt          |  4 +-
 Documentation/gitworkflows.txt         |  3 +-
 Documentation/revisions.txt            |  2 +-
 Documentation/user-manual.txt          | 56 +++++++++++++-------------
 advice.c                               | 11 +++--
 sha1-name.c                            |  2 +-
 t/t2020-checkout-detach.sh             | 16 ++++----
 18 files changed, 101 insertions(+), 98 deletions(-)
Show changes to 18 files +101 −98

Documentation/git-branch.txt, Documentation/git-check-ref-format.txt, Documentation/git-format-patch.txt, Documentation/git-merge-base.txt, Documentation/git-rebase.txt, Documentation/git-remote.txt, Documentation/git-rerere.txt, Documentation/git-reset.txt, Documentation/git-stash.txt, Documentation/gitcore-tutorial.txt, Documentation/giteveryday.txt, Documentation/gittutorial.txt, Documentation/gitworkflows.txt, Documentation/revisions.txt, Documentation/user-manual.txt, advice.c, sha1-name.c, t/t2020-checkout-detach.sh

diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 0cd87ddeff..1e2d89b174 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -48,7 +48,7 @@ The command's second form creates a new branch head named <branchname>
 which points to the current `HEAD`, or <start-point> if given.
 
 Note that this will create the new branch, but it will not switch the
-working tree to it; use "git checkout <newbranch>" to switch to the
+working tree to it; use "git switch <newbranch>" to switch to the
 new branch.
 
 When a local branch is started off a remote-tracking branch, Git sets up the
@@ -198,7 +198,7 @@ This option is only applicable in non-verbose mode.
 +
 This behavior is the default when the start point is a remote-tracking branch.
 Set the branch.autoSetupMerge configuration variable to `false` if you
-want `git checkout` and `git branch` to always behave as if `--no-track`
+want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
 were given. Set it to `always` if you want this behavior when the
 start-point is either a local or remote-tracking branch.
 
@@ -297,7 +297,7 @@ Start development from a known tag::
 $ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6
 $ cd my2.6
 $ git branch my2.6.14 v2.6.14   <1>
-$ git checkout my2.6.14
+$ git switch my2.6.14
 ------------
 +
 <1> This step and the next one could be combined into a single step with
@@ -322,9 +322,9 @@ $ git branch -D test                                    <2>
 NOTES
 -----
 
-If you are creating a branch that you want to checkout immediately, it is
-easier to use the git checkout command with its `-b` option to create
-a branch and check it out with a single command.
+If you are creating a branch that you want to switch to immediately,
+it is easier to use the "git switch" command with its `-c` option to
+do the same thing with a single command.
 
 The options `--contains`, `--no-contains`, `--merged` and `--no-merged`
 serve four related but different purposes:
diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt
index d9de992585..ee6a4144fb 100644
--- a/Documentation/git-check-ref-format.txt
+++ b/Documentation/git-check-ref-format.txt
@@ -88,7 +88,8 @@ but it is explicitly forbidden at the beginning of a branch name).
 When run with `--branch` option in a repository, the input is first
 expanded for the ``previous checkout syntax''
 `@{-n}`.  For example, `@{-1}` is a way to refer the last thing that
-was checked out using "git checkout" operation. This option should be
+was checked out using "git switch" or "git checkout" operation.
+This option should be
 used by porcelains to accept this syntax anywhere a branch name is
 expected, so they can act as if you typed the branch name. As an
 exception note that, the ``previous checkout operation'' might result
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 1af85d404f..0a24a5679e 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -421,7 +421,7 @@ One way to test if your MUA is set up correctly is:
 * Apply it:
 
     $ git fetch <project> master:test-apply
-    $ git checkout test-apply
+    $ git switch test-apply
     $ git reset --hard
     $ git am a.patch
 
diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt
index 9f07f4f6ed..261d5c1164 100644
--- a/Documentation/git-merge-base.txt
+++ b/Documentation/git-merge-base.txt
@@ -149,7 +149,7 @@ instead.
 Discussion on fork-point mode
 -----------------------------
 
-After working on the `topic` branch created with `git checkout -b
+After working on the `topic` branch created with `git switch -c
 topic origin/master`, the history of remote-tracking branch
 `origin/master` may have been rewound and rebuilt, leading to a
 history of this shape:
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 5629ba4c5d..cb6fc166e2 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -17,7 +17,7 @@ SYNOPSIS
 DESCRIPTION
 -----------
 If <branch> is specified, 'git rebase' will perform an automatic
-`git checkout <branch>` before doing anything else.  Otherwise
+`git switch <branch>` before doing anything else.  Otherwise
 it remains on the current branch.
 
 If <upstream> is not specified, the upstream configured in
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index 0cad37fb81..9659abbf8e 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -230,7 +230,7 @@ $ git branch -r
   staging/master
   staging/staging-linus
   staging/staging-next
-$ git checkout -b staging staging/master
+$ git switch -c staging staging/master
 ...
 ------------
 
diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt
index df310d2a58..fe4434ad9d 100644
--- a/Documentation/git-rerere.txt
+++ b/Documentation/git-rerere.txt
@@ -91,7 +91,7 @@ For such a test, you need to merge master and topic somehow.
 One way to do it is to pull master into the topic branch:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 
               o---*---o---+ topic
@@ -113,10 +113,10 @@ the upstream might have been advanced since the test merge `+`,
 in which case the final commit graph would look like this:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 	$ ... work on both topic and master branches
-	$ git checkout master
+	$ git switch master
 	$ git merge topic
 
               o---*---o---+---o---o topic
@@ -136,11 +136,11 @@ merges, you could blow away the test merge, and keep building on
 top of the tip before the test merge:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 	$ git reset --hard HEAD^ ;# rewind the test merge
 	$ ... work on both topic and master branches
-	$ git checkout master
+	$ git switch master
 	$ git merge topic
 
               o---*---o-------o---o topic
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 132f8e55f6..cbf901efb4 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -149,9 +149,9 @@ See also the `--amend` option to linkgit:git-commit[1].
 Undo a commit, making it a topic branch::
 +
 ------------
-$ git branch topic/wip     <1>
-$ git reset --hard HEAD~3  <2>
-$ git checkout topic/wip   <3>
+$ git branch topic/wip          <1>
+$ git reset --hard HEAD~3       <2>
+$ git switch topic/wip          <3>
 ------------
 +
 <1> You have made some commits, but realize they were premature
@@ -232,13 +232,13 @@ working tree are not in any shape to be committed yet, but you
 need to get to the other branch for a quick bugfix.
 +
 ------------
-$ git checkout feature ;# you were working in "feature" branch and
-$ work work work       ;# got interrupted
+$ git switch feature  ;# you were working in "feature" branch and
+$ work work work      ;# got interrupted
 $ git commit -a -m "snapshot WIP"                 <1>
-$ git checkout master
+$ git switch master
 $ fix fix fix
 $ git commit ;# commit with real log
-$ git checkout feature
+$ git switch feature
 $ git reset --soft HEAD^ ;# go back to WIP state  <2>
 $ git reset                                       <3>
 ------------
@@ -279,18 +279,18 @@ reset it while keeping the changes in your working tree.
 +
 ------------
 $ git tag start
-$ git checkout -b branch1
+$ git switch -c branch1
 $ edit
 $ git commit ...                            <1>
 $ edit
-$ git checkout -b branch2                   <2>
+$ git switch -c branch2                     <2>
 $ git reset --keep start                    <3>
 ------------
 +
 <1> This commits your first edits in `branch1`.
 <2> In the ideal world, you could have realized that the earlier
     commit did not belong to the new topic when you created and switched
-    to `branch2` (i.e. `git checkout -b branch2 start`), but nobody is
+    to `branch2` (i.e. `git switch -c branch2 start`), but nobody is
     perfect.
 <3> But you can use `reset --keep` to remove the unwanted commit after
     you switched to `branch2`.
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 7ef8c47911..ebb6282db3 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -235,12 +235,12 @@ return to your original branch to make the emergency fix, like this:
 +
 ----------------------------------------------------------------
 # ... hack hack hack ...
-$ git checkout -b my_wip
+$ git switch -c my_wip
 $ git commit -a -m "WIP"
-$ git checkout master
+$ git switch master
 $ edit emergency fix
 $ git commit -a -m "Fix in a hurry"
-$ git checkout my_wip
+$ git switch my_wip
 $ git reset --soft HEAD^
 # ... continue hacking ...
 ----------------------------------------------------------------
@@ -293,7 +293,8 @@ SEE ALSO
 linkgit:git-checkout[1],
 linkgit:git-commit[1],
 linkgit:git-reflog[1],
-linkgit:git-reset[1]
+linkgit:git-reset[1],
+linkgit:git-switch[1]
 
 GIT
 ---
diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt
index e29a9effcc..f880d21dfb 100644
--- a/Documentation/gitcore-tutorial.txt
+++ b/Documentation/gitcore-tutorial.txt
@@ -741,7 +741,7 @@ used earlier, and create a branch in it. You do that by simply just
 saying that you want to check out a new branch:
 
 ------------
-$ git checkout -b mybranch
+$ git switch -c mybranch
 ------------
 
 will create a new branch based at the current `HEAD` position, and switch
@@ -755,7 +755,7 @@ just telling 'git checkout' what the base of the checkout would be.
 In other words, if you have an earlier tag or branch, you'd just do
 
 ------------
-$ git checkout -b mybranch earlier-commit
+$ git switch -c mybranch earlier-commit
 ------------
 
 and it would create the new branch `mybranch` at the earlier commit,
@@ -765,7 +765,7 @@ and check out the state at that time.
 You can always just jump back to your original `master` branch by doing
 
 ------------
-$ git checkout master
+$ git switch master
 ------------
 
 (or any other branch-name, for that matter) and if you forget which
@@ -794,7 +794,7 @@ $ git branch <branchname> [startingpoint]
 
 which will simply _create_ the branch, but will not do anything further.
 You can then later -- once you decide that you want to actually develop
-on that branch -- switch to that branch with a regular 'git checkout'
+on that branch -- switch to that branch with a regular 'git switch'
 with the branchname as the argument.
 
 
@@ -808,7 +808,7 @@ being the same as the original `master` branch, let's make sure we're in
 that branch, and do some work there.
 
 ------------------------------------------------
-$ git checkout mybranch
+$ git switch mybranch
 $ echo "Work, work, work" >>hello
 $ git commit -m "Some work." -i hello
 ------------------------------------------------
@@ -825,7 +825,7 @@ does some work in the original branch, and simulate that by going back
 to the master branch, and editing the same file differently there:
 
 ------------
-$ git checkout master
+$ git switch master
 ------------
 
 Here, take a moment to look at the contents of `hello`, and notice how they
@@ -958,7 +958,7 @@ to the `master` branch. Let's go back to `mybranch`, and run
 'git merge' to get the "upstream changes" back to your branch.
 
 ------------
-$ git checkout mybranch
+$ git switch mybranch
 $ git merge -m "Merge upstream changes." master
 ------------
 
@@ -1133,9 +1133,8 @@ Remember, before running 'git merge', our `master` head was at
 work." commit.
 
 ------------
-$ git checkout mybranch
-$ git reset --hard master^2
-$ git checkout master
+$ git switch -C mybranch master^2
+$ git switch master
 $ git reset --hard master^
 ------------
 
diff --git a/Documentation/giteveryday.txt b/Documentation/giteveryday.txt
index 9f2528fc8c..ad455f3e39 100644
--- a/Documentation/giteveryday.txt
+++ b/Documentation/giteveryday.txt
@@ -41,7 +41,7 @@ following commands.
 
   * linkgit:git-log[1] to see what happened.
 
-  * linkgit:git-checkout[1] and linkgit:git-branch[1] to switch
+  * linkgit:git-switch[1] and linkgit:git-branch[1] to switch
     branches.
 
   * linkgit:git-add[1] to manage the index file.
@@ -80,7 +80,7 @@ $ git tag v2.43 <2>
 Create a topic branch and develop.::
 +
 ------------
-$ git checkout -b alsa-audio <1>
+$ git switch -c alsa-audio <1>
 $ edit/compile/test
 $ git checkout -- curses/ux_audio_oss.c <2>
 $ git add curses/ux_audio_alsa.c <3>
@@ -90,7 +90,7 @@ $ git commit -a -s <5>
 $ edit/compile/test
 $ git diff HEAD^ <6>
 $ git commit -a --amend <7>
-$ git checkout master <8>
+$ git switch master <8>
 $ git merge alsa-audio <9>
 $ git log --since='3 days ago' <10>
 $ git log v2.43.. curses/ <11>
@@ -148,11 +148,11 @@ Clone the upstream and work on it.  Feed changes to upstream.::
 ------------
 $ git clone git://git.kernel.org/pub/scm/.../torvalds/linux-2.6 my2.6
 $ cd my2.6
-$ git checkout -b mine master <1>
+$ git switch -c mine master <1>
 $ edit/compile/test; git commit -a -s <2>
 $ git format-patch master <3>
 $ git send-email --to="person <email@example.com>" 00*.patch <4>
-$ git checkout master <5>
+$ git switch master <5>
 $ git pull <6>
 $ git log -p ORIG_HEAD.. arch/i386 include/asm-i386 <7>
 $ git ls-remote --heads http://git.kernel.org/.../jgarzik/libata-dev.git <8>
@@ -194,7 +194,7 @@ satellite$ edit/compile/test/commit
 satellite$ git push origin <4>
 
 mothership$ cd frotz
-mothership$ git checkout master
+mothership$ git switch master
 mothership$ git merge satellite/master <5>
 ------------
 +
@@ -216,7 +216,7 @@ machine into the master branch.
 Branch off of a specific tag.::
 +
 ------------
-$ git checkout -b private2.6.14 v2.6.14 <1>
+$ git switch -c private2.6.14 v2.6.14 <1>
 $ edit/compile/test; git commit -a
 $ git checkout master
 $ git cherry-pick v2.6.14..private2.6.14 <2>
@@ -274,14 +274,14 @@ $ mailx <3>
 & s 2 3 4 5 ./+to-apply
 & s 7 8 ./+hold-linus
 & q
-$ git checkout -b topic/one master
+$ git switch -c topic/one master
 $ git am -3 -i -s ./+to-apply <4>
 $ compile/test
-$ git checkout -b hold/linus && git am -3 -i -s ./+hold-linus <5>
-$ git checkout topic/one && git rebase master <6>
-$ git checkout pu && git reset --hard next <7>
+$ git switch -c hold/linus && git am -3 -i -s ./+hold-linus <5>
+$ git switch topic/one && git rebase master <6>
+$ git switch -C pu next <7>
 $ git merge topic/one topic/two && git merge hold/linus <8>
-$ git checkout maint
+$ git switch maint
 $ git cherry-pick master~4 <9>
 $ compile/test
 $ git tag -s -m "GIT 0.99.9x" v0.99.9x <10>
diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt
index 242de31cb6..e6ad6b5f8d 100644
--- a/Documentation/gittutorial.txt
+++ b/Documentation/gittutorial.txt
@@ -207,7 +207,7 @@ automatically.  The asterisk marks the branch you are currently on;
 type
 
 ------------------------------------------------
-$ git checkout experimental
+$ git switch experimental
 ------------------------------------------------
 
 to switch to the experimental branch.  Now edit a file, commit the
@@ -216,7 +216,7 @@ change, and switch back to the master branch:
 ------------------------------------------------
 (edit file)
 $ git commit -a
-$ git checkout master
+$ git switch master
 ------------------------------------------------
 
 Check that the change you made is no longer visible, since it was
diff --git a/Documentation/gitworkflows.txt b/Documentation/gitworkflows.txt
index ca11c7bdaf..abc0dc6bc7 100644
--- a/Documentation/gitworkflows.txt
+++ b/Documentation/gitworkflows.txt
@@ -301,8 +301,7 @@ topics on 'next':
 .Rewind and rebuild next
 [caption="Recipe: "]
 =====================================
-* `git checkout next`
-* `git reset --hard master`
+* `git switch -C next master`
 * `git merge ai/topic_in_next1`
 * `git merge ai/topic_in_next2`
 * ...
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
index 72daa20e76..a1c7a65da6 100644
--- a/Documentation/revisions.txt
+++ b/Documentation/revisions.txt
@@ -115,7 +115,7 @@ Here's an example to make it more clear:
 ------------------------------
 $ git config push.default current
 $ git config remote.pushdefault myfork
-$ git checkout -b mybranch origin/master
+$ git switch -c mybranch origin/master
 
 $ git rev-parse --symbolic-full-name @{upstream}
 refs/remotes/origin/master
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index eff7890274..94799faa2b 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -122,10 +122,10 @@ Tags are expected to always point at the same version of a project,
 while heads are expected to advance as development progresses.
 
 Create a new branch head pointing to one of these versions and check it
-out using linkgit:git-checkout[1]:
+out using linkgit:git-switch[1]:
 
 ------------------------------------------------
-$ git checkout -b new v2.6.13
+$ git switch -c new v2.6.13
 ------------------------------------------------
 
 The working directory then reflects the contents that the project had
@@ -282,10 +282,10 @@ a summary of the commands:
 	this command will fail with a warning.
 `git branch -D <branch>`::
 	delete the branch `<branch>` irrespective of its merged status.
-`git checkout <branch>`::
+`git switch <branch>`::
 	make the current branch `<branch>`, updating the working
 	directory to reflect the version referenced by `<branch>`.
-`git checkout -b <new> <start-point>`::
+`git switch -c <new> <start-point>`::
 	create a new branch `<new>` referencing `<start-point>`, and
 	check it out.
 
@@ -302,22 +302,22 @@ ref: refs/heads/master
 Examining an old version without creating a new branch
 ------------------------------------------------------
 
-The `git checkout` command normally expects a branch head, but will also
-accept an arbitrary commit; for example, you can check out the commit
-referenced by a tag:
+The `git switch` command normally expects a branch head, but will also
+accept an arbitrary commit when invoked with --detach; for example,
+you can check out the commit referenced by a tag:
 
 ------------------------------------------------
-$ git checkout v2.6.17
+$ git switch --detach v2.6.17
 Note: checking out 'v2.6.17'.
 
 You are in 'detached HEAD' state. You can look around, make experimental
 changes and commit them, and you can discard any commits you make in this
-state without impacting any branches by performing another checkout.
+state without impacting any branches by performing another switch.
 
 If you want to create a new branch to retain commits you create, you may
-do so (now or later) by using -b with the checkout command again. Example:
+do so (now or later) by using -c with the switch command again. Example:
 
-  git checkout -b new_branch_name
+  git switch -c new_branch_name
 
 HEAD is now at 427abfa Linux v2.6.17
 ------------------------------------------------
@@ -373,7 +373,7 @@ You might want to build on one of these remote-tracking branches
 on a branch of your own, just as you would for a tag:
 
 ------------------------------------------------
-$ git checkout -b my-todo-copy origin/todo
+$ git switch -c my-todo-copy origin/todo
 ------------------------------------------------
 
 You can also check out `origin/todo` directly to examine it or
@@ -2211,8 +2211,8 @@ $ git branch --track release origin/master
 These can be easily kept up to date using linkgit:git-pull[1].
 
 -------------------------------------------------
-$ git checkout test && git pull
-$ git checkout release && git pull
+$ git switch test && git pull
+$ git switch release && git pull
 -------------------------------------------------
 
 Important note!  If you have any local changes in these branches, then
@@ -2264,7 +2264,7 @@ tested changes
 2) help future bug hunters that use `git bisect` to find problems
 
 -------------------------------------------------
-$ git checkout -b speed-up-spinlocks v2.6.35
+$ git switch -c speed-up-spinlocks v2.6.35
 -------------------------------------------------
 
 Now you apply the patch(es), run some tests, and commit the change(s).  If
@@ -2279,7 +2279,7 @@ When you are happy with the state of this change, you can merge it into the
 "test" branch in preparation to make it public:
 
 -------------------------------------------------
-$ git checkout test && git merge speed-up-spinlocks
+$ git switch test && git merge speed-up-spinlocks
 -------------------------------------------------
 
 It is unlikely that you would have any conflicts here ... but you might if you
@@ -2291,7 +2291,7 @@ see the value of keeping each patch (or patch series) in its own branch.  It
 means that the patches can be moved into the `release` tree in any order.
 
 -------------------------------------------------
-$ git checkout release && git merge speed-up-spinlocks
+$ git switch release && git merge speed-up-spinlocks
 -------------------------------------------------
 
 After a while, you will have a number of branches, and despite the
@@ -2512,7 +2512,7 @@ Suppose that you create a branch `mywork` on a remote-tracking branch
 `origin`, and create some commits on top of it:
 
 -------------------------------------------------
-$ git checkout -b mywork origin
+$ git switch -c mywork origin
 $ vi file.txt
 $ git commit
 $ vi otherfile.txt
@@ -2552,7 +2552,7 @@ commits without any merges, you may instead choose to use
 linkgit:git-rebase[1]:
 
 -------------------------------------------------
-$ git checkout mywork
+$ git switch mywork
 $ git rebase origin
 -------------------------------------------------
 
@@ -3668,13 +3668,13 @@ change within the submodule, and then update the superproject to reference the
 new commit:
 
 -------------------------------------------------
-$ git checkout master
+$ git switch master
 -------------------------------------------------
 
 or
 
 -------------------------------------------------
-$ git checkout -b fix-up
+$ git switch -c fix-up
 -------------------------------------------------
 
 then
@@ -4194,7 +4194,7 @@ start.
 A good place to start is with the contents of the initial commit, with:
 
 ----------------------------------------------------
-$ git checkout e83c5163
+$ git switch --detach e83c5163
 ----------------------------------------------------
 
 The initial revision lays the foundation for almost everything Git has
@@ -4437,10 +4437,10 @@ Managing branches
 -----------------
 
 -----------------------------------------------
-$ git branch	     # list all local branches in this repo
-$ git checkout test  # switch working directory to branch "test"
-$ git branch new     # create branch "new" starting at current HEAD
-$ git branch -d new  # delete branch "new"
+$ git branch			# list all local branches in this repo
+$ git switch test	        # switch working directory to branch "test"
+$ git branch new		# create branch "new" starting at current HEAD
+$ git branch -d new		# delete branch "new"
 -----------------------------------------------
 
 Instead of basing a new branch on current HEAD (the default), use:
@@ -4456,7 +4456,7 @@ $ git branch new test~10 # ten commits before tip of branch "test"
 Create and switch to a new branch at the same time:
 
 -----------------------------------------------
-$ git checkout -b new v2.6.15
+$ git switch -c new v2.6.15
 -----------------------------------------------
 
 Update and examine branches from the repository you cloned from:
@@ -4467,7 +4467,7 @@ $ git branch -r		# list
   origin/master
   origin/next
   ...
-$ git checkout -b masterwork origin/master
+$ git switch -c masterwork origin/master
 -----------------------------------------------
 
 Fetch a branch from a different repository, and give it a new
diff --git a/advice.c b/advice.c
index 567209aa79..457c1a255e 100644
--- a/advice.c
+++ b/advice.c
@@ -191,13 +191,16 @@ void NORETURN die_conclude_merge(void)
 void detach_advice(const char *new_name)
 {
 	const char *fmt =
-	_("Note: checking out '%s'.\n\n"
+	_("Note: switching to '%s'.\n"
+	"\n"
 	"You are in 'detached HEAD' state. You can look around, make experimental\n"
 	"changes and commit them, and you can discard any commits you make in this\n"
-	"state without impacting any branches by performing another checkout.\n\n"
+	"state without impacting any branches by switching back to a branch.\n"
+	"\n"
 	"If you want to create a new branch to retain commits you create, you may\n"
-	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
-	"  git checkout -b <new-branch-name>\n\n");
+	"do so (now or later) by using -c with the switch command. Example:\n"
+	"\n"
+	"  git switch -c <new-branch-name>\n\n");
 
 	fprintf(stderr, fmt, new_name);
 }
diff --git a/sha1-name.c b/sha1-name.c
index 6dda2c16df..da0518c8e3 100644
--- a/sha1-name.c
+++ b/sha1-name.c
@@ -743,7 +743,7 @@ static int get_oid_basic(const char *str, int len, struct object_id *oid,
 	"because it will be ignored when you just specify 40-hex. These refs\n"
 	"may be created by mistake. For example,\n"
 	"\n"
-	"  git checkout -b $br $(git rev-parse ...)\n"
+	"  git switch -c $br $(git rev-parse ...)\n"
 	"\n"
 	"where \"$br\" is somehow empty and a 40-hex ref is created. Please\n"
 	"examine these refs and maybe delete them. Turn this message off by\n"
diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
index 1fa670625c..f85dbd6740 100755
--- a/t/t2020-checkout-detach.sh
+++ b/t/t2020-checkout-detach.sh
@@ -195,16 +195,16 @@ test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not as
 
 	# The first detach operation is more chatty than the following ones.
 	cat >1st_detach <<-EOF &&
-	Note: checking out 'HEAD^'.
+	Note: switching to 'HEAD^'.
 
 	You are in 'detached HEAD' state. You can look around, make experimental
 	changes and commit them, and you can discard any commits you make in this
-	state without impacting any branches by performing another checkout.
+	state without impacting any branches by switching back to a branch.
 
 	If you want to create a new branch to retain commits you create, you may
-	do so (now or later) by using -b with the checkout command again. Example:
+	do so (now or later) by using -c with the switch command. Example:
 
-	  git checkout -b <new-branch-name>
+	  git switch -c <new-branch-name>
 
 	HEAD is now at \$commit three
 	EOF
@@ -271,16 +271,16 @@ test_expect_success 'describe_detached_head does print SHA-1 ellipsis when asked
 
 	# The first detach operation is more chatty than the following ones.
 	cat >1st_detach <<-EOF &&
-	Note: checking out 'HEAD^'.
+	Note: switching to 'HEAD^'.
 
 	You are in 'detached HEAD' state. You can look around, make experimental
 	changes and commit them, and you can discard any commits you make in this
-	state without impacting any branches by performing another checkout.
+	state without impacting any branches by switching back to a branch.
 
 	If you want to create a new branch to retain commits you create, you may
-	do so (now or later) by using -b with the checkout command again. Example:
+	do so (now or later) by using -c with the switch command. Example:
 
-	  git checkout -b <new-branch-name>
+	  git switch -c <new-branch-name>
 
 	HEAD is now at \$commit... three
 	EOF
-- 
2.21.0.rc1.337.gdf7f8d0522
Ramsay Jones· Mar 8, 2019, 17:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v3 00/21] Add new command "switch"

On 08/03/2019 09:57, Nguyễn Thái Ngọc Duy wrote: [snip]

Show 11 quoted lines
> Range-diff dựa trên v2:
>  -:  ---------- >  1:  949f3dd4fd git-checkout.txt: spell out --no-option
>  1:  8358b9ca36 =  2:  1ddbbae3e2 git-checkout.txt: fix one syntax line
>  2:  1686ccbf8d !  3:  b0cb2372db doc: document --overwrite-ignore
>     @@ -14,14 +14,15 @@
>       	out anyway. In other words, the ref can be held by more than one
>       	worktree.
>       
>     -+--[no-]overwrite-ignore::
>     ++--overwrite-ignore::
>     ++--no-overwrite-ignore::

Just curious, but why? Is '--[no-]overwrite-ignore' thought to be harder to read? What about the rest of the man-pages?

ATB, Ramsay Jones

Duy Nguyen· Mar 9, 2019, 11:56 UTC · re: Ramsay Jones · lore

Re: [PATCH v3 00/21] Add new command "switch"

On Sat, Mar 9, 2019 at 12:49 AM Ramsay Jones <ramsay@ramsayjones.plus.com> wrote:

Show 19 quoted lines
>
>
>
> On 08/03/2019 09:57, Nguyễn Thái Ngọc Duy wrote:
> [snip]
> > Range-diff dựa trên v2:
> >  -:  ---------- >  1:  949f3dd4fd git-checkout.txt: spell out --no-option
> >  1:  8358b9ca36 =  2:  1ddbbae3e2 git-checkout.txt: fix one syntax line
> >  2:  1686ccbf8d !  3:  b0cb2372db doc: document --overwrite-ignore
> >     @@ -14,14 +14,15 @@
> >               out anyway. In other words, the ref can be held by more than one
> >               worktree.
> >
> >     -+--[no-]overwrite-ignore::
> >     ++--overwrite-ignore::
> >     ++--no-overwrite-ignore::
>
> Just curious, but why? Is '--[no-]overwrite-ignore' thought to
> be harder to read? What about the rest of the man-pages?

It's also easier to search. If I remember correctly I did search for the rest of man pages and the --no- wins over --[no-]

-- 
Duy
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 00/26] Add new command 'switch'

Thanks for all the comments from v3 (and before), I didn't expect feedback from so many people. v4 fixes most of them, but still leaves a couple for v5.

- -C remains because people seem to need it
- --recreate vs --force-create: there was no strong preference for
  either, so I stick with --force-create
- --guess (dwim mode) is now made default
- --orphan by default switches to an empty tree instead of current HEAD
  (this is Elijah's alternative approach). This allows you to start
  fresh, but you can still keep "HEAD" content or even switch to another
  tree.
- advice.detachedHead text is updated to suggest a way out (currently it
  only suggests a way forward with 'checkout -b')
- --force has a new alias --discard-changes
- the error message when we expect a branch is improved to help point
  out why the given argument is not a branch
- more docs update and reorganization, and other silly bugs I made in
  v3
The two things I'm aware of but have not done:
- the bug in 'git checkout -m' and 'git switch -m' that could lead to
  loss of staged changes.
- better suggestion when dwim fails (e.g. when there are multiple dwim
  candidates, we could show them all).

There have been lots of discussions back and forth. If I miss anything else, please let me know.

Nguyễn Thái Ngọc Duy (26):
  git-checkout.txt: spell out --no-option
  git-checkout.txt: fix one syntax line
  doc: document --overwrite-ignore
  git-checkout.txt: fix monospace typeset
  t: rename t2014-switch.sh to t2014-checkout-switch.sh
  checkout: advice how to get out of detached HEAD mode
  checkout: keep most #include sorted
  checkout: factor out some code in parse_branchname_arg()
  checkout: make "opts" in cmd_checkout() a pointer
  checkout: move 'confict_style' and 'dwim_..' to checkout_opts
  checkout: split options[] array in three pieces
  checkout: split part of it to new command 'switch'
  switch: better names for -b and -B
  switch: add --discard-changes
  switch: remove -l
  switch: stop accepting pathspec
  switch: reject "do nothing" case
  switch: only allow explicit detached HEAD
  switch: add short option for --detach
  switch: implicit dwim, use --no-guess to disable it
  switch: no worktree status unless real branch switch happens
  switch: reject if some operation is in progress
  switch: --orphan defaults to empty tree as HEAD
  t: add tests for switch
  completion: support switch
  doc: promote "git switch"
 .gitignore                                    |   1 +
 Documentation/config/advice.txt               |  13 +-
 Documentation/config/branch.txt               |   4 +-
 Documentation/config/checkout.txt             |  17 +-
 Documentation/config/diff.txt                 |   3 +-
 Documentation/git-branch.txt                  |  12 +-
 Documentation/git-check-ref-format.txt        |   3 +-
 Documentation/git-checkout.txt                | 221 ++++---
 Documentation/git-format-patch.txt            |   2 +-
 Documentation/git-merge-base.txt              |   2 +-
 Documentation/git-merge.txt                   |   5 +
 Documentation/git-rebase.txt                  |   2 +-
 Documentation/git-remote.txt                  |   2 +-
 Documentation/git-rerere.txt                  |  10 +-
 Documentation/git-reset.txt                   |  20 +-
 Documentation/git-stash.txt                   |   9 +-
 Documentation/git-switch.txt (new)            | 277 ++++++++
 Documentation/gitattributes.txt               |   3 +-
 Documentation/gitcore-tutorial.txt            |  19 +-
 Documentation/giteveryday.txt                 |  24 +-
 Documentation/githooks.txt                    |   8 +-
 Documentation/gittutorial.txt                 |   4 +-
 Documentation/gitworkflows.txt                |   3 +-
 Documentation/revisions.txt                   |   2 +-
 Documentation/user-manual.txt                 |  56 +-
 Makefile                                      |   1 +
 advice.c                                      |  17 +-
 builtin.h                                     |   1 +
 builtin/checkout.c                            | 597 ++++++++++--------
 command-list.txt                              |   1 +
 contrib/completion/git-completion.bash        |  37 +-
 git.c                                         |   1 +
 parse-options-cb.c                            |  17 +
 parse-options.h                               |   1 +
 sha1-name.c                                   |   2 +-
 t/t1090-sparse-checkout-scope.sh              |  14 -
 ...014-switch.sh => t2014-checkout-switch.sh} |   0
 t/t2020-checkout-detach.sh                    |  28 +-
 t/t2060-switch.sh (new +x)                    |  97 +++
 39 files changed, 1043 insertions(+), 493 deletions(-)
 create mode 100644 Documentation/git-switch.txt
 rename t/{t2014-switch.sh => t2014-checkout-switch.sh} (100%)
 create mode 100755 t/t2060-switch.sh
Range-diff dựa trên v3:
 1:  b0cb2372db !  1:  535dc1f310 doc: document --overwrite-ignore
    @@ -22,7 +22,20 @@
     +
      --recurse-submodules::
      --no-recurse-submodules::
    - 	Using --recurse-submodules will update the content of all initialized
    +-	Using --recurse-submodules will update the content of all initialized
    ++	Using `--recurse-submodules` will update the content of all initialized
    + 	submodules according to the commit recorded in the superproject. If
    + 	local modifications in a submodule would be overwritten the checkout
    +-	will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
    ++	will fail unless `-f` is used. If nothing (or `--no-recurse-submodules`)
    + 	is used, the work trees of submodules will not be updated.
    +-	Just like linkgit:git-submodule[1], this will detach the
    +-	submodules HEAD.
    ++	Just like linkgit:git-submodule[1], this will detach `HEAD` of the
    ++	submodule.
    + 
    + --no-guess::
    + 	Do not attempt to create a branch if a remote tracking branch
     
      diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
      --- a/Documentation/git-merge.txt
 2:  5b165524d1 <  -:  ---------- git-checkout.txt: fix monospace typeset
 -:  ---------- >  2:  b6305d2871 git-checkout.txt: fix monospace typeset
 3:  a4240a888e =  3:  bb56e45457 t: rename t2014-switch.sh to t2014-checkout-switch.sh
 -:  ---------- >  4:  123392757a checkout: advice how to get out of detached HEAD mode
 -:  ---------- >  5:  d1ec6b4ce0 checkout: keep most #include sorted
 4:  dc4a8e8933 =  6:  4b1742672b checkout: factor out some code in parse_branchname_arg()
 5:  f0d933c2ac =  7:  e0bcc3a4dd checkout: make "opts" in cmd_checkout() a pointer
 6:  0aa541689c =  8:  ca5b4d7db0 checkout: move 'confict_style' and 'dwim_..' to checkout_opts
 7:  84ca42bb26 =  9:  13c03997f0 checkout: split options[] array in three pieces
 8:  e983c8bb2c ! 10:  24d532b276 checkout: split part of it to new command 'switch'
    @@ -4,9 +4,9 @@
     
         "git checkout" doing too many things is a source of confusion for many
         users (and it even bites old timers sometimes). To remedy that, the
    -    command will be split into two new ones: switch and
    -    something-to-checkout-paths. The good old "git checkout" command is
    -    still here and will be until all (or most of users) are sick of it.
    +    command will be split into two new ones: switch and restore. The good
    +    old "git checkout" command is still here and will be until all (or most
    +    of users) are sick of it.
     
         See the new man page for the final design of switch. The actual
         implementation though is still pretty much the same as "git checkout"
    @@ -157,12 +157,13 @@
     +updated to match the branch. All new commits will be added to the tip
     +of this branch.
     +
    -+Optionally a new branch could be created with either `-c` or `-C`, or
    ++Optionally a new branch could be created with either `-c`, `-C`,
    ++automatically from a remote branch of same name (see `--guess`), or
     +detach the working tree from any branch with `--detach`, along with
     +switching.
     +
     +Switching branches does not require a clean index and working tree
    -+(i.e. no differences compared to 'HEAD'). The operation is aborted
    ++(i.e. no differences compared to `HEAD`). The operation is aborted
     +however if the switch leads to loss of local changes, unless told
     +otherwise.
     +
    @@ -175,14 +176,19 @@
     +	Name for the new branch.
     +
     +<start-point>::
    -+	The name of a commit at which to switch to before creating a
    -+	new branch or detach from.
    ++	The starting point for the new branch. Specifying a
    ++	`<start-point>` allows you to create a branch based on some
    ++	other point in history than where HEAD currently points. (Or,
    ++	in the case of `--detach`, allows you to inspect and detach
    ++	from some other point.)
     ++
    -+You can use the `"@{-N}"` syntax to refer to the N-th last
    ++You can use the `@{-N}` syntax to refer to the N-th last
     +branch/commit switched to "git switch" or "git checkout"
    -+operation. You may also specify `-` which is synonymous to `"@{-1}"`.
    ++operation. You may also specify `-` which is synonymous to `@{-1}`.
    ++This is often used to switch quickly between two branches, or to undo
    ++a branch switch by mistake.
     ++
    -+As a special case, you may use `"A...B"` as a shortcut for the merge
    ++As a special case, you may use `A...B` as a shortcut for the merge
     +base of `A` and `B` if there is exactly one merge base. You can leave
     +out at most one of `A` and `B`, in which case it defaults to `HEAD`.
     +
    @@ -214,8 +220,8 @@
     +	experiments. See the "DETACHED HEAD" section in
     +	linkgit:git-checkout[1] for details.
     +
    -+-g::
     +--guess::
    ++--no-guess::
     +	If `<branch>` is not found but there does exist a tracking
     +	branch in exactly one remote (call it `<remote>`) with a
     +	matching name, treat as equivalent to
    @@ -231,18 +237,45 @@
     +to always checkout remote branches from there if `<branch>` is
     +ambiguous but exists on the 'origin' remote. See also
     +`checkout.defaultRemote` in linkgit:git-config[1].
    -+
    -+-q::
    -+--quiet::
    -+	Quiet, suppress feedback messages.
    +++
    ++`--guess` is the default behavior. Use `--no-guess` to disable it.
     +
     +-f::
     +--force::
    ++	An alias for `--discard-changes`.
    ++
    ++--discard-changes::
     +	Proceed even if the index or the working tree differs from
    -+	HEAD. Both the index and working tree are restored to match
    ++	`HEAD`. Both the index and working tree are restored to match
     +	the switching target. This is used to throw away local
     +	changes.
     +
    ++-m::
    ++--merge::
    ++	If you have local modifications to one or more files that are
    ++	different between the current branch and the branch to which
    ++	you are switching, the command refuses to switch branches in
    ++	order to preserve your modifications in context.  However,
    ++	with this option, a three-way merge between the current
    ++	branch, your working tree contents, and the new branch is
    ++	done, and you will be on the new branch.
    +++
    ++When a merge conflict happens, the index entries for conflicting
    ++paths are left unmerged, and you need to resolve the conflicts
    ++and mark the resolved paths with `git add` (or `git rm` if the merge
    ++should result in deletion of the path).
    ++
    ++--conflict=<style>::
    ++	The same as `--merge` option above, but changes the way the
    ++	conflicting hunks are presented, overriding the
    ++	`merge.conflictStyle` configuration variable.  Possible values are
    ++	"merge" (default) and "diff3" (in addition to what is shown by
    ++	"merge" style, shows the original contents).
    ++
    ++-q::
    ++--quiet::
    ++	Quiet, suppress feedback messages.
    ++
     +--progress::
     +--no-progress::
     +	Progress status is reported on the standard error stream
    @@ -253,49 +286,28 @@
     +-t::
     +--track::
     +	When creating a new branch, set up "upstream" configuration.
    -+	`-c` is implied. See "--track" in linkgit:git-branch[1] for
    ++	`-c` is implied. See `--track` in linkgit:git-branch[1] for
     +	details.
     ++
     +If no `-c` option is given, the name of the new branch will be derived
     +from the remote-tracking branch, by looking at the local part of the
     +refspec configured for the corresponding remote, and then stripping
    -+the initial part up to the "*".  This would tell us to use "hack" as
    -+the local branch when branching off of "origin/hack" (or
    -+"remotes/origin/hack", or even "refs/remotes/origin/hack").  If the
    ++the initial part up to the "*".  This would tell us to use `hack` as
    ++the local branch when branching off of `origin/hack` (or
    ++`remotes/origin/hack`, or even `refs/remotes/origin/hack`).  If the
     +given name has no slash, or the above guessing results in an empty
     +name, the guessing is aborted.  You can explicitly give a name with
     +`-c` in such a case.
     +
     +--no-track::
     +	Do not set up "upstream" configuration, even if the
    -+	branch.autoSetupMerge configuration variable is true.
    -+
    -+-m::
    -+--merge::
    -+	If you have local modifications to one or more files that are
    -+	different between the current branch and the branch to which
    -+	you are switching, the command refuses to switch branches in
    -+	order to preserve your modifications in context.  However,
    -+	with this option, a three-way merge between the current
    -+	branch, your working tree contents, and the new branch is
    -+	done, and you will be on the new branch.
    -++
    -+When a merge conflict happens, the index entries for conflicting
    -+paths are left unmerged, and you need to resolve the conflicts
    -+and mark the resolved paths with `git add` (or `git rm` if the merge
    -+should result in deletion of the path).
    -+
    -+--conflict=<style>::
    -+	The same as --merge option above, but changes the way the
    -+	conflicting hunks are presented, overriding the
    -+	merge.conflictStyle configuration variable.  Possible values are
    -+	"merge" (default) and "diff3" (in addition to what is shown by
    -+	"merge" style, shows the original contents).
    ++	`branch.autoSetupMerge` configuration variable is true.
     +
     +--orphan <new-branch>::
    -+	Create a new 'orphan' branch, named `<new-branch>`, started from
    -+	`<start-point>` and switch to it. See explanation of the same
    -+	option in linkgit:git-checkout[1] for details.
    ++	Create a new 'orphan' branch, named `<new-branch>`. If
    ++	`<start-point>` is specified, the index and working tree are
    ++	adjusted to match it. Otherwise both are adjusted to contain no
    ++	tracked files.
     +
     +--ignore-other-worktrees::
     +	`git switch` refuses when the wanted ref is already
    @@ -303,15 +315,20 @@
     +	the ref out anyway. In other words, the ref can be held by
     +	more than one worktree.
     +
    ++--ignore-in-progress::
    ++	`git switch` by default refuses when some operation is in
    ++	progress (e.g. "git rebase", "git am" ...). This option
    ++	overrides this safety check and allows switching.
    ++
     +--recurse-submodules::
     +--no-recurse-submodules::
    -+	Using --recurse-submodules will update the content of all initialized
    ++	Using `--recurse-submodules` will update the content of all initialized
     +	submodules according to the commit recorded in the superproject. If
     +	local modifications in a submodule would be overwritten the checkout
    -+	will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
    ++	will fail unless `-f` is used. If nothing (or `--no-recurse-submodules`)
     +	is used, the work trees of submodules will not be updated.
    -+	Just like linkgit:git-submodule[1], this will detach the
    -+	submodules HEAD.
    ++	Just like linkgit:git-submodule[1], this will detach `HEAD` of the
    ++	submodule.
     +
     +EXAMPLES
     +--------
    @@ -366,7 +383,7 @@
     +------------
     +
     +If you want to start a new branch from a remote branch of the same
    -+name, use `-g`:
    ++name:
     +
     +------------
     +$ git switch -g new-topic
    @@ -374,7 +391,7 @@
     +Switched to a new branch 'new-topic'
     +------------
     +
    -+To check out commit "HEAD~3" for temporary inspection or experiment
    ++To check out commit `HEAD~3` for temporary inspection or experiment
     +without creating a new branch:
     +
     +------------
    @@ -391,7 +408,8 @@
     +
     +SEE ALSO
     +--------
    -+linkgit:git-checkout[1]
    ++linkgit:git-checkout[1],
    ++linkgit:git-branch[1]
     +
     +GIT
     +---
 9:  5491932cc8 = 11:  c966bacfcc switch: better names for -b and -B
 -:  ---------- > 12:  bdb88bf9a9 switch: add --discard-changes
10:  e0f1247b09 = 13:  d5fe7f4bd0 switch: remove -l
11:  f7ff1a7df6 ! 14:  3bce4c521e switch: stop accepting pathspec
    @@ -11,9 +11,9 @@
      --- a/builtin/checkout.c
      +++ b/builtin/checkout.c
     @@
    - 	int count_checkout_paths;
      	int overlay_mode;
      	int no_dwim_new_local_branch;
    + 	int discard_changes;
     +	int accept_pathspec;
      
      	/*
12:  0750d63d38 ! 15:  dad0063fc4 switch: reject "do nothing" case
    @@ -17,8 +17,8 @@
      --- a/builtin/checkout.c
      +++ b/builtin/checkout.c
     @@
    - 	int overlay_mode;
      	int no_dwim_new_local_branch;
    + 	int discard_changes;
      	int accept_pathspec;
     +	int switch_branch_doing_nothing_is_ok;
      
13:  bcd843146d ! 16:  41ca042917 switch: only allow explicit detached HEAD
    @@ -24,6 +24,36 @@
      	int writeout_stage;
      	int overwrite_ignore;
      	int ignore_skipworktree;
    +@@
    + 	return status;
    + }
    + 
    ++static void die_expecting_a_branch(const struct branch_info *branch_info)
    ++{
    ++	struct object_id oid;
    ++	char *to_free;
    ++
    ++	if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free) == 1) {
    ++		const char *ref = to_free;
    ++
    ++		if (skip_prefix(ref, "refs/tags/", &ref))
    ++			die(_("a branch is expected, got tag '%s'"), ref);
    ++		if (skip_prefix(ref, "refs/remotes/", &ref))
    ++			die(_("a branch is expected, got remote branch '%s'"), ref);
    ++		die(_("a branch is expected, got '%s'"), ref);
    ++	}
    ++	if (branch_info->commit)
    ++		die(_("a branch is expected, got commit '%s'"), branch_info->name);
    ++	/*
    ++	 * This case should never happen because we already die() on
    ++	 * non-commit, but just in case.
    ++	 */
    ++	die(_("a branch is expected, got '%s'"), branch_info->name);
    ++}
    ++
    + static int checkout_branch(struct checkout_opts *opts,
    + 			   struct branch_info *new_branch_info)
    + {
     @@
      	    !opts->force_detach)
      		die(_("missing branch or commit argument"));
    @@ -34,7 +64,7 @@
     +	    !opts->new_branch_force &&
     +	    new_branch_info->name &&
     +	    !new_branch_info->path)
    -+		die(_("a branch is expected, got %s"), new_branch_info->name);
    ++		die_expecting_a_branch(new_branch_info);
     +
      	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
      	    !opts->ignore_other_worktrees) {
14:  d5cc55525d = 17:  a0b9f1b285 switch: add short option for --detach
15:  4a27400a58 ! 18:  3d254df104 switch: no implicit dwim, use --guess to dwim
    @@ -1,15 +1,15 @@
     Author: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
     
    -    switch: no implicit dwim, use --guess to dwim
    +    switch: implicit dwim, use --no-guess to disable it
     
    -    Similar to automatic detach, this behavior could be confusing because
    -    it can sometimes create a new branch without a user asking it to,
    -    especially when the user is still not aware about this feature.
    +    This is already the default in git-checkout. The real change in here is
    +    just minor cleanup. The main excuse is to explain why dwim is kept default.
     
    -    In the future, perhaps we could have a config key to disable these
    -    safety nets and let 'switch' do automatic detach or dwim
    -    again. But that will be opt-in after the user knows what is what. For
    -    now give a short option if you want to use it often.
    +    Contrary to detach mode that is easy to get into and confusing to get
    +    back out. Automatically creating a tracking branch often does not kick
    +    in as often (you would need a branch of the same name on a remote). And
    +    since the branch creation is reported clearly, the user should be able
    +    to undo/delete it if it's unwanted.
     
      diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
      --- a/Documentation/git-checkout.txt
    @@ -41,7 +41,7 @@
      rather expensive side-effects to show only the tracking information,
     @@
      	Do not set up "upstream" configuration, even if the
    - 	branch.autoSetupMerge configuration variable is true.
    + 	`branch.autoSetupMerge` configuration variable is true.
      
     +--guess::
     +--no-guess::
    @@ -68,8 +68,8 @@
      	Create the new branch's reflog; see linkgit:git-branch[1] for
      	details.
     @@
    - 	Just like linkgit:git-submodule[1], this will detach the
    - 	submodules HEAD.
    + 	Just like linkgit:git-submodule[1], this will detach `HEAD` of the
    + 	submodule.
      
     ---no-guess::
     -	Do not attempt to create a branch if a remote tracking branch
    @@ -88,9 +88,9 @@
      	int overlay_mode;
     -	int no_dwim_new_local_branch;
     +	int dwim_new_local_branch;
    + 	int discard_changes;
      	int accept_pathspec;
      	int switch_branch_doing_nothing_is_ok;
    - 
     @@
      		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
      			   N_("update ignored files (default)"),
    @@ -145,15 +145,17 @@
      			   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('g', "guess", &opts.dwim_new_local_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()
    - 	};
    +@@
      	int ret;
      
      	memset(&opts, 0, sizeof(opts));
     -	opts.no_dwim_new_local_branch = 0;
    -+	opts.dwim_new_local_branch = 0;
    ++	opts.dwim_new_local_branch = 1;
      	opts.accept_pathspec = 0;
      	opts.switch_branch_doing_nothing_is_ok = 0;
      	opts.implicit_detach = 0;
16:  f7dd33abe6 ! 19:  c6ea203f36 switch: no worktree status unless real branch switch happens
    @@ -36,8 +36,8 @@
      --- a/builtin/checkout.c
      +++ b/builtin/checkout.c
     @@
    - #include "submodule.h"
    - #include "advice.h"
    + #include "unpack-trees.h"
    + #include "xdiff-interface.h"
      
     -static int checkout_optimize_new_branch;
     -
    @@ -45,7 +45,7 @@
      	N_("git checkout [<options>] <branch>"),
      	N_("git checkout [<options>] [<branch>] -- <file>..."),
     @@
    - 	int dwim_new_local_branch;
    + 	int discard_changes;
      	int accept_pathspec;
      	int switch_branch_doing_nothing_is_ok;
     -
    @@ -220,7 +220,7 @@
      	opts.implicit_detach = 1;
      
     @@
    - 	opts.dwim_new_local_branch = 0;
    + 	opts.dwim_new_local_branch = 1;
      	opts.accept_pathspec = 0;
      	opts.switch_branch_doing_nothing_is_ok = 0;
     +	opts.only_merge_on_switching_branches = 1;
 -:  ---------- > 20:  5c4effc7fd switch: reject if some operation is in progress
 -:  ---------- > 21:  a4afe6a999 switch: --orphan defaults to empty tree as HEAD
17:  5b182abea8 ! 22:  6cca78f835 t: add tests for switch
    @@ -31,12 +31,9 @@
     +	test_path_is_missing second.t
     +'
     +
    -+test_expect_success 'switch to a commit' '
    -+	test_must_fail git switch master^{commit}
    -+'
    -+
     +test_expect_success 'switch and detach' '
     +	test_when_finished git switch master &&
    ++	test_must_fail git switch master^{commit} &&
     +	git switch --detach master^{commit} &&
     +	test_must_fail git symbolic-ref HEAD
     +'
    @@ -60,6 +57,7 @@
     +test_expect_success 'force create branch from HEAD' '
     +	test_when_finished git switch master &&
     +	git switch --detach master &&
    ++	test_must_fail git switch -c temp &&
     +	git switch -C temp &&
     +	test_cmp_rev master refs/heads/temp &&
     +	echo refs/heads/temp >expected-branch &&
    @@ -67,12 +65,15 @@
     +	test_cmp expected-branch actual-branch
     +'
     +
    -+test_expect_success 'new orphan branch' '
    ++test_expect_success 'new orphan branch from empty' '
     +	test_when_finished git switch master &&
    -+	git switch --orphan new-orphan master^ &&
    ++	git switch --orphan new-orphan &&
     +	test_commit orphan &&
     +	git cat-file commit refs/heads/new-orphan >commit &&
    -+	! grep ^parent commit
    ++	! grep ^parent commit &&
    ++	git ls-files >tracked-files &&
    ++	echo orphan.t >expected &&
    ++	test_cmp expected tracked-files
     +'
     +
     +test_expect_success 'switching ignores file of same branch name' '
    @@ -81,16 +82,25 @@
     +	git switch first-branch &&
     +	echo refs/heads/first-branch >expected &&
     +	git symbolic-ref HEAD >actual &&
    -+	test_commit expected actual
    ++	test_cmp expected actual
     +'
     +
     +test_expect_success 'guess and create branch ' '
     +	test_when_finished git switch master &&
    -+	test_must_fail git switch foo &&
    -+	git switch --guess foo &&
    ++	test_must_fail git switch --no-guess foo &&
    ++	git switch foo &&
     +	echo refs/heads/foo >expected &&
     +	git symbolic-ref HEAD >actual &&
     +	test_cmp expected actual
     +'
     +
    ++test_expect_success 'switching when something is in progress' '
    ++	test_when_finished rm -f .git/MERGE_HEAD &&
    ++	# fake a merge-in-progress
    ++	cp .git/HEAD .git/MERGE_HEAD &&
    ++	test_must_fail git switch -d @^ &&
    ++	git switch --ignore-in-progress -d @^ &&
    ++	git switch -
    ++'
    ++
     +test_done
18:  1575064fe6 ! 23:  e5e6f9d6f1 completion: support switch
    @@ -10,6 +10,16 @@
      diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
      --- a/contrib/completion/git-completion.bash
      +++ b/contrib/completion/git-completion.bash
    +@@
    + #   GIT_COMPLETION_CHECKOUT_NO_GUESS
    + #
    + #     When set to "1", do not include "DWIM" suggestions in git-checkout
    +-#     completion (e.g., completing "foo" when "origin/foo" exists).
    ++#     and git-switch completion (e.g., completing "foo" when "origin/foo"
    ++#     exists).
    + 
    + case "$COMP_WORDBREAKS" in
    + *:*) : great ;;
     @@
      	__git_complete_index_file "$complete_opt"
      }
    @@ -24,15 +34,22 @@
     +		__gitcomp_builtin switch
     +		;;
     +	*)
    -+		# check if ---guess was specified to enable DWIM mode
    -+		local track_opt= only_local_ref=n
    -+		if [ -n "$(__git_find_on_cmdline "-g --guess")" ]; then
    ++		# check if --track, --no-track, or --no-guess was specified
    ++		# if so, disable DWIM mode
    ++		local track_opt="--track" only_local_ref=n
    ++		if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ] ||
    ++		   [ -n "$(__git_find_on_cmdline "--track --no-track --no-guess")" ]; then
    ++			track_opt=''
    ++		fi
    ++		# explicit --guess enables DWIM mode regardless of
    ++		# $GIT_COMPLETION_CHECKOUT_NO_GUESS
    ++		if [ -n "$(__git_find_on_cmdline "--guess")" ]; then
     +			track_opt='--track'
     +		fi
     +		if [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then
     +			only_local_ref=y
     +		fi
    -+		if [ $only_local_ref = y -a -n "$track_opt"]; then
    ++		if [ $only_local_ref = y -a -z "$track_opt" ]; then
     +			__gitcomp_direct "$(__git_heads "" "$cur" " ")"
     +		else
     +			__git_complete_refs $track_opt
19:  8ed01ad0c5 ! 24:  807e8bc50b doc: promote "git switch"
    @@ -647,23 +647,28 @@
      void detach_advice(const char *new_name)
      {
      	const char *fmt =
    --	_("Note: checking out '%s'.\n\n"
    +-	_("Note: checking out '%s'.\n"
     +	_("Note: switching to '%s'.\n"
    -+	"\n"
    + 	"\n"
      	"You are in 'detached HEAD' state. You can look around, make experimental\n"
      	"changes and commit them, and you can discard any commits you make in this\n"
    --	"state without impacting any branches by performing another checkout.\n\n"
    +-	"state without impacting any branches by performing another checkout.\n"
     +	"state without impacting any branches by switching back to a branch.\n"
    -+	"\n"
    + 	"\n"
      	"If you want to create a new branch to retain commits you create, you may\n"
    --	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
    --	"  git checkout -b <new-branch-name>\n\n");
    +-	"do so (now or later) by using -b with the checkout command again. Example:\n"
     +	"do so (now or later) by using -c with the switch command. Example:\n"
    -+	"\n"
    -+	"  git switch -c <new-branch-name>\n\n");
    + 	"\n"
    +-	"  git checkout -b <new-branch-name>\n"
    ++	"  git switch -c <new-branch-name>\n"
    + 	"\n"
    + 	"Or undo this checkout with:\n"
    + 	"\n"
    +-	"  git checkout -\n"
    ++	"  git switch -\n"
    + 	"\n"
    + 	"Turn off this advice by setting config variable advice.detachedHead to false\n\n");
      
    - 	fprintf(stderr, fmt, new_name);
    - }
     
      diff --git a/sha1-name.c b/sha1-name.c
      --- a/sha1-name.c
    @@ -700,8 +705,13 @@
     -	  git checkout -b <new-branch-name>
     +	  git switch -c <new-branch-name>
      
    - 	HEAD is now at \$commit three
    - 	EOF
    + 	Or undo this checkout with:
    + 
    +-	  git checkout -
    ++	  git switch -
    + 
    + 	Turn off this advice by setting config variable advice.detachedHead to false
    + 
     @@
      
      	# The first detach operation is more chatty than the following ones.
    @@ -721,5 +731,10 @@
     -	  git checkout -b <new-branch-name>
     +	  git switch -c <new-branch-name>
      
    - 	HEAD is now at \$commit... three
    - 	EOF
    + 	Or undo this checkout with:
    + 
    +-	  git checkout -
    ++	  git switch -
    + 
    + 	Turn off this advice by setting config variable advice.detachedHead to false
    + 
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 01/26] git-checkout.txt: spell out --no-option

It's easier to search for and also less cryptic.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)
Show changes to Documentation/git-checkout.txt +7 −1
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index f179b43732..99c8c0dc0f 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -118,7 +118,8 @@ OPTIONS
 --quiet::
 	Quiet, suppress feedback messages.
 
---[no-]progress::
+--progress::
+--no-progress::
 	Progress status is reported on the standard error stream
 	by default when it is attached to a terminal, unless `--quiet`
 	is specified. This flag enables progress reporting even if not
@@ -262,7 +263,7 @@ edits from your current working tree. See the ``Interactive Mode''
 section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
 +
 Note that this option uses the no overlay mode by default (see also
-`--[no-]overlay`), and currently doesn't support overlay mode.
+`--overlay`), and currently doesn't support overlay mode.
 
 --ignore-other-worktrees::
 	`git checkout` refuses when the wanted ref is already checked
@@ -270,7 +271,8 @@ Note that this option uses the no overlay mode by default (see also
 	out anyway. In other words, the ref can be held by more than one
 	worktree.
 
---[no-]recurse-submodules::
+--recurse-submodules::
+--no-recurse-submodules::
 	Using --recurse-submodules will update the content of all initialized
 	submodules according to the commit recorded in the superproject. If
 	local modifications in a submodule would be overwritten the checkout
@@ -283,7 +285,8 @@ Note that this option uses the no overlay mode by default (see also
 	Do not attempt to create a branch if a remote tracking branch
 	of the same name exists.
 
---[no-]overlay::
+--overlay::
+--no-overlay::
 	In the default overlay mode, `git checkout` never
 	removes files from the index or the working tree.  When
 	specifying `--no-overlay`, files that appear in the index and
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 02/26] git-checkout.txt: fix one syntax line

<branch> can be omitted in this syntax, and it's actually documented a few paragraphs down:

  You could omit <branch>, in which case the command degenerates to
  "check out the current branch", which is a glorified no-op with
  rather expensive side-effects to show only the tracking information,
  if exists, for the current branch.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to Documentation/git-checkout.txt +1 −1
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 99c8c0dc0f..28817cfa41 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -23,7 +23,7 @@ or the specified tree.  If no paths are given, 'git checkout' will
 also update `HEAD` to set the specified branch as the current
 branch.
 
-'git checkout' <branch>::
+'git checkout' [<branch>]::
 	To prepare for working on <branch>, switch to it by updating
 	the index and the files in the working tree, and by pointing
 	HEAD at the branch. Local modifications to the files in the
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 03/26] doc: document --overwrite-ignore

I added this option in git-checkout and git-merge in c1d7036b6b (checkout,merge: disallow overwriting ignored files with --no-overwrite-ignore - 2011-11-27) but did not remember to update documentation. This completes that commit.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt | 14 ++++++++++----
 Documentation/git-merge.txt    |  5 +++++
 2 files changed, 15 insertions(+), 4 deletions(-)
Show changes to 2 files +15 −4

Documentation/git-checkout.txt, Documentation/git-merge.txt

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 28817cfa41..82e0f81869 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -271,15 +271,21 @@ Note that this option uses the no overlay mode by default (see also
 	out anyway. In other words, the ref can be held by more than one
 	worktree.
 
+--overwrite-ignore::
+--no-overwrite-ignore::
+	Silently overwrite ignored files when switching branches. This
+	is the default behavior. Use `--no-overwrite-ignore` to abort
+	the operation when the new branch contains ignored files.
+
 --recurse-submodules::
 --no-recurse-submodules::
-	Using --recurse-submodules will update the content of all initialized
+	Using `--recurse-submodules` will update the content of all initialized
 	submodules according to the commit recorded in the superproject. If
 	local modifications in a submodule would be overwritten the checkout
-	will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
+	will fail unless `-f` is used. If nothing (or `--no-recurse-submodules`)
 	is used, the work trees of submodules will not be updated.
-	Just like linkgit:git-submodule[1], this will detach the
-	submodules HEAD.
+	Just like linkgit:git-submodule[1], this will detach `HEAD` of the
+	submodule.
 
 --no-guess::
 	Do not attempt to create a branch if a remote tracking branch
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index 4cc86469f3..6a9163d8fe 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -87,6 +87,11 @@ will be appended to the specified message.
 	Allow the rerere mechanism to update the index with the
 	result of auto-conflict resolution if possible.
 
+--overwrite-ignore::
+--no-overwrite-ignore::
+	Silently overwrite ignored files from the merge result. This
+	is the default behavior. Use `--no-overwrite-ignore` to abort.
+
 --abort::
 	Abort the current conflict resolution process, and
 	try to reconstruct the pre-merge state.
-- 
2.21.0.548.gd3c7d92dc2
Martin Ågren· Mar 17, 2019, 19:18 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v4 03/26] doc: document --overwrite-ignore

On Sun, 17 Mar 2019 at 13:49, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 5 quoted lines
>
> I added this option in git-checkout and git-merge in c1d7036b6b
> (checkout,merge: disallow overwriting ignored files with
> --no-overwrite-ignore - 2011-11-27) but did not remember to update
> documentation. This completes that commit.
Show 19 quoted lines
> +--overwrite-ignore::
> +--no-overwrite-ignore::
> +       Silently overwrite ignored files when switching branches. This
> +       is the default behavior. Use `--no-overwrite-ignore` to abort
> +       the operation when the new branch contains ignored files.
> +
>  --recurse-submodules::
>  --no-recurse-submodules::
> -       Using --recurse-submodules will update the content of all initialized
> +       Using `--recurse-submodules` will update the content of all initialized
>         submodules according to the commit recorded in the superproject. If
>         local modifications in a submodule would be overwritten the checkout
> -       will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
> +       will fail unless `-f` is used. If nothing (or `--no-recurse-submodules`)
>         is used, the work trees of submodules will not be updated.
> -       Just like linkgit:git-submodule[1], this will detach the
> -       submodules HEAD.
> +       Just like linkgit:git-submodule[1], this will detach `HEAD` of the
> +       submodule.

All of this `--recurse-submodules` stuff seems to have have ended up in the wrong commit.

Martin
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 04/26] git-checkout.txt: fix monospace typeset

Add backticks where we have none, replace single quotes with backticks and replace double-quotes. Drop double-quotes from nested constructions such as `"@{-1}"`. Add a missing possessive apostrophe after the word "submodules" while at it.

Helped-by: Martin Ågren <martin.agren@gmail.com>
Signed-off-by: Martin Ågren <martin.agren@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt | 154 ++++++++++++++++-----------------
 1 file changed, 77 insertions(+), 77 deletions(-)
Show changes to Documentation/git-checkout.txt +77 −77
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 82e0f81869..bf90966c95 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -24,14 +24,14 @@ also update `HEAD` to set the specified branch as the current
 branch.
 
 'git checkout' [<branch>]::
-	To prepare for working on <branch>, switch to it by updating
+	To prepare for working on `<branch>`, switch to it by updating
 	the index and the files in the working tree, and by pointing
-	HEAD at the branch. Local modifications to the files in the
+	`HEAD` at the branch. Local modifications to the files in the
 	working tree are kept, so that they can be committed to the
-	<branch>.
+	`<branch>`.
 +
-If <branch> is not found but there does exist a tracking branch in
-exactly one remote (call it <remote>) with a matching name, treat as
+If `<branch>` is not found but there does exist a tracking branch in
+exactly one remote (call it `<remote>`) with a matching name, treat as
 equivalent to
 +
 ------------
@@ -47,7 +47,7 @@ branches from there if `<branch>` is ambiguous but exists on the
 'origin' remote. See also `checkout.defaultRemote` in
 linkgit:git-config[1].
 +
-You could omit <branch>, in which case the command degenerates to
+You could omit `<branch>`, in which case the command degenerates to
 "check out the current branch", which is a glorified no-op with
 rather expensive side-effects to show only the tracking information,
 if exists, for the current branch.
@@ -61,7 +61,7 @@ if exists, for the current branch.
 	`--track` without `-b` implies branch creation; see the
 	description of `--track` below.
 +
-If `-B` is given, <new_branch> is created if it doesn't exist; otherwise, it
+If `-B` is given, `<new_branch>` is created if it doesn't exist; otherwise, it
 is reset. This is the transactional equivalent of
 +
 ------------
@@ -75,25 +75,25 @@ successful.
 'git checkout' --detach [<branch>]::
 'git checkout' [--detach] <commit>::
 
-	Prepare to work on top of <commit>, by detaching HEAD at it
+	Prepare to work on top of `<commit>`, by detaching `HEAD` at it
 	(see "DETACHED HEAD" section), and updating the index and the
 	files in the working tree.  Local modifications to the files
 	in the working tree are kept, so that the resulting working
 	tree will be the state recorded in the commit plus the local
 	modifications.
 +
-When the <commit> argument is a branch name, the `--detach` option can
-be used to detach HEAD at the tip of the branch (`git checkout
-<branch>` would check out that branch without detaching HEAD).
+When the `<commit>` argument is a branch name, the `--detach` option can
+be used to detach `HEAD` at the tip of the branch (`git checkout
+<branch>` would check out that branch without detaching `HEAD`).
 +
-Omitting <branch> detaches HEAD at the tip of the current branch.
+Omitting `<branch>` detaches `HEAD` at the tip of the current branch.
 
 'git checkout' [<tree-ish>] [--] <pathspec>...::
 
 	Overwrite paths in the working tree by replacing with the
-	contents in the index or in the <tree-ish> (most often a
-	commit).  When a <tree-ish> is given, the paths that
-	match the <pathspec> are updated both in the index and in
+	contents in the index or in the `<tree-ish>` (most often a
+	commit).  When a `<tree-ish>` is given, the paths that
+	match the `<pathspec>` are updated both in the index and in
 	the working tree.
 +
 The index may contain unmerged entries because of a previous failed merge.
@@ -128,7 +128,7 @@ OPTIONS
 -f::
 --force::
 	When switching branches, proceed even if the index or the
-	working tree differs from HEAD.  This is used to throw away
+	working tree differs from `HEAD`.  This is used to throw away
 	local changes.
 +
 When checking out paths from the index, do not fail upon unmerged
@@ -155,12 +155,12 @@ on your side branch as `theirs` (i.e. "one contributor's work on top
 of it").
 
 -b <new_branch>::
-	Create a new branch named <new_branch> and start it at
-	<start_point>; see linkgit:git-branch[1] for details.
+	Create a new branch named `<new_branch>` and start it at
+	`<start_point>`; see linkgit:git-branch[1] for details.
 
 -B <new_branch>::
-	Creates the branch <new_branch> and start it at <start_point>;
-	if it already exists, then reset it to <start_point>. This is
+	Creates the branch `<new_branch>` and start it at `<start_point>`;
+	if it already exists, then reset it to `<start_point>`. This is
 	equivalent to running "git branch" with "-f"; see
 	linkgit:git-branch[1] for details.
 
@@ -173,15 +173,15 @@ If no `-b` option is given, the name of the new branch will be
 derived from the remote-tracking branch, by looking at the local part of
 the refspec configured for the corresponding remote, and then stripping
 the initial part up to the "*".
-This would tell us to use "hack" as the local branch when branching
-off of "origin/hack" (or "remotes/origin/hack", or even
-"refs/remotes/origin/hack").  If the given name has no slash, or the above
+This would tell us to use `hack` as the local branch when branching
+off of `origin/hack` (or `remotes/origin/hack`, or even
+`refs/remotes/origin/hack`).  If the given name has no slash, or the above
 guessing results in an empty name, the guessing is aborted.  You can
 explicitly give a name with `-b` in such a case.
 
 --no-track::
 	Do not set up "upstream" configuration, even if the
-	branch.autoSetupMerge configuration variable is true.
+	`branch.autoSetupMerge` configuration variable is true.
 
 -l::
 	Create the new branch's reflog; see linkgit:git-branch[1] for
@@ -190,21 +190,21 @@ explicitly give a name with `-b` in such a case.
 --detach::
 	Rather than checking out a branch to work on it, check out a
 	commit for inspection and discardable experiments.
-	This is the default behavior of "git checkout <commit>" when
-	<commit> is not a branch name.  See the "DETACHED HEAD" section
+	This is the default behavior of `git checkout <commit>` when
+	`<commit>` is not a branch name.  See the "DETACHED HEAD" section
 	below for details.
 
 --orphan <new_branch>::
-	Create a new 'orphan' branch, named <new_branch>, started from
-	<start_point> and switch to it.  The first commit made on this
+	Create a new 'orphan' branch, named `<new_branch>`, started from
+	`<start_point>` and switch to it.  The first commit made on this
 	new branch will have no parents and it will be the root of a new
 	history totally disconnected from all the other branches and
 	commits.
 +
 The index and the working tree are adjusted as if you had previously run
-"git checkout <start_point>".  This allows you to start a new history
-that records a set of paths similar to <start_point> by easily running
-"git commit -a" to make the root commit.
+`git checkout <start_point>`.  This allows you to start a new history
+that records a set of paths similar to `<start_point>` by easily running
+`git commit -a` to make the root commit.
 +
 This can be useful when you want to publish the tree from a commit
 without exposing its full history. You might want to do this to publish
@@ -213,17 +213,17 @@ whose full history contains proprietary or otherwise encumbered bits of
 code.
 +
 If you want to start a disconnected history that records a set of paths
-that is totally different from the one of <start_point>, then you should
+that is totally different from the one of `<start_point>`, then you should
 clear the index and the working tree right after creating the orphan
-branch by running "git rm -rf ." from the top level of the working tree.
+branch by running `git rm -rf .` from the top level of the working tree.
 Afterwards you will be ready to prepare your new files, repopulating the
 working tree, by copying them from elsewhere, extracting a tarball, etc.
 
 --ignore-skip-worktree-bits::
 	In sparse checkout mode, `git checkout -- <paths>` would
-	update only entries matched by <paths> and sparse patterns
-	in $GIT_DIR/info/sparse-checkout. This option ignores
-	the sparse patterns and adds back any files in <paths>.
+	update only entries matched by `<paths>` and sparse patterns
+	in `$GIT_DIR/info/sparse-checkout`. This option ignores
+	the sparse patterns and adds back any files in `<paths>`.
 
 -m::
 --merge::
@@ -245,18 +245,18 @@ When checking out paths from the index, this option lets you recreate
 the conflicted merge in the specified paths.
 
 --conflict=<style>::
-	The same as --merge option above, but changes the way the
+	The same as `--merge` option above, but changes the way the
 	conflicting hunks are presented, overriding the
-	merge.conflictStyle configuration variable.  Possible values are
+	`merge.conflictStyle` configuration variable.  Possible values are
 	"merge" (default) and "diff3" (in addition to what is shown by
 	"merge" style, shows the original contents).
 
 -p::
 --patch::
 	Interactively select hunks in the difference between the
-	<tree-ish> (or the index, if unspecified) and the working
+	`<tree-ish>` (or the index, if unspecified) and the working
 	tree.  The chosen hunks are then applied in reverse to the
-	working tree (and if a <tree-ish> was specified, the index).
+	working tree (and if a `<tree-ish>` was specified, the index).
 +
 This means that you can use `git checkout -p` to selectively discard
 edits from your current working tree. See the ``Interactive Mode''
@@ -296,21 +296,21 @@ Note that this option uses the no overlay mode by default (see also
 	In the default overlay mode, `git checkout` never
 	removes files from the index or the working tree.  When
 	specifying `--no-overlay`, files that appear in the index and
-	working tree, but not in <tree-ish> are removed, to make them
-	match <tree-ish> exactly.
+	working tree, but not in `<tree-ish>` are removed, to make them
+	match `<tree-ish>` exactly.
 
 <branch>::
 	Branch to checkout; if it refers to a branch (i.e., a name that,
 	when prepended with "refs/heads/", is a valid ref), then that
 	branch is checked out. Otherwise, if it refers to a valid
-	commit, your HEAD becomes "detached" and you are no longer on
+	commit, your `HEAD` becomes "detached" and you are no longer on
 	any branch (see below for details).
 +
-You can use the `"@{-N}"` syntax to refer to the N-th last
+You can use the `@{-N}` syntax to refer to the N-th last
 branch/commit checked out using "git checkout" operation. You may
-also specify `-` which is synonymous to `"@{-1}"`.
+also specify `-` which is synonymous to `@{-1}`.
 +
-As a special case, you may use `"A...B"` as a shortcut for the
+As a special case, you may use `A...B` as a shortcut for the
 merge base of `A` and `B` if there is exactly one merge base. You can
 leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 
@@ -319,7 +319,7 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 
 <start_point>::
 	The name of a commit at which to start the new branch; see
-	linkgit:git-branch[1] for details. Defaults to HEAD.
+	linkgit:git-branch[1] for details. Defaults to `HEAD`.
 
 <tree-ish>::
 	Tree to checkout from (when paths are given). If not specified,
@@ -329,9 +329,9 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 
 DETACHED HEAD
 -------------
-HEAD normally refers to a named branch (e.g. 'master'). Meanwhile, each
+`HEAD` normally refers to a named branch (e.g. `master`). Meanwhile, each
 branch refers to a specific commit. Let's look at a repo with three
-commits, one of them tagged, and with branch 'master' checked out:
+commits, one of them tagged, and with branch `master` checked out:
 
 ------------
            HEAD (refers to branch 'master')
@@ -344,10 +344,10 @@ a---b---c  branch 'master' (refers to commit 'c')
 ------------
 
 When a commit is created in this state, the branch is updated to refer to
-the new commit. Specifically, 'git commit' creates a new commit 'd', whose
-parent is commit 'c', and then updates branch 'master' to refer to new
-commit 'd'. HEAD still refers to branch 'master' and so indirectly now refers
-to commit 'd':
+the new commit. Specifically, 'git commit' creates a new commit `d`, whose
+parent is commit `c`, and then updates branch `master` to refer to new
+commit `d`. `HEAD` still refers to branch `master` and so indirectly now refers
+to commit `d`:
 
 ------------
 $ edit; git add; git commit
@@ -364,7 +364,7 @@ a---b---c---d  branch 'master' (refers to commit 'd')
 It is sometimes useful to be able to checkout a commit that is not at
 the tip of any named branch, or even to create a new commit that is not
 referenced by a named branch. Let's look at what happens when we
-checkout commit 'b' (here we show two ways this may be done):
+checkout commit `b` (here we show two ways this may be done):
 
 ------------
 $ git checkout v2.0  # or
@@ -379,9 +379,9 @@ a---b---c---d  branch 'master' (refers to commit 'd')
   tag 'v2.0' (refers to commit 'b')
 ------------
 
-Notice that regardless of which checkout command we use, HEAD now refers
-directly to commit 'b'. This is known as being in detached HEAD state.
-It means simply that HEAD refers to a specific commit, as opposed to
+Notice that regardless of which checkout command we use, `HEAD` now refers
+directly to commit `b`. This is known as being in detached `HEAD` state.
+It means simply that `HEAD` refers to a specific commit, as opposed to
 referring to a named branch. Let's see what happens when we create a commit:
 
 ------------
@@ -398,7 +398,7 @@ a---b---c---d  branch 'master' (refers to commit 'd')
   tag 'v2.0' (refers to commit 'b')
 ------------
 
-There is now a new commit 'e', but it is referenced only by HEAD. We can
+There is now a new commit `e`, but it is referenced only by `HEAD`. We can
 of course add yet another commit in this state:
 
 ------------
@@ -416,7 +416,7 @@ a---b---c---d  branch 'master' (refers to commit 'd')
 ------------
 
 In fact, we can perform all the normal Git operations. But, let's look
-at what happens when we then checkout master:
+at what happens when we then checkout `master`:
 
 ------------
 $ git checkout master
@@ -431,9 +431,9 @@ a---b---c---d  branch 'master' (refers to commit 'd')
 ------------
 
 It is important to realize that at this point nothing refers to commit
-'f'. Eventually commit 'f' (and by extension commit 'e') will be deleted
+`f`. Eventually commit `f` (and by extension commit `e`) will be deleted
 by the routine Git garbage collection process, unless we create a reference
-before that happens. If we have not yet moved away from commit 'f',
+before that happens. If we have not yet moved away from commit `f`,
 any of these will create a reference to it:
 
 ------------
@@ -442,19 +442,19 @@ $ git branch foo        <2>
 $ git tag foo           <3>
 ------------
 
-<1> creates a new branch 'foo', which refers to commit 'f', and then
-    updates HEAD to refer to branch 'foo'. In other words, we'll no longer
-    be in detached HEAD state after this command.
+<1> creates a new branch `foo`, which refers to commit `f`, and then
+    updates `HEAD` to refer to branch `foo`. In other words, we'll no longer
+    be in detached `HEAD` state after this command.
 
-<2> similarly creates a new branch 'foo', which refers to commit 'f',
-    but leaves HEAD detached.
+<2> similarly creates a new branch `foo`, which refers to commit `f`,
+    but leaves `HEAD` detached.
 
-<3> creates a new tag 'foo', which refers to commit 'f',
-    leaving HEAD detached.
+<3> creates a new tag `foo`, which refers to commit `f`,
+    leaving `HEAD` detached.
 
-If we have moved away from commit 'f', then we must first recover its object
+If we have moved away from commit `f`, then we must first recover its object
 name (typically by using git reflog), and then we can create a reference to
-it. For example, to see the last two commits to which HEAD referred, we
+it. For example, to see the last two commits to which `HEAD` referred, we
 can use either of these commands:
 
 ------------
@@ -465,12 +465,12 @@ $ git log -g -2 HEAD
 ARGUMENT DISAMBIGUATION
 -----------------------
 
-When there is only one argument given and it is not `--` (e.g. "git
-checkout abc"), and when the argument is both a valid `<tree-ish>`
-(e.g. a branch "abc" exists) and a valid `<pathspec>` (e.g. a file
+When there is only one argument given and it is not `--` (e.g. `git
+checkout abc`), and when the argument is both a valid `<tree-ish>`
+(e.g. a branch `abc` exists) and a valid `<pathspec>` (e.g. a file
 or a directory whose name is "abc" exists), Git would usually ask
 you to disambiguate.  Because checking out a branch is so common an
-operation, however, "git checkout abc" takes "abc" as a `<tree-ish>`
+operation, however, `git checkout abc` takes "abc" as a `<tree-ish>`
 in such a situation.  Use `git checkout -- <pathspec>` if you want
 to checkout these paths out of the index.
 
@@ -478,7 +478,7 @@ EXAMPLES
 --------
 
 . The following sequence checks out the `master` branch, reverts
-  the `Makefile` to two revisions back, deletes hello.c by
+  the `Makefile` to two revisions back, deletes `hello.c` by
   mistake, and gets it back from the index.
 +
 ------------
@@ -490,7 +490,7 @@ $ git checkout hello.c            <3>
 +
 <1> switch branch
 <2> take a file out of another commit
-<3> restore hello.c from the index
+<3> restore `hello.c` from the index
 +
 If you want to check out _all_ C source files out of the index,
 you can say
@@ -519,7 +519,7 @@ $ git checkout -- hello.c
 $ git checkout mytopic
 ------------
 +
-However, your "wrong" branch and correct "mytopic" branch may
+However, your "wrong" branch and correct `mytopic` branch may
 differ in files that you have modified locally, in which case
 the above checkout would fail like this:
 +
-- 
2.21.0.548.gd3c7d92dc2
Martin Ågren· Mar 17, 2019, 19:21 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v4 04/26] git-checkout.txt: fix monospace typeset

On Sun, 17 Mar 2019 at 13:50, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 5 quoted lines
>
> Add backticks where we have none, replace single quotes with backticks
> and replace double-quotes. Drop double-quotes from nested constructions
> such as `"@{-1}"`. Add a missing possessive apostrophe after the word
> "submodules" while at it.

Assuming you move those "submodules" hunks from an earlier commit, this last part needs some tweaking since you're not adding a "'", but you actually rephrase the docs to avoid a buggy AsciiDoc rendering. (And if a while-at-it needs too much explaining, maybe just skip it altogether? I dunno.)

Other than some things appearing in the wrong commit(?), this matches the "how about this?" I sent for v3 and our discussion of that.

Martin
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 05/26] t: rename t2014-switch.sh to t2014-checkout-switch.sh

The old name does not really say that this is about 'checkout -b'. See 49d833dc07 (Revert "checkout branch: prime cache-tree fully" - 2009-05-12) for more information

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 t/{t2014-switch.sh => t2014-checkout-switch.sh} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
Show changes to t/t2014-switch.sh +0 −0
diff --git a/t/t2014-switch.sh b/t/t2014-checkout-switch.sh
similarity index 100%
rename from t/t2014-switch.sh
rename to t/t2014-checkout-switch.sh
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 06/26] checkout: advice how to get out of detached HEAD mode

Detached HEAD mode is considered dangerous and confusing for newcomers and we print a big block of warning how to move forward. But we should also suggest the user the way to get out of it if they get into detached HEAD by mistake.

While at there, I also suggest how to turn the advice off. This is another thing I find annoying with advices and should be dealt with in a more generic way. But that may require some refactoring in advice.c first.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 advice.c                   | 17 +++++++++++++----
 t/t2020-checkout-detach.sh | 12 ++++++++++++
 2 files changed, 25 insertions(+), 4 deletions(-)
Show changes to 2 files +25 −4

advice.c, t/t2020-checkout-detach.sh

diff --git a/advice.c b/advice.c
index 567209aa79..b224825637 100644
--- a/advice.c
+++ b/advice.c
@@ -191,13 +191,22 @@ void NORETURN die_conclude_merge(void)
 void detach_advice(const char *new_name)
 {
 	const char *fmt =
-	_("Note: checking out '%s'.\n\n"
+	_("Note: checking out '%s'.\n"
+	"\n"
 	"You are in 'detached HEAD' state. You can look around, make experimental\n"
 	"changes and commit them, and you can discard any commits you make in this\n"
-	"state without impacting any branches by performing another checkout.\n\n"
+	"state without impacting any branches by performing another checkout.\n"
+	"\n"
 	"If you want to create a new branch to retain commits you create, you may\n"
-	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
-	"  git checkout -b <new-branch-name>\n\n");
+	"do so (now or later) by using -b with the checkout command again. Example:\n"
+	"\n"
+	"  git checkout -b <new-branch-name>\n"
+	"\n"
+	"Or undo this checkout with:\n"
+	"\n"
+	"  git checkout -\n"
+	"\n"
+	"Turn off this advice by setting config variable advice.detachedHead to false\n\n");
 
 	fprintf(stderr, fmt, new_name);
 }
diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
index 1fa670625c..743c7248a2 100755
--- a/t/t2020-checkout-detach.sh
+++ b/t/t2020-checkout-detach.sh
@@ -206,6 +206,12 @@ test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not as
 
 	  git checkout -b <new-branch-name>
 
+	Or undo this checkout with:
+
+	  git checkout -
+
+	Turn off this advice by setting config variable advice.detachedHead to false
+
 	HEAD is now at \$commit three
 	EOF
 
@@ -282,6 +288,12 @@ test_expect_success 'describe_detached_head does print SHA-1 ellipsis when asked
 
 	  git checkout -b <new-branch-name>
 
+	Or undo this checkout with:
+
+	  git checkout -
+
+	Turn off this advice by setting config variable advice.detachedHead to false
+
 	HEAD is now at \$commit... three
 	EOF
 
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 07/26] checkout: keep most #include sorted

The include list becomes very long and frankly a bit unorganized. With the exception of builtin.h, cache.h or git-compat-util.h which have to come first, keep the rest sorted.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 34 +++++++++++++++++-----------------
 1 file changed, 17 insertions(+), 17 deletions(-)
Show changes to builtin/checkout.c +17 −17
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 0e6037b296..f7801db2fb 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1,30 +1,30 @@
 #define USE_THE_INDEX_COMPATIBILITY_MACROS
 #include "builtin.h"
-#include "config.h"
+#include "advice.h"
+#include "blob.h"
+#include "branch.h"
+#include "cache-tree.h"
 #include "checkout.h"
+#include "commit.h"
+#include "config.h"
+#include "diff.h"
+#include "dir.h"
+#include "ll-merge.h"
 #include "lockfile.h"
+#include "merge-recursive.h"
+#include "object-store.h"
 #include "parse-options.h"
 #include "refs.h"
-#include "object-store.h"
-#include "commit.h"
+#include "remote.h"
+#include "resolve-undo.h"
+#include "revision.h"
+#include "run-command.h"
+#include "submodule.h"
+#include "submodule-config.h"
 #include "tree.h"
 #include "tree-walk.h"
-#include "cache-tree.h"
 #include "unpack-trees.h"
-#include "dir.h"
-#include "run-command.h"
-#include "merge-recursive.h"
-#include "branch.h"
-#include "diff.h"
-#include "revision.h"
-#include "remote.h"
-#include "blob.h"
 #include "xdiff-interface.h"
-#include "ll-merge.h"
-#include "resolve-undo.h"
-#include "submodule-config.h"
-#include "submodule.h"
-#include "advice.h"
 
 static int checkout_optimize_new_branch;
 
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 08/26] checkout: factor out some code in parse_branchname_arg()

This is in preparation for the new command restore, which also needs to parse opts->source_tree but does not need all the disambiguation logic.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 51 ++++++++++++++++++++++++++++------------------
 1 file changed, 31 insertions(+), 20 deletions(-)
Show changes to builtin/checkout.c +31 −20
diff --git a/builtin/checkout.c b/builtin/checkout.c
index f7801db2fb..1ce0446bc6 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1081,6 +1081,34 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
 	return git_xmerge_config(var, value, NULL);
 }
 
+static void setup_new_branch_info_and_source_tree(
+	struct branch_info *new_branch_info,
+	struct checkout_opts *opts,
+	struct object_id *rev,
+	const char *arg)
+{
+	struct tree **source_tree = &opts->source_tree;
+	struct object_id branch_rev;
+
+	new_branch_info->name = arg;
+	setup_branch_path(new_branch_info);
+
+	if (!check_refname_format(new_branch_info->path, 0) &&
+	    !read_ref(new_branch_info->path, &branch_rev))
+		oidcpy(rev, &branch_rev);
+	else
+		new_branch_info->path = NULL; /* not an existing branch */
+
+	new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
+	if (!new_branch_info->commit) {
+		/* not a commit */
+		*source_tree = parse_tree_indirect(rev);
+	} else {
+		parse_commit_or_die(new_branch_info->commit);
+		*source_tree = get_commit_tree(new_branch_info->commit);
+	}
+}
+
 static int parse_branchname_arg(int argc, const char **argv,
 				int dwim_new_local_branch_ok,
 				struct branch_info *new_branch_info,
@@ -1088,10 +1116,8 @@ static int parse_branchname_arg(int argc, const char **argv,
 				struct object_id *rev,
 				int *dwim_remotes_matched)
 {
-	struct tree **source_tree = &opts->source_tree;
 	const char **new_branch = &opts->new_branch;
 	int argcount = 0;
-	struct object_id branch_rev;
 	const char *arg;
 	int dash_dash_pos;
 	int has_dash_dash = 0;
@@ -1213,26 +1239,11 @@ static int parse_branchname_arg(int argc, const char **argv,
 	argv++;
 	argc--;
 
-	new_branch_info->name = arg;
-	setup_branch_path(new_branch_info);
-
-	if (!check_refname_format(new_branch_info->path, 0) &&
-	    !read_ref(new_branch_info->path, &branch_rev))
-		oidcpy(rev, &branch_rev);
-	else
-		new_branch_info->path = NULL; /* not an existing branch */
+	setup_new_branch_info_and_source_tree(new_branch_info, opts, rev, arg);
 
-	new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
-	if (!new_branch_info->commit) {
-		/* not a commit */
-		*source_tree = parse_tree_indirect(rev);
-	} else {
-		parse_commit_or_die(new_branch_info->commit);
-		*source_tree = get_commit_tree(new_branch_info->commit);
-	}
-
-	if (!*source_tree)                   /* case (1): want a tree */
+	if (!opts->source_tree)                   /* case (1): want a tree */
 		die(_("reference is not a tree: %s"), arg);
+
 	if (!has_dash_dash) {	/* case (3).(d) -> (1) */
 		/*
 		 * Do not complain the most common case
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 09/26] checkout: make "opts" in cmd_checkout() a pointer

"opts" will soon be moved out of cmd_checkout(). To keep changes in that patch smaller, convert "opts" to a pointer and keep the real thing behind "real_opts".

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 115 +++++++++++++++++++++++----------------------
 1 file changed, 58 insertions(+), 57 deletions(-)
Show changes to builtin/checkout.c +58 −57
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 1ce0446bc6..963f10b05b 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1341,82 +1341,83 @@ static int checkout_branch(struct checkout_opts *opts,
 
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
-	struct checkout_opts opts;
+	struct checkout_opts real_opts;
+	struct checkout_opts *opts = &real_opts;
 	struct branch_info new_branch_info;
 	char *conflict_style = NULL;
 	int dwim_new_local_branch, no_dwim_new_local_branch = 0;
 	int dwim_remotes_matched = 0;
 	struct option options[] = {
-		OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
-		OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
+		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
+		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"),
+		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"),
+		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),
-		OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
-		OPT_SET_INT_F('2', "ours", &opts.writeout_stage,
+		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
+		OPT_SET_INT_F('2', "ours", &opts->writeout_stage,
 			      N_("checkout our version for unmerged files"),
 			      2, PARSE_OPT_NONEG),
-		OPT_SET_INT_F('3', "theirs", &opts.writeout_stage,
+		OPT_SET_INT_F('3', "theirs", &opts->writeout_stage,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
-		OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)"),
+		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")),
-		OPT_BOOL_F(0, "overwrite-ignore", &opts.overwrite_ignore,
+		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
+		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
 		OPT_STRING(0, "conflict", &conflict_style, N_("style"),
 			   N_("conflict style (merge or diff3)")),
-		OPT_BOOL('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
-		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree,
+		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_BOOL(0, "no-guess", &no_dwim_new_local_branch,
 			 N_("do not second guess 'git checkout <no-such-branch>'")),
-		OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
+		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
 		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
 			    "checkout", "control recursive updating of submodules",
 			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
-		OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
-		OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")),
+		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
+		OPT_BOOL(0, "overlay", &opts->overlay_mode, N_("use overlay mode (default)")),
 		OPT_END(),
 	};
 
-	memset(&opts, 0, sizeof(opts));
+	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->overwrite_ignore = 1;
+	opts->prefix = prefix;
+	opts->show_progress = -1;
+	opts->overlay_mode = -1;
 
-	git_config(git_checkout_config, &opts);
+	git_config(git_checkout_config, opts);
 
-	opts.track = BRANCH_TRACK_UNSPECIFIED;
+	opts->track = BRANCH_TRACK_UNSPECIFIED;
 
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
 	dwim_new_local_branch = !no_dwim_new_local_branch;
-	if (opts.show_progress < 0) {
-		if (opts.quiet)
-			opts.show_progress = 0;
+	if (opts->show_progress < 0) {
+		if (opts->quiet)
+			opts->show_progress = 0;
 		else
-			opts.show_progress = isatty(2);
+			opts->show_progress = isatty(2);
 	}
 
 	if (conflict_style) {
-		opts.merge = 1; /* implied */
+		opts->merge = 1; /* implied */
 		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
 	}
 
-	if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)
+	if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
 		die(_("-b, -B and --orphan are mutually exclusive"));
 
-	if (opts.overlay_mode == 1 && opts.patch_mode)
+	if (opts->overlay_mode == 1 && opts->patch_mode)
 		die(_("-p and --overlay are mutually exclusive"));
 
 	/*
@@ -1424,14 +1425,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	 * and new_branch_force and new_orphan_branch will tell us which one of
 	 * -b/-B/--orphan is being used.
 	 */
-	if (opts.new_branch_force)
-		opts.new_branch = opts.new_branch_force;
+	if (opts->new_branch_force)
+		opts->new_branch = opts->new_branch_force;
 
-	if (opts.new_orphan_branch)
-		opts.new_branch = opts.new_orphan_branch;
+	if (opts->new_orphan_branch)
+		opts->new_branch = opts->new_orphan_branch;
 
 	/* --track without -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];
 		if (!argc || !strcmp(argv0, "--"))
 			die(_("--track needs a branch name"));
@@ -1440,7 +1441,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		argv0 = strchr(argv0, '/');
 		if (!argv0 || !argv0[1])
 			die(_("missing branch name; try -b"));
-		opts.new_branch = argv0 + 1;
+		opts->new_branch = argv0 + 1;
 	}
 
 	/*
@@ -1459,56 +1460,56 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	if (argc) {
 		struct object_id rev;
 		int dwim_ok =
-			!opts.patch_mode &&
+			!opts->patch_mode &&
 			dwim_new_local_branch &&
-			opts.track == BRANCH_TRACK_UNSPECIFIED &&
-			!opts.new_branch;
+			opts->track == BRANCH_TRACK_UNSPECIFIED &&
+			!opts->new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
-					     &new_branch_info, &opts, &rev,
+					     &new_branch_info, opts, &rev,
 					     &dwim_remotes_matched);
 		argv += n;
 		argc -= n;
 	}
 
 	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)
+		if (opts->new_branch && argc == 1)
 			die(_("'%s' is not a commit and a branch '%s' cannot be created from it"),
-				argv[0], opts.new_branch);
+				argv[0], opts->new_branch);
 
-		if (opts.force_detach)
+		if (opts->force_detach)
 			die(_("git checkout: --detach does not take a path argument '%s'"),
 			    argv[0]);
 
-		if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
+		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.new_branch) {
+	if (opts->new_branch) {
 		struct strbuf buf = STRBUF_INIT;
 
-		if (opts.new_branch_force)
-			opts.branch_exists = validate_branchname(opts.new_branch, &buf);
+		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);
+			opts->branch_exists =
+				validate_new_branchname(opts->new_branch, &buf, 0);
 		strbuf_release(&buf);
 	}
 
 	UNLEAK(opts);
-	if (opts.patch_mode || opts.pathspec.nr) {
-		int ret = checkout_paths(&opts, new_branch_info.name);
+	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"
@@ -1527,6 +1528,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			       dwim_remotes_matched);
 		return ret;
 	} else {
-		return checkout_branch(&opts, &new_branch_info);
+		return checkout_branch(opts, &new_branch_info);
 	}
 }
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 10/26] checkout: move 'confict_style' and 'dwim_..' to checkout_opts

These local variables are referenced by struct option[]. This struct will soon be broken down, moved away and we can't rely on local variables anymore. Move these two to struct checkout_opts in preparation for that.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)
Show changes to builtin/checkout.c +10 −7
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 963f10b05b..b6fc1fc69f 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -47,6 +47,8 @@ struct checkout_opts {
 	int show_progress;
 	int count_checkout_paths;
 	int overlay_mode;
+	int no_dwim_new_local_branch;
+
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
 	 * should be updated accordingly.
@@ -58,6 +60,7 @@ struct checkout_opts {
 	int new_branch_log;
 	enum branch_track track;
 	struct diff_options diff_options;
+	char *conflict_style;
 
 	int branch_exists;
 	const char *prefix;
@@ -1344,8 +1347,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	struct checkout_opts real_opts;
 	struct checkout_opts *opts = &real_opts;
 	struct branch_info new_branch_info;
-	char *conflict_style = NULL;
-	int dwim_new_local_branch, no_dwim_new_local_branch = 0;
+	int dwim_new_local_branch;
 	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
@@ -1370,12 +1372,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_STRING(0, "conflict", &conflict_style, N_("style"),
+		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
 			   N_("conflict style (merge or diff3)")),
 		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_BOOL(0, "no-guess", &no_dwim_new_local_branch,
+		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
 			 N_("do not second guess 'git checkout <no-such-branch>'")),
 		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
@@ -1393,6 +1395,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts->prefix = prefix;
 	opts->show_progress = -1;
 	opts->overlay_mode = -1;
+	opts->no_dwim_new_local_branch = 0;
 
 	git_config(git_checkout_config, opts);
 
@@ -1401,7 +1404,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
-	dwim_new_local_branch = !no_dwim_new_local_branch;
+	dwim_new_local_branch = !opts->no_dwim_new_local_branch;
 	if (opts->show_progress < 0) {
 		if (opts->quiet)
 			opts->show_progress = 0;
@@ -1409,9 +1412,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			opts->show_progress = isatty(2);
 	}
 
-	if (conflict_style) {
+	if (opts->conflict_style) {
 		opts->merge = 1; /* implied */
-		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
+		git_xmerge_config("merge.conflictstyle", opts->conflict_style, NULL);
 	}
 
 	if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 11/26] checkout: split options[] array in three pieces

This is a preparation step for introducing new commands that do parts of what checkout does. There will be two new commands, one is about switching branches, detaching HEAD... one about checking out paths. These share the a subset of command line options. The rest of command line options are separate.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 82 +++++++++++++++++++++++++++++++++-------------
 parse-options-cb.c | 17 ++++++++++
 parse-options.h    |  1 +
 3 files changed, 77 insertions(+), 23 deletions(-)
Show changes to 3 files +77 −23

builtin/checkout.c, parse-options-cb.c, parse-options.h

diff --git a/builtin/checkout.c b/builtin/checkout.c
index b6fc1fc69f..cf1d70511e 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1342,15 +1342,31 @@ static int checkout_branch(struct checkout_opts *opts,
 	return switch_branches(opts, new_branch_info);
 }
 
-int cmd_checkout(int argc, const char **argv, const char *prefix)
+static struct option *add_common_options(struct checkout_opts *opts,
+					 struct option *prevopts)
 {
-	struct checkout_opts real_opts;
-	struct checkout_opts *opts = &real_opts;
-	struct branch_info new_branch_info;
-	int dwim_new_local_branch;
-	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
+		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
+			    "checkout", "control recursive updating of submodules",
+			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
+		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
+		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
+			   PARSE_OPT_NOCOMPLETE),
+		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
+		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
+			   N_("conflict style (merge or diff3)")),
+		OPT_END()
+	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+static struct option *add_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"),
@@ -1360,34 +1376,49 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
 			BRANCH_TRACK_EXPLICIT),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
+		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
+			   N_("update ignored files (default)"),
+			   PARSE_OPT_NOCOMPLETE),
+		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
+			 N_("second guess 'git checkout <no-such-branch>'")),
+		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
+			 N_("do not check if another worktree is holding the given ref")),
+		OPT_END()
+	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+static struct option *add_checkout_path_options(struct checkout_opts *opts,
+						struct option *prevopts)
+{
+	struct option options[] = {
 		OPT_SET_INT_F('2', "ours", &opts->writeout_stage,
 			      N_("checkout our version for unmerged files"),
 			      2, PARSE_OPT_NONEG),
 		OPT_SET_INT_F('3', "theirs", &opts->writeout_stage,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
-		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
-			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
-		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
-			   N_("update ignored files (default)"),
-			   PARSE_OPT_NOCOMPLETE),
-		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
-			   N_("conflict style (merge or diff3)")),
 		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_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
-			 N_("do not second guess 'git checkout <no-such-branch>'")),
-		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
-			 N_("do not check if another worktree is holding the given ref")),
-		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
-			    "checkout", "control recursive updating of submodules",
-			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
-		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
 		OPT_BOOL(0, "overlay", &opts->overlay_mode, N_("use overlay mode (default)")),
-		OPT_END(),
+		OPT_END()
 	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+int cmd_checkout(int argc, const char **argv, const char *prefix)
+{
+	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));
@@ -1401,6 +1432,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	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,
 			     PARSE_OPT_KEEP_DASHDASH);
 
diff --git a/parse-options-cb.c b/parse-options-cb.c
index 2733393546..caaeed896f 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -122,6 +122,23 @@ int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
 	return 0;
 }
 
+struct option *parse_options_dup(const struct option *o)
+{
+	struct option *opts;
+	int nr = 0;
+
+	while (o && o->type != OPTION_END) {
+		nr++;
+		o++;
+	}
+
+	ALLOC_ARRAY(opts, nr + 1);
+	memcpy(opts, o - nr, sizeof(*o) * nr);
+	memset(opts + nr, 0, sizeof(*opts));
+	opts[nr].type = OPTION_END;
+	return opts;
+}
+
 struct option *parse_options_concat(struct option *a, struct option *b)
 {
 	struct option *ret;
diff --git a/parse-options.h b/parse-options.h
index 7d83e2971d..9a90c332a5 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -257,6 +257,7 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
 
 int parse_options_end(struct parse_opt_ctx_t *ctx);
 
+struct option *parse_options_dup(const struct option *a);
 struct option *parse_options_concat(struct option *a, struct option *b);
 
 /*----- some often used options -----*/
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 12/26] checkout: split part of it to new command 'switch'

"git checkout" doing too many things is a source of confusion for many users (and it even bites old timers sometimes). To remedy that, the command will be split into two new ones: switch and restore. The good old "git checkout" command is still here and will be until all (or most of users) are sick of it.

See the new man page for the final design of switch. The actual implementation though is still pretty much the same as "git checkout" and not completely aligned with the man page. Following patches will adjust their behavior to match the man page.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 .gitignore                         |   1 +
 Documentation/config/advice.txt    |  13 +-
 Documentation/config/branch.txt    |   4 +-
 Documentation/config/checkout.txt  |   9 +-
 Documentation/config/diff.txt      |   3 +-
 Documentation/git-checkout.txt     |   4 +
 Documentation/git-switch.txt (new) | 277 +++++++++++++++++++++++++++++
 Documentation/gitattributes.txt    |   3 +-
 Documentation/githooks.txt         |   8 +-
 Makefile                           |   1 +
 builtin.h                          |   1 +
 builtin/checkout.c                 |  60 +++++--
 command-list.txt                   |   1 +
 git.c                              |   1 +
 14 files changed, 359 insertions(+), 27 deletions(-)
Show changes to 14 files +359 −27

.gitignore, Documentation/config/advice.txt, Documentation/config/branch.txt, Documentation/config/checkout.txt, Documentation/config/diff.txt, Documentation/git-checkout.txt, Documentation/git-switch.txt, Documentation/gitattributes.txt, Documentation/githooks.txt, Makefile, builtin.h, builtin/checkout.c, command-list.txt, git.c

diff --git a/.gitignore b/.gitignore
index 7374587f9d..c687b92b1c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -167,6 +167,7 @@
 /git-submodule
 /git-submodule--helper
 /git-svn
+/git-switch
 /git-symbolic-ref
 /git-tag
 /git-unpack-file
diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
index 88620429ea..239d479506 100644
--- a/Documentation/config/advice.txt
+++ b/Documentation/config/advice.txt
@@ -42,7 +42,8 @@ advice.*::
 		state in the output of linkgit:git-status[1], in
 		the template shown when writing commit messages in
 		linkgit:git-commit[1], and in the help message shown
-		by linkgit:git-checkout[1] when switching branch.
+		by linkgit:git-switch[1] or
+		linkgit:git-checkout[1] when switching branch.
 	statusUoption::
 		Advise to consider using the `-u` option to linkgit:git-status[1]
 		when the command takes more than 2 seconds to enumerate untracked
@@ -62,12 +63,14 @@ advice.*::
 		your information is guessed from the system username and
 		domain name.
 	detachedHead::
-		Advice shown when you used linkgit:git-checkout[1] to
-		move to the detach HEAD state, to instruct how to create
-		a local branch after the fact.
+		Advice shown when you used
+		linkgit:git-switch[1] or linkgit:git-checkout[1]
+		to move to the detach HEAD state, to instruct how to
+		create a local branch after the fact.
 	checkoutAmbiguousRemoteBranchName::
 		Advice shown when the argument to
-		linkgit:git-checkout[1] ambiguously resolves to a
+		linkgit:git-checkout[1] and linkgit:git-switch[1]
+		ambiguously resolves to a
 		remote tracking branch on more than one remote in
 		situations where an unambiguous argument would have
 		otherwise caused a remote-tracking branch to be
diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index 019d60ede2..8050466159 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -1,5 +1,5 @@
 branch.autoSetupMerge::
-	Tells 'git branch' and 'git checkout' to set up new branches
+	Tells 'git branch', 'git switch' and 'git checkout' to set up new branches
 	so that linkgit:git-pull[1] will appropriately merge from the
 	starting point branch. Note that even if this option is not set,
 	this behavior can be chosen per-branch using the `--track`
@@ -11,7 +11,7 @@ branch.autoSetupMerge::
 	branch. This option defaults to true.
 
 branch.autoSetupRebase::
-	When a new branch is created with 'git branch' or 'git checkout'
+	When a new branch is created with 'git branch', 'git switch' or 'git checkout'
 	that tracks another branch, this variable tells Git to set
 	up pull to rebase instead of merge (see "branch.<name>.rebase").
 	When `never`, rebase is never automatically set to true.
diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt
index c4118fa196..d6872ffa83 100644
--- a/Documentation/config/checkout.txt
+++ b/Documentation/config/checkout.txt
@@ -1,5 +1,6 @@
 checkout.defaultRemote::
-	When you run 'git checkout <something>' and only have one
+	When you run 'git checkout <something>'
+	or 'git switch <something>' and only have one
 	remote, it may implicitly fall back on checking out and
 	tracking e.g. 'origin/<something>'. This stops working as soon
 	as you have more than one remote with a '<something>'
@@ -8,8 +9,10 @@ checkout.defaultRemote::
 	disambiguation. The typical use-case is to set this to
 	`origin`.
 +
-Currently this is used by linkgit:git-checkout[1] when 'git checkout
-<something>' will checkout the '<something>' branch on another remote,
+Currently this is used by linkgit:git-switch[1] and
+linkgit:git-checkout[1] when 'git checkout <something>'
+or 'git switch <something>'
+will checkout the '<something>' branch on another remote,
 and by linkgit:git-worktree[1] when 'git worktree add' refers to a
 remote branch. This setting might be used for other checkout-like
 commands or functionality in the future.
diff --git a/Documentation/config/diff.txt b/Documentation/config/diff.txt
index e48bb987d7..b3b304ee12 100644
--- a/Documentation/config/diff.txt
+++ b/Documentation/config/diff.txt
@@ -78,7 +78,8 @@ diff.external::
 diff.ignoreSubmodules::
 	Sets the default value of --ignore-submodules. Note that this
 	affects only 'git diff' Porcelain, and not lower level 'diff'
-	commands such as 'git diff-files'. 'git checkout' also honors
+	commands such as 'git diff-files'. 'git checkout'
+	and 'git switch' also honor
 	this setting when reporting uncommitted changes. Setting it to
 	'all' disables the submodule summary normally shown by 'git commit'
 	and 'git status' when `status.submoduleSummary` is set unless it is
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index bf90966c95..c7192bdefe 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -560,6 +560,10 @@ $ edit frotz
 $ git add frotz
 ------------
 
+SEE ALSO
+--------
+linkgit:git-switch[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
new file mode 100644
index 0000000000..74415f12ab
--- /dev/null
+++ b/Documentation/git-switch.txt
@@ -0,0 +1,277 @@
+git-switch(1)
+=============
+
+NAME
+----
+git-switch - Switch branches
+
+SYNOPSIS
+--------
+[verse]
+'git switch' [<options>] [--guess] <branch>
+'git switch' [<options>] --detach [<start-point>]
+'git switch' [<options>] (-c|-C|--orphan) <new-branch> [<start-point>]
+
+DESCRIPTION
+-----------
+Switch to a specified branch. The working tree and the index are
+updated to match the branch. All new commits will be added to the tip
+of this branch.
+
+Optionally a new branch could be created with either `-c`, `-C`,
+automatically from a remote branch of same name (see `--guess`), or
+detach the working tree from any branch with `--detach`, along with
+switching.
+
+Switching branches does not require a clean index and working tree
+(i.e. no differences compared to `HEAD`). The operation is aborted
+however if the switch leads to loss of local changes, unless told
+otherwise.
+
+OPTIONS
+-------
+<branch>::
+	Branch to switch to.
+
+<new-branch>::
+	Name for the new branch.
+
+<start-point>::
+	The starting point for the new branch. Specifying a
+	`<start-point>` allows you to create a branch based on some
+	other point in history than where HEAD currently points. (Or,
+	in the case of `--detach`, allows you to inspect and detach
+	from some other point.)
++
+You can use the `@{-N}` syntax to refer to the N-th last
+branch/commit switched to "git switch" or "git checkout"
+operation. You may also specify `-` which is synonymous to `@{-1}`.
+This is often used to switch quickly between two branches, or to undo
+a branch switch by mistake.
++
+As a special case, you may use `A...B` as a shortcut for the merge
+base of `A` and `B` if there is exactly one merge base. You can leave
+out at most one of `A` and `B`, in which case it defaults to `HEAD`.
+
+-c <new-branch>::
+--create <new-branch>::
+	Create a new branch named `<new-branch>` starting at
+	`<start-point>` before switching to the branch. This is a
+	convenient shortcut for:
++
+------------
+$ git branch <new-branch>
+$ git switch <new-branch>
+------------
+
+-C <new-branch>::
+--force-create <new-branch>::
+	Similar to `--create` except that if `<new-branch>` already
+	exists, it will be reset to `<start-point>`. This is a
+	convenient shortcut for:
++
+------------
+$ git branch -f <new-branch>
+$ git switch <new-branch>
+------------
+
+-d::
+--detach::
+	Switch to a commit for inspection and discardable
+	experiments. See the "DETACHED HEAD" section in
+	linkgit:git-checkout[1] for details.
+
+--guess::
+--no-guess::
+	If `<branch>` is not found but there does exist a tracking
+	branch in exactly one remote (call it `<remote>`) with a
+	matching name, treat as equivalent to
++
+------------
+$ git switch -c <branch> --track <remote>/<branch>
+------------
++
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to e.g. `checkout.defaultRemote=origin`
+to always checkout remote branches from there if `<branch>` is
+ambiguous but exists on the 'origin' remote. See also
+`checkout.defaultRemote` in linkgit:git-config[1].
++
+`--guess` is the default behavior. Use `--no-guess` to disable it.
+
+-f::
+--force::
+	An alias for `--discard-changes`.
+
+--discard-changes::
+	Proceed even if the index or the working tree differs from
+	`HEAD`. Both the index and working tree are restored to match
+	the switching target. This is used to throw away local
+	changes.
+
+-m::
+--merge::
+	If you have local modifications to one or more files that are
+	different between the current branch and the branch to which
+	you are switching, the command refuses to switch branches in
+	order to preserve your modifications in context.  However,
+	with this option, a three-way merge between the current
+	branch, your working tree contents, and the new branch is
+	done, and you will be on the new branch.
++
+When a merge conflict happens, the index entries for conflicting
+paths are left unmerged, and you need to resolve the conflicts
+and mark the resolved paths with `git add` (or `git rm` if the merge
+should result in deletion of the path).
+
+--conflict=<style>::
+	The same as `--merge` option above, but changes the way the
+	conflicting hunks are presented, overriding the
+	`merge.conflictStyle` configuration variable.  Possible values are
+	"merge" (default) and "diff3" (in addition to what is shown by
+	"merge" style, shows the original contents).
+
+-q::
+--quiet::
+	Quiet, suppress feedback messages.
+
+--progress::
+--no-progress::
+	Progress status is reported on the standard error stream
+	by default when it is attached to a terminal, unless `--quiet`
+	is specified. This flag enables progress reporting even if not
+	attached to a terminal, regardless of `--quiet`.
+
+-t::
+--track::
+	When creating a new branch, set up "upstream" configuration.
+	`-c` is implied. See `--track` in linkgit:git-branch[1] for
+	details.
++
+If no `-c` option is given, the name of the new branch will be derived
+from the remote-tracking branch, by looking at the local part of the
+refspec configured for the corresponding remote, and then stripping
+the initial part up to the "*".  This would tell us to use `hack` as
+the local branch when branching off of `origin/hack` (or
+`remotes/origin/hack`, or even `refs/remotes/origin/hack`).  If the
+given name has no slash, or the above guessing results in an empty
+name, the guessing is aborted.  You can explicitly give a name with
+`-c` in such a case.
+
+--no-track::
+	Do not set up "upstream" configuration, even if the
+	`branch.autoSetupMerge` configuration variable is true.
+
+--orphan <new-branch>::
+	Create a new 'orphan' branch, named `<new-branch>`. If
+	`<start-point>` is specified, the index and working tree are
+	adjusted to match it. Otherwise both are adjusted to contain no
+	tracked files.
+
+--ignore-other-worktrees::
+	`git switch` refuses when the wanted ref is already
+	checked out by another worktree. This option makes it check
+	the ref out anyway. In other words, the ref can be held by
+	more than one worktree.
+
+--ignore-in-progress::
+	`git switch` by default refuses when some operation is in
+	progress (e.g. "git rebase", "git am" ...). This option
+	overrides this safety check and allows switching.
+
+--recurse-submodules::
+--no-recurse-submodules::
+	Using `--recurse-submodules` will update the content of all initialized
+	submodules according to the commit recorded in the superproject. If
+	local modifications in a submodule would be overwritten the checkout
+	will fail unless `-f` is used. If nothing (or `--no-recurse-submodules`)
+	is used, the work trees of submodules will not be updated.
+	Just like linkgit:git-submodule[1], this will detach `HEAD` of the
+	submodule.
+
+EXAMPLES
+--------
+
+The following command switches to the "master" branch:
+
+------------
+$ git switch master
+------------
+
+After working in the wrong branch, switching to the correct branch
+would be done using:
+
+------------
+$ git switch mytopic
+------------
+
+However, your "wrong" branch and correct "mytopic" branch may differ
+in files that you have modified locally, in which case the above
+switch would fail like this:
+
+------------
+$ git switch mytopic
+error: You have local changes to 'frotz'; not switching branches.
+------------
+
+You can give the `-m` flag to the command, which would try a three-way
+merge:
+
+------------
+$ git switch -m mytopic
+Auto-merging frotz
+------------
+
+After this three-way merge, the local modifications are _not_
+registered in your index file, so `git diff` would show you what
+changes you made since the tip of the new branch.
+
+To switch back to the previous branch before we switched to mytopic
+(i.e. "master" branch):
+
+------------
+$ git switch -
+------------
+
+You can grow a new branch from any commit. For example, switch to
+"HEAD~3" and create branch "fixup":
+
+------------
+$ git switch -c fixup HEAD~3
+Switched to a new branch 'fixup'
+------------
+
+If you want to start a new branch from a remote branch of the same
+name:
+
+------------
+$ git switch -g new-topic
+Branch 'new-topic' set up to track remote branch 'new-topic' from 'origin'
+Switched to a new branch 'new-topic'
+------------
+
+To check out commit `HEAD~3` for temporary inspection or experiment
+without creating a new branch:
+
+------------
+$ git switch --detach HEAD~3
+HEAD is now at 9fc9555312 Merge branch 'cc/shared-index-permbits'
+------------
+
+If it turns out whatever you have done is worth keeping, you can
+always create a new name for it (without switching away):
+
+------------
+$ git switch -c good-surprises
+------------
+
+SEE ALSO
+--------
+linkgit:git-checkout[1],
+linkgit:git-branch[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 9b41f81c06..cd0f9fa507 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -112,7 +112,8 @@ Checking-out and checking-in
 
 These attributes affect how the contents stored in the
 repository are copied to the working tree files when commands
-such as 'git checkout' and 'git merge' run.  They also affect how
+such as 'git switch', 'git checkout'  and 'git merge' run.
+They also affect how
 Git stores the contents you prepare in the working tree in the
 repository upon 'git add' and 'git commit'.
 
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 5bf653c111..8ff72f0613 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -165,12 +165,13 @@ rebased, and is not set when rebasing the current branch.
 post-checkout
 ~~~~~~~~~~~~~
 
-This hook is invoked when a linkgit:git-checkout[1] is run after having updated the
+This hook is invoked when a linkgit:git-checkout[1] or
+linkgit:git-switch[1] is run after having updated the
 worktree.  The hook is given three parameters: the ref of the previous HEAD,
 the ref of the new HEAD (which may or may not have changed), and a flag
 indicating whether the checkout was a branch checkout (changing branches,
 flag=1) or a file checkout (retrieving a file from the index, flag=0).
-This hook cannot affect the outcome of `git checkout`.
+This hook cannot affect the outcome of `git switch` or `git checkout`.
 
 It is also run after linkgit:git-clone[1], unless the `--no-checkout` (`-n`) option is
 used. The first parameter given to the hook is the null-ref, the second the
@@ -406,7 +407,8 @@ exit with a zero status.
 For example, the hook can simply run `git read-tree -u -m HEAD "$1"`
 in order to emulate `git fetch` that is run in the reverse direction
 with `git push`, as the two-tree form of `git read-tree -u -m` is
-essentially the same as `git checkout` that switches branches while
+essentially the same as `git switch` or `git checkout`
+that switches branches while
 keeping the local changes in the working tree that do not interfere
 with the difference between the branches.
 
diff --git a/Makefile b/Makefile
index 148668368b..8e91db73ad 100644
--- a/Makefile
+++ b/Makefile
@@ -802,6 +802,7 @@ BUILT_INS += git-merge-subtree$X
 BUILT_INS += git-show$X
 BUILT_INS += git-stage$X
 BUILT_INS += git-status$X
+BUILT_INS += git-switch$X
 BUILT_INS += git-whatchanged$X
 
 # what 'all' will build and 'install' will install in gitexecdir,
diff --git a/builtin.h b/builtin.h
index 6538932e99..c64e44450e 100644
--- a/builtin.h
+++ b/builtin.h
@@ -227,6 +227,7 @@ extern int cmd_show_index(int argc, const char **argv, const char *prefix);
 extern int cmd_status(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
+extern int cmd_switch(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index cf1d70511e..dd3c200cb5 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -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;
@@ -1411,33 +1416,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;
@@ -1570,3 +1567,42 @@ 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 = NULL;
+	int ret;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.no_dwim_new_local_branch = 0;
+
+	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);
+
+	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;
+	int ret;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.no_dwim_new_local_branch = 0;
+
+	options = parse_options_dup(options);
+	options = add_common_options(&opts, options);
+	options = add_switch_branch_options(&opts, options);
+
+	ret = checkout_main(argc, argv, prefix, &opts,
+			    options, switch_branch_usage);
+	FREE_AND_NULL(options);
+	return ret;
+}
diff --git a/command-list.txt b/command-list.txt
index 3a9af104b5..13317f47d4 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -171,6 +171,7 @@ git-status                              mainporcelain           info
 git-stripspace                          purehelpers
 git-submodule                           mainporcelain
 git-svn                                 foreignscminterface
+git-switch                              mainporcelain           history
 git-symbolic-ref                        plumbingmanipulators
 git-tag                                 mainporcelain           history
 git-unpack-file                         plumbinginterrogators
diff --git a/git.c b/git.c
index 2014aab6b8..39582cf511 100644
--- a/git.c
+++ b/git.c
@@ -573,6 +573,7 @@ static struct cmd_struct commands[] = {
 	{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
 	{ "stripspace", cmd_stripspace },
 	{ "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
+	{ "switch", cmd_switch, RUN_SETUP | NEED_WORK_TREE },
 	{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
 	{ "tag", cmd_tag, RUN_SETUP | DELAY_PAGER_CONFIG },
 	{ "unpack-file", cmd_unpack_file, RUN_SETUP | NO_PARSEOPT },
-- 
2.21.0.548.gd3c7d92dc2
Elijah Newren· Mar 18, 2019, 16:48 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v4 12/26] checkout: split part of it to new command 'switch'

On Sun, Mar 17, 2019 at 5:50 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 14 quoted lines
>
> "git checkout" doing too many things is a source of confusion for many
> users (and it even bites old timers sometimes). To remedy that, the
> command will be split into two new ones: switch and restore. The good
> old "git checkout" command is still here and will be until all (or most
> of users) are sick of it.
>
> See the new man page for the final design of switch. The actual
> implementation though is still pretty much the same as "git checkout"
> and not completely aligned with the man page. Following patches will
> adjust their behavior to match the man page.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
Looking really good.  Just some minor comments...
Show 11 quoted lines
> +git-switch(1)
> +=============
> +
> +NAME
> +----
> +git-switch - Switch branches
> +
> +SYNOPSIS
> +--------
> +[verse]
> +'git switch' [<options>] [--guess] <branch>
Should this now use [--no-guess] since --guess is the default?
Show 18 quoted lines
> +'git switch' [<options>] --detach [<start-point>]
> +'git switch' [<options>] (-c|-C|--orphan) <new-branch> [<start-point>]
> +
> +DESCRIPTION
> +-----------
> +Switch to a specified branch. The working tree and the index are
> +updated to match the branch. All new commits will be added to the tip
> +of this branch.
> +
> +Optionally a new branch could be created with either `-c`, `-C`,
> +automatically from a remote branch of same name (see `--guess`), or
> +detach the working tree from any branch with `--detach`, along with
> +switching.
> +
> +Switching branches does not require a clean index and working tree
> +(i.e. no differences compared to `HEAD`). The operation is aborted
> +however if the switch leads to loss of local changes, unless told
> +otherwise.

Maybe s/otherwise./otherwise with --discard-changes or --merge./, just for a little extra clarity?

Show 9 quoted lines
> +-f::
> +--force::
> +       An alias for `--discard-changes`.
> +
> +--discard-changes::
> +       Proceed even if the index or the working tree differs from
> +       `HEAD`. Both the index and working tree are restored to match
> +       the switching target. This is used to throw away local
> +       changes.

It looks like elsewhere you and Junio discussed making --force also imply --ignore-in-progress. That option should be moved close to --force, so that similar options are adjacent, but it also brings up a question for me:

Is --force an alias for both `--discard-changes` and `--ignore-in-progress`, or is `--discard-changes` really just another name for `--force` (i.e. does it too imply `--ignore-in-progress`)? I'd be tempted to say the former, but I'm curious on others' thoughts.

Show 5 quoted lines
> +--orphan <new-branch>::
> +       Create a new 'orphan' branch, named `<new-branch>`. If
> +       `<start-point>` is specified, the index and working tree are
> +       adjusted to match it. Otherwise both are adjusted to contain no
> +       tracked files.

Thanks. I'm still slightly hesitant about whether <start-point> should be allowed with --orphan; it seems equivalent to me to letting people have a flag for switching to existing branch A while forcing the index and working tree to match branch B (defaulting B to HEAD from before the switch). Having <start-point> and --orphan together is just a special case of this idea, and thus allowing those together seems like it'll cause the more general request to be filed at some point, and we will already have the precedent of supporting it somewhere. This usecase seems to be somewhat esoteric and infrequent, and could be easily obtained by combining other commands. I'm worried that trying to explain this usecase may make the documentation for the common everyday commands even more complex, and these manpages are already kind of long. However, as I said above, that's just a slight hesitation and maybe I'm just excessively worried about the length of our manpages for the most common commands. This new description of --orphan at least gets the default behavior right.

Great work so far; thanks for working on this.
Elijah
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 13/26] switch: better names for -b and -B

The shortcut of these options do not make much sense when used with switch. And their descriptions are also tied to checkout. Move -b/-B to cmd_checkout() and new -c/-C with the same functionality in cmd_switch_branch()

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 32 +++++++++++++++++++++-----------
 1 file changed, 21 insertions(+), 11 deletions(-)
Show changes to builtin/checkout.c +21 −11
diff --git a/builtin/checkout.c b/builtin/checkout.c
index dd3c200cb5..400a0d5620 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1368,14 +1368,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"),
@@ -1571,15 +1567,22 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
 	struct checkout_opts opts;
-	struct option *options = NULL;
+	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_END()
+	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
 
-	options = parse_options_dup(options);
+	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
-	options = add_switch_branch_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,
@@ -1592,14 +1595,21 @@ 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_END()
+	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
 
-	options = parse_options_dup(options);
+	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-	options = add_switch_branch_options(&opts, options);
+	options = add_common_switch_branch_options(&opts, options);
 
 	ret = checkout_main(argc, argv, prefix, &opts,
 			    options, switch_branch_usage);
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 14/26] switch: add --discard-changes

--discard-changes is a better name than --force for this option since it's what really happens. --force is turned to an alias for --discard-changes. But it's meant to an alias for potentially more force options in the future.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)
Show changes to builtin/checkout.c +10 −2
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 400a0d5620..bd94b04303 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -53,6 +53,7 @@ struct checkout_opts {
 	int count_checkout_paths;
 	int overlay_mode;
 	int no_dwim_new_local_branch;
+	int discard_changes;
 
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
@@ -680,7 +681,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)
@@ -802,7 +803,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;
@@ -1309,6 +1310,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");
@@ -1445,6 +1449,8 @@ static int checkout_main(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"));
@@ -1600,6 +1606,8 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 			   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;
-- 
2.21.0.548.gd3c7d92dc2
Eric Sunshine· Mar 18, 2019, 23:55 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v4 14/26] switch: add --discard-changes

On Sun, Mar 17, 2019 at 8:51 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> --discard-changes is a better name than --force for this option since
> it's what really happens. --force is turned to an alias for
> --discard-changes. But it's meant to an alias for potentially more force
s/to an/to be an/
> options in the future.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 15/26] switch: remove -l

This option is ancient. Nowadays reflog is enabled by default and automatically created for new branches. Keep it in git-checkout only.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/checkout.c +1 −1
diff --git a/builtin/checkout.c b/builtin/checkout.c
index bd94b04303..85fc60f5f2 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1376,7 +1376,6 @@ static struct option *add_common_switch_branch_options(
 	struct checkout_opts *opts, struct option *prevopts)
 {
 	struct option options[] = {
-		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),
@@ -1579,6 +1578,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			   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;
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 16/26] switch: stop accepting pathspec

This command is about switching branch (or creating a new one) and should not accept pathspec. This helps simplify ambiguation handling. The other two ("git checkout" and "git restore") of course do accept pathspec as before.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)
Show changes to builtin/checkout.c +15 −5
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 85fc60f5f2..2c4cab6406 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -54,6 +54,7 @@ struct checkout_opts {
 	int overlay_mode;
 	int no_dwim_new_local_branch;
 	int discard_changes;
+	int accept_pathspec;
 
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
@@ -1176,10 +1177,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;
 		}
@@ -1213,11 +1220,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) {
@@ -1262,7 +1270,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--;
@@ -1585,6 +1593,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
+	opts.accept_pathspec = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1614,6 +1623,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
+	opts.accept_pathspec = 0;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 17/26] switch: reject "do nothing" case

"git checkout" can be executed without any arguments. What it does is not exactly great: it switches from HEAD to HEAD and shows worktree modification as a side effect.

Make switch reject this case. Just use "git status" if you want that side effect. For switch, you have to either

- really switch a branch
- (explicitly) detach from the current branch
- create a new branch
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 9 +++++++++
 1 file changed, 9 insertions(+)
Show changes to builtin/checkout.c +9 −0
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2c4cab6406..73b7373414 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -55,6 +55,7 @@ struct checkout_opts {
 	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
@@ -1338,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;
@@ -1593,6 +1600,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	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);
@@ -1624,6 +1632,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	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);
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 18/26] switch: only allow explicit detached HEAD

"git checkout <commit>" will checkout the commit in question and detach HEAD from the current branch. It is naturally a right thing to do once you get git references. But detached HEAD is a scary concept to new users because we show a lot of warnings and stuff, and it could be hard to get out of (until you know better).

To keep switch a bit more friendly to new users, we only allow entering detached HEAD mode when --detach is given. "git switch" must take a branch (unless you create a new branch, then of course switch can take any commit-ish)

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)
Show changes to builtin/checkout.c +34 −0
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 73b7373414..d72e28aabb 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -45,6 +45,7 @@ struct checkout_opts {
 	int merge;
 	int force;
 	int force_detach;
+	int implicit_detach;
 	int writeout_stage;
 	int overwrite_ignore;
 	int ignore_skipworktree;
@@ -1298,6 +1299,29 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)
 	return status;
 }
 
+static void die_expecting_a_branch(const struct branch_info *branch_info)
+{
+	struct object_id oid;
+	char *to_free;
+
+	if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free) == 1) {
+		const char *ref = to_free;
+
+		if (skip_prefix(ref, "refs/tags/", &ref))
+			die(_("a branch is expected, got tag '%s'"), ref);
+		if (skip_prefix(ref, "refs/remotes/", &ref))
+			die(_("a branch is expected, got remote branch '%s'"), ref);
+		die(_("a branch is expected, got '%s'"), ref);
+	}
+	if (branch_info->commit)
+		die(_("a branch is expected, got commit '%s'"), branch_info->name);
+	/*
+	 * This case should never happen because we already die() on
+	 * non-commit, but just in case.
+	 */
+	die(_("a branch is expected, got '%s'"), branch_info->name);
+}
+
 static int checkout_branch(struct checkout_opts *opts,
 			   struct branch_info *new_branch_info)
 {
@@ -1345,6 +1369,14 @@ static int checkout_branch(struct checkout_opts *opts,
 	    !opts->force_detach)
 		die(_("missing branch or commit argument"));
 
+	if (!opts->implicit_detach &&
+	    !opts->force_detach &&
+	    !opts->new_branch &&
+	    !opts->new_branch_force &&
+	    new_branch_info->name &&
+	    !new_branch_info->path)
+		die_expecting_a_branch(new_branch_info);
+
 	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
 	    !opts->ignore_other_worktrees) {
 		int flag;
@@ -1602,6 +1634,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.no_dwim_new_local_branch = 0;
 	opts.switch_branch_doing_nothing_is_ok = 1;
 	opts.accept_pathspec = 1;
+	opts.implicit_detach = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1633,6 +1666,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	opts.no_dwim_new_local_branch = 0;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
+	opts.implicit_detach = 0;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 19/26] switch: add short option for --detach

"git checkout" automatically detaches branches and --detach is not that useful (--no-detach is more likely). But for "switch", you may want to use it more often once you're used to detached HEAD. This of course adds -d to git-checkout but it does not harm (yet?) to do it.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/checkout.c +1 −1
diff --git a/builtin/checkout.c b/builtin/checkout.c
index d72e28aabb..a14133b474 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1423,7 +1423,7 @@ static struct option *add_common_switch_branch_options(
 	struct checkout_opts *opts, struct option *prevopts)
 {
 	struct option options[] = {
-		OPT_BOOL(0, "detach", &opts->force_detach, N_("detach HEAD at named commit")),
+		OPT_BOOL('d', "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),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 20/26] switch: implicit dwim, use --no-guess to disable it

This is already the default in git-checkout. The real change in here is just minor cleanup. The main excuse is to explain why dwim is kept default.

Contrary to detach mode that is easy to get into and confusing to get back out. Automatically creating a tracking branch often does not kick in as often (you would need a branch of the same name on a remote). And since the branch creation is reported clearly, the user should be able to undo/delete it if it's unwanted.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt | 38 ++++++++++++++++++++--------------
 builtin/checkout.c             | 16 +++++++-------
 2 files changed, 31 insertions(+), 23 deletions(-)
Show changes to 2 files +31 −22

Documentation/git-checkout.txt, builtin/checkout.c

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index c7192bdefe..58f18a0842 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -31,22 +31,13 @@ branch.
 	`<branch>`.
 +
 If `<branch>` is not found but there does exist a tracking branch in
-exactly one remote (call it `<remote>`) with a matching name, treat as
-equivalent to
+exactly one remote (call it `<remote>`) with a matching name and
+`--no-guess` is not specified, treat as equivalent to
 +
 ------------
 $ git checkout -b <branch> --track <remote>/<branch>
 ------------
 +
-If the branch exists in multiple remotes and one of them is named by
-the `checkout.defaultRemote` configuration variable, we'll use that
-one for the purposes of disambiguation, even if the `<branch>` isn't
-unique across all remotes. Set it to
-e.g. `checkout.defaultRemote=origin` to always checkout remote
-branches from there if `<branch>` is ambiguous but exists on the
-'origin' remote. See also `checkout.defaultRemote` in
-linkgit:git-config[1].
-+
 You could omit `<branch>`, in which case the command degenerates to
 "check out the current branch", which is a glorified no-op with
 rather expensive side-effects to show only the tracking information,
@@ -183,6 +174,27 @@ explicitly give a name with `-b` in such a case.
 	Do not set up "upstream" configuration, even if the
 	`branch.autoSetupMerge` configuration variable is true.
 
+--guess::
+--no-guess::
+	If `<branch>` is not found but there does exist a tracking
+	branch in exactly one remote (call it `<remote>`) with a
+	matching name, treat as equivalent to
++
+------------
+$ git checkout -b <branch> --track <remote>/<branch>
+------------
++
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to
+e.g. `checkout.defaultRemote=origin` to always checkout remote
+branches from there if `<branch>` is ambiguous but exists on the
+'origin' remote. See also `checkout.defaultRemote` in
+linkgit:git-config[1].
++
+Use `--no-guess` to disable this.
+
 -l::
 	Create the new branch's reflog; see linkgit:git-branch[1] for
 	details.
@@ -287,10 +299,6 @@ Note that this option uses the no overlay mode by default (see also
 	Just like linkgit:git-submodule[1], this will detach `HEAD` of the
 	submodule.
 
---no-guess::
-	Do not attempt to create a branch if a remote tracking branch
-	of the same name exists.
-
 --overlay::
 --no-overlay::
 	In the default overlay mode, `git checkout` never
diff --git a/builtin/checkout.c b/builtin/checkout.c
index a14133b474..ee74379038 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -53,7 +53,7 @@ struct checkout_opts {
 	int show_progress;
 	int count_checkout_paths;
 	int overlay_mode;
-	int no_dwim_new_local_branch;
+	int dwim_new_local_branch;
 	int discard_changes;
 	int accept_pathspec;
 	int switch_branch_doing_nothing_is_ok;
@@ -1430,8 +1430,6 @@ static struct option *add_common_switch_branch_options(
 		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
-			 N_("second guess 'git checkout <no-such-branch>'")),
 		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
 		OPT_END()
@@ -1468,7 +1466,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 {
 	struct branch_info new_branch_info;
 	int dwim_remotes_matched = 0;
-	int dwim_new_local_branch;
 
 	memset(&new_branch_info, 0, sizeof(new_branch_info));
 	opts->overwrite_ignore = 1;
@@ -1483,7 +1480,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 	argc = parse_options(argc, argv, prefix, options, usagestr,
 			     PARSE_OPT_KEEP_DASHDASH);
 
-	dwim_new_local_branch = !opts->no_dwim_new_local_branch;
 	if (opts->show_progress < 0) {
 		if (opts->quiet)
 			opts->show_progress = 0;
@@ -1545,7 +1541,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 		struct object_id rev;
 		int dwim_ok =
 			!opts->patch_mode &&
-			dwim_new_local_branch &&
+			opts->dwim_new_local_branch &&
 			opts->track == BRANCH_TRACK_UNSPECIFIED &&
 			!opts->new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
@@ -1626,12 +1622,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		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, "guess", &opts.dwim_new_local_branch,
+			 N_("second guess 'git checkout <no-such-branch>' (default)")),
 		OPT_END()
 	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
-	opts.no_dwim_new_local_branch = 0;
+	opts.dwim_new_local_branch = 1;
 	opts.switch_branch_doing_nothing_is_ok = 1;
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
@@ -1656,6 +1654,8 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 			   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()
@@ -1663,7 +1663,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
-	opts.no_dwim_new_local_branch = 0;
+	opts.dwim_new_local_branch = 1;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
 	opts.implicit_detach = 0;
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 21/26] switch: no worktree status unless real branch switch happens

When we switch from one branch to another, it makes sense to show a summary of local changes since there could be conflicts, or some files left modified.... When switch is used solely for creating a new branch (and "switch" to the same commit) or detaching, we don't really need to show anything.

"git checkout" does it anyway for historical reasons. But we can start with a clean slate with switch and don't have to.

This essentially reverts fa655d8411 (checkout: optimize "git checkout -b <new_branch>" - 2018-08-16) and make it default for switch, but also for -B and --detach. Users of big repos are encouraged to move to switch.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/config/checkout.txt |   8 --
 builtin/checkout.c                | 134 ++----------------------------
 t/t1090-sparse-checkout-scope.sh  |  14 ----
 3 files changed, 8 insertions(+), 148 deletions(-)
Show changes to 3 files +8 −148

Documentation/config/checkout.txt, builtin/checkout.c, t/t1090-sparse-checkout-scope.sh

diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt
index d6872ffa83..6b646813ab 100644
--- a/Documentation/config/checkout.txt
+++ b/Documentation/config/checkout.txt
@@ -16,11 +16,3 @@ will checkout the '<something>' branch on another remote,
 and by linkgit:git-worktree[1] when 'git worktree add' refers to a
 remote branch. This setting might be used for other checkout-like
 commands or functionality in the future.
-
-checkout.optimizeNewBranch::
-	Optimizes the performance of "git checkout -b <new_branch>" when
-	using sparse-checkout.  When set to true, git will not update the
-	repo based on the current sparse-checkout settings.  This means it
-	will not update the skip-worktree bit in the index nor add/remove
-	files in the working directory to reflect the current sparse checkout
-	settings nor will it show the local changes.
diff --git a/builtin/checkout.c b/builtin/checkout.c
index ee74379038..2aeaac092d 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -26,8 +26,6 @@
 #include "unpack-trees.h"
 #include "xdiff-interface.h"
 
-static int checkout_optimize_new_branch;
-
 static const char * const checkout_usage[] = {
 	N_("git checkout [<options>] <branch>"),
 	N_("git checkout [<options>] [<branch>] -- <file>..."),
@@ -57,11 +55,7 @@ struct checkout_opts {
 	int discard_changes;
 	int accept_pathspec;
 	int switch_branch_doing_nothing_is_ok;
-
-	/*
-	 * If new checkout options are added, skip_merge_working_tree
-	 * should be updated accordingly.
-	 */
+	int only_merge_on_switching_branches;
 
 	const char *new_branch;
 	const char *new_branch_force;
@@ -565,112 +559,6 @@ static void setup_branch_path(struct branch_info *branch)
 	branch->path = strbuf_detach(&buf, NULL);
 }
 
-/*
- * Skip merging the trees, updating the index and working directory if and
- * only if we are creating a new branch via "git checkout -b <new_branch>."
- */
-static int skip_merge_working_tree(const struct checkout_opts *opts,
-	const struct branch_info *old_branch_info,
-	const struct branch_info *new_branch_info)
-{
-	/*
-	 * Do the merge if sparse checkout is on and the user has not opted in
-	 * to the optimized behavior
-	 */
-	if (core_apply_sparse_checkout && !checkout_optimize_new_branch)
-		return 0;
-
-	/*
-	 * We must do the merge if we are actually moving to a new commit.
-	 */
-	if (!old_branch_info->commit || !new_branch_info->commit ||
-		!oideq(&old_branch_info->commit->object.oid,
-		       &new_branch_info->commit->object.oid))
-		return 0;
-
-	/*
-	 * opts->patch_mode cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * opts->quiet only impacts output so doesn't require a merge
-	 */
-
-	/*
-	 * Honor the explicit request for a three-way merge or to throw away
-	 * local changes
-	 */
-	if (opts->merge || opts->force)
-		return 0;
-
-	/*
-	 * --detach is documented as "updating the index and the files in the
-	 * working tree" but this optimization skips those steps so fall through
-	 * to the regular code path.
-	 */
-	if (opts->force_detach)
-		return 0;
-
-	/*
-	 * opts->writeout_stage cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * Honor the explicit ignore requests
-	 */
-	if (!opts->overwrite_ignore || opts->ignore_skipworktree ||
-		opts->ignore_other_worktrees)
-		return 0;
-
-	/*
-	 * opts->show_progress only impacts output so doesn't require a merge
-	 */
-
-	/*
-	 * opts->overlay_mode cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * If we aren't creating a new branch any changes or updates will
-	 * happen in the existing branch.  Since that could only be updating
-	 * the index and working directory, we don't want to skip those steps
-	 * or we've defeated any purpose in running the command.
-	 */
-	if (!opts->new_branch)
-		return 0;
-
-	/*
-	 * new_branch_force is defined to "create/reset and checkout a branch"
-	 * so needs to go through the merge to do the reset
-	 */
-	if (opts->new_branch_force)
-		return 0;
-
-	/*
-	 * A new orphaned branch requrires the index and the working tree to be
-	 * adjusted to <start_point>
-	 */
-	if (opts->new_orphan_branch)
-		return 0;
-
-	/*
-	 * Remaining variables are not checkout options but used to track state
-	 */
-
-	 /*
-	  * Do the merge if this is the initial checkout. We cannot use
-	  * is_cache_unborn() here because the index hasn't been loaded yet
-	  * so cache_nr and timestamp.sec are always zero.
-	  */
-	if (!file_exists(get_index_file()))
-		return 0;
-
-	return 1;
-}
-
 static int merge_working_tree(const struct checkout_opts *opts,
 			      struct branch_info *old_branch_info,
 			      struct branch_info *new_branch_info,
@@ -1028,6 +916,7 @@ static int switch_branches(const struct checkout_opts *opts,
 	void *path_to_free;
 	struct object_id rev;
 	int flag, writeout_error = 0;
+	int do_merge = 1;
 
 	trace2_cmd_mode("branch");
 
@@ -1047,16 +936,12 @@ static int switch_branches(const struct checkout_opts *opts,
 		if (!new_branch_info->commit)
 			die(_("You are on a branch yet to be born"));
 		parse_commit_or_die(new_branch_info->commit);
+
+		if (opts->only_merge_on_switching_branches)
+			do_merge = 0;
 	}
 
-	/* optimize the "checkout -b <new_branch> path */
-	if (skip_merge_working_tree(opts, &old_branch_info, new_branch_info)) {
-		if (!checkout_optimize_new_branch && !opts->quiet) {
-			if (read_cache_preload(NULL) < 0)
-				return error(_("index file corrupt"));
-			show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
-		}
-	} else {
+	if (do_merge) {
 		ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
 		if (ret) {
 			free(path_to_free);
@@ -1076,11 +961,6 @@ static int switch_branches(const struct checkout_opts *opts,
 
 static int git_checkout_config(const char *var, const char *value, void *cb)
 {
-	if (!strcmp(var, "checkout.optimizenewbranch")) {
-		checkout_optimize_new_branch = git_config_bool(var, value);
-		return 0;
-	}
-
 	if (!strcmp(var, "diff.ignoresubmodules")) {
 		struct checkout_opts *opts = cb;
 		handle_ignore_submodules_arg(&opts->diff_options, value);
@@ -1631,6 +1511,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	memset(&opts, 0, sizeof(opts));
 	opts.dwim_new_local_branch = 1;
 	opts.switch_branch_doing_nothing_is_ok = 1;
+	opts.only_merge_on_switching_branches = 0;
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
 
@@ -1666,6 +1547,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	opts.dwim_new_local_branch = 1;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
+	opts.only_merge_on_switching_branches = 1;
 	opts.implicit_detach = 0;
 
 	options = parse_options_dup(switch_options);
diff --git a/t/t1090-sparse-checkout-scope.sh b/t/t1090-sparse-checkout-scope.sh
index 090b7fc3d3..40cc004326 100755
--- a/t/t1090-sparse-checkout-scope.sh
+++ b/t/t1090-sparse-checkout-scope.sh
@@ -31,20 +31,6 @@ test_expect_success 'perform sparse checkout of master' '
 	test_path_is_file c
 '
 
-test_expect_success 'checkout -b checkout.optimizeNewBranch interaction' '
-	cp .git/info/sparse-checkout .git/info/sparse-checkout.bak &&
-	test_when_finished "
-		mv -f .git/info/sparse-checkout.bak .git/info/sparse-checkout
-		git checkout master
-	" &&
-	echo "/b" >>.git/info/sparse-checkout &&
-	test "$(git ls-files -t b)" = "S b" &&
-	git -c checkout.optimizeNewBranch=true checkout -b fast &&
-	test "$(git ls-files -t b)" = "S b" &&
-	git checkout -b slow &&
-	test "$(git ls-files -t b)" = "H b"
-'
-
 test_expect_success 'merge feature branch into sparse checkout of master' '
 	git merge feature &&
 	test_path_is_file a &&
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 22/26] switch: reject if some operation is in progress

Unless you know what you're doing, switching to another branch to do something then switching back could be confusing. Worse, you may even forget that you're in the middle of something. By the time you realize, you may have done a ton of work and it gets harder to go back.

The new option --ignore-in-progress is added to override this check. However it's probably just safer to create a separate worktree for that separate thing you want to work on and leave this worktree alone (unless of course creating or preparing worktrees are not cheap).

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)
Show changes to builtin/checkout.c +26 −0
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2aeaac092d..21a585eaa9 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -24,6 +24,7 @@
 #include "tree.h"
 #include "tree-walk.h"
 #include "unpack-trees.h"
+#include "wt-status.h"
 #include "xdiff-interface.h"
 
 static const char * const checkout_usage[] = {
@@ -56,6 +57,7 @@ struct checkout_opts {
 	int accept_pathspec;
 	int switch_branch_doing_nothing_is_ok;
 	int only_merge_on_switching_branches;
+	int can_switch_when_in_progress;
 
 	const char *new_branch;
 	const char *new_branch_force;
@@ -1257,6 +1259,26 @@ static int checkout_branch(struct checkout_opts *opts,
 	    !new_branch_info->path)
 		die_expecting_a_branch(new_branch_info);
 
+	if (!opts->can_switch_when_in_progress) {
+		struct wt_status_state state;
+
+		memset(&state, 0, sizeof(state));
+		wt_status_get_state(the_repository, &state, 0);
+
+		if (state.merge_in_progress)
+			die(_("cannot switch branch while merging"));
+		if (state.am_in_progress)
+			die(_("cannot switch branch in the middle of an am session"));
+		if (state.rebase_in_progress || state.rebase_in_progress)
+			die(_("cannot switch branch while rebasing"));
+		if (state.cherry_pick_in_progress)
+			die(_("cannot switch branch while cherry-picking"));
+		if (state.revert_in_progress)
+			die(_("cannot switch branch while reverting"));
+		if (state.bisect_in_progress)
+			die(_("cannot switch branch while bisecting"));
+	}
+
 	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
 	    !opts->ignore_other_worktrees) {
 		int flag;
@@ -1514,6 +1536,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.only_merge_on_switching_branches = 0;
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
+	opts.can_switch_when_in_progress = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1539,6 +1562,8 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 			 N_("second guess 'git switch <no-such-branch>'")),
 		OPT_BOOL(0, "discard-changes", &opts.discard_changes,
 			 N_("throw away local modifications")),
+		OPT_BOOL(0, "ignore-in-progress", &opts.can_switch_when_in_progress,
+			 N_("allow to switch branch when some operation is still in progress")),
 		OPT_END()
 	};
 	int ret;
@@ -1549,6 +1574,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	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;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.21.0.548.gd3c7d92dc2
Phillip Wood· Mar 20, 2019, 11:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v4 22/26] switch: reject if some operation is in progress

Hi Duy
The new switch command is shaping up nicely
On 17/03/2019 12:49, Nguyễn Thái Ngọc Duy wrote:
Show 50 quoted lines
> Unless you know what you're doing, switching to another branch to do
> something then switching back could be confusing. Worse, you may even
> forget that you're in the middle of something. By the time you realize,
> you may have done a ton of work and it gets harder to go back.
> 
> The new option --ignore-in-progress is added to override this check.
> However it's probably just safer to create a separate worktree for
> that separate thing you want to work on and leave this worktree
> alone (unless of course creating or preparing worktrees are not cheap).
> 
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  builtin/checkout.c | 26 ++++++++++++++++++++++++++
>  1 file changed, 26 insertions(+)
> 
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index 2aeaac092d..21a585eaa9 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -24,6 +24,7 @@
>  #include "tree.h"
>  #include "tree-walk.h"
>  #include "unpack-trees.h"
> +#include "wt-status.h"
>  #include "xdiff-interface.h"
>  
>  static const char * const checkout_usage[] = {
> @@ -56,6 +57,7 @@ struct checkout_opts {
>  	int accept_pathspec;
>  	int switch_branch_doing_nothing_is_ok;
>  	int only_merge_on_switching_branches;
> +	int can_switch_when_in_progress;
>  
>  	const char *new_branch;
>  	const char *new_branch_force;
> @@ -1257,6 +1259,26 @@ static int checkout_branch(struct checkout_opts *opts,
>  	    !new_branch_info->path)
>  		die_expecting_a_branch(new_branch_info);
>  
> +	if (!opts->can_switch_when_in_progress) {
> +		struct wt_status_state state;
> +
> +		memset(&state, 0, sizeof(state));
> +		wt_status_get_state(the_repository, &state, 0);
> +
> +		if (state.merge_in_progress)
> +			die(_("cannot switch branch while merging"));
> +		if (state.am_in_progress)
> +			die(_("cannot switch branch in the middle of an am session"));
> +		if (state.rebase_in_progress || state.rebase_in_progress)
I think one of those should be state.rebase_interactive_in_progress
Best Wishes
Phillip
Show 38 quoted lines
> +			die(_("cannot switch branch while rebasing"));
> +		if (state.cherry_pick_in_progress)
> +			die(_("cannot switch branch while cherry-picking"));
> +		if (state.revert_in_progress)
> +			die(_("cannot switch branch while reverting"));
> +		if (state.bisect_in_progress)
> +			die(_("cannot switch branch while bisecting"));
> +	}
> +
>  	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
>  	    !opts->ignore_other_worktrees) {
>  		int flag;
> @@ -1514,6 +1536,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
>  	opts.only_merge_on_switching_branches = 0;
>  	opts.accept_pathspec = 1;
>  	opts.implicit_detach = 1;
> +	opts.can_switch_when_in_progress = 1;
>  
>  	options = parse_options_dup(checkout_options);
>  	options = add_common_options(&opts, options);
> @@ -1539,6 +1562,8 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
>  			 N_("second guess 'git switch <no-such-branch>'")),
>  		OPT_BOOL(0, "discard-changes", &opts.discard_changes,
>  			 N_("throw away local modifications")),
> +		OPT_BOOL(0, "ignore-in-progress", &opts.can_switch_when_in_progress,
> +			 N_("allow to switch branch when some operation is still in progress")),
>  		OPT_END()
>  	};
>  	int ret;
> @@ -1549,6 +1574,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
>  	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;
>  
>  	options = parse_options_dup(switch_options);
>  	options = add_common_options(&opts, options);
> 
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 23/26] switch: --orphan defaults to empty tree as HEAD

Switching and creating branches always involves knowing the <start-point> to begin the new branch from. Sometimes, people want to create a new branch that does not have any commits yet; --orphan is a flag to allow that.

If <start-point> is not specified, instead of leaving index/worktree unchanged, default to "empty tree" starting point, allowing a clean start.

A note on the implementation. An alternative is just create a dummy commit in-core with empty tree and switch to it. But there's a chance the commit's SHA-1 may end up somewhere permanent like reflog. It's best to make sure "commit" pointer is NULL to avoid it.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 33 +++++++++++++++++++++++++--------
 1 file changed, 25 insertions(+), 8 deletions(-)
Show changes to builtin/checkout.c +25 −8
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 21a585eaa9..f0d6207236 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -58,6 +58,7 @@ struct checkout_opts {
 	int switch_branch_doing_nothing_is_ok;
 	int only_merge_on_switching_branches;
 	int can_switch_when_in_progress;
+	int orphan_default_empty_tree;
 
 	const char *new_branch;
 	const char *new_branch_force;
@@ -568,15 +569,19 @@ static int merge_working_tree(const struct checkout_opts *opts,
 {
 	int ret;
 	struct lock_file lock_file = LOCK_INIT;
+	struct tree *new_tree;
 
 	hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
 	if (read_cache_preload(NULL) < 0)
 		return error(_("index file corrupt"));
 
 	resolve_undo_clear();
+	if (opts->orphan_default_empty_tree && !new_branch_info->commit)
+		new_tree = parse_tree_indirect(the_hash_algo->empty_tree);
+	else
+		new_tree = get_commit_tree(new_branch_info->commit);
 	if (opts->discard_changes) {
-		ret = reset_tree(get_commit_tree(new_branch_info->commit),
-				 opts, 1, writeout_error);
+		ret = reset_tree(new_tree, opts, 1, writeout_error);
 		if (ret)
 			return ret;
 	} else {
@@ -614,7 +619,8 @@ static int merge_working_tree(const struct checkout_opts *opts,
 					   &old_branch_info->commit->object.oid :
 					   the_hash_algo->empty_tree);
 		init_tree_desc(&trees[0], tree->buffer, tree->size);
-		tree = parse_tree_indirect(&new_branch_info->commit->object.oid);
+		parse_tree(new_tree);
+		tree = new_tree;
 		init_tree_desc(&trees[1], tree->buffer, tree->size);
 
 		ret = unpack_trees(2, trees, &topts);
@@ -663,7 +669,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			o.verbosity = 0;
 			work = write_tree_from_memory(&o);
 
-			ret = reset_tree(get_commit_tree(new_branch_info->commit),
+			ret = reset_tree(new_tree,
 					 opts, 1,
 					 writeout_error);
 			if (ret)
@@ -672,13 +678,13 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			o.branch1 = new_branch_info->name;
 			o.branch2 = "local";
 			ret = merge_trees(&o,
-					  get_commit_tree(new_branch_info->commit),
+					  new_tree,
 					  work,
 					  get_commit_tree(old_branch_info->commit),
 					  &result);
 			if (ret < 0)
 				exit(128);
-			ret = reset_tree(get_commit_tree(new_branch_info->commit),
+			ret = reset_tree(new_tree,
 					 opts, 0,
 					 writeout_error);
 			strbuf_release(&o.obuf);
@@ -696,7 +702,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->discard_changes && !opts->quiet)
+	if (!opts->discard_changes && !opts->quiet && new_branch_info->commit)
 		show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
 
 	return 0;
@@ -897,7 +903,10 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne
 	add_pending_object(&revs, object, oid_to_hex(&object->oid));
 
 	for_each_ref(add_pending_uninteresting_ref, &revs);
-	add_pending_oid(&revs, "HEAD", &new_commit->object.oid, UNINTERESTING);
+	if (new_commit)
+		add_pending_oid(&revs, "HEAD",
+				&new_commit->object.oid,
+				UNINTERESTING);
 
 	if (prepare_revision_walk(&revs))
 		die(_("internal error in revision walk"));
@@ -941,6 +950,12 @@ static int switch_branches(const struct checkout_opts *opts,
 
 		if (opts->only_merge_on_switching_branches)
 			do_merge = 0;
+
+		if (opts->new_orphan_branch && opts->orphan_default_empty_tree) {
+			new_branch_info->commit = NULL;
+			new_branch_info->name = "(empty)";
+			do_merge = 1;
+		}
 	}
 
 	if (do_merge) {
@@ -1537,6 +1552,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
 	opts.can_switch_when_in_progress = 1;
+	opts.orphan_default_empty_tree = 0;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1575,6 +1591,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	opts.only_merge_on_switching_branches = 1;
 	opts.implicit_detach = 0;
 	opts.can_switch_when_in_progress = 0;
+	opts.orphan_default_empty_tree = 1;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 24/26] t: add tests for switch

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 t/t2060-switch.sh (new +x) | 97 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 97 insertions(+)
Show changes to t/t2060-switch.sh +97 −0
diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
new file mode 100755
index 0000000000..62ec1bb924
--- /dev/null
+++ b/t/t2060-switch.sh
@@ -0,0 +1,97 @@
+#!/bin/sh
+
+test_description='switch basic functionality'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit first &&
+	git branch first-branch &&
+	test_commit second &&
+	test_commit third &&
+	git remote add origin nohost:/nopath &&
+	git update-ref refs/remotes/origin/foo first-branch
+'
+
+test_expect_success 'switch branch no arguments' '
+	test_must_fail git switch
+'
+
+test_expect_success 'switch branch' '
+	git switch first-branch &&
+	test_path_is_missing second.t
+'
+
+test_expect_success 'switch and detach' '
+	test_when_finished git switch master &&
+	test_must_fail git switch master^{commit} &&
+	git switch --detach master^{commit} &&
+	test_must_fail git symbolic-ref HEAD
+'
+
+test_expect_success 'switch and detach current branch' '
+	test_when_finished git switch master &&
+	git switch master &&
+	git switch --detach &&
+	test_must_fail git symbolic-ref HEAD
+'
+
+test_expect_success 'switch and create branch' '
+	test_when_finished git switch master &&
+	git switch -c temp master^ &&
+	test_cmp_rev master^ refs/heads/temp &&
+	echo refs/heads/temp >expected-branch &&
+	git symbolic-ref HEAD >actual-branch &&
+	test_cmp expected-branch actual-branch
+'
+
+test_expect_success 'force create branch from HEAD' '
+	test_when_finished git switch master &&
+	git switch --detach master &&
+	test_must_fail git switch -c temp &&
+	git switch -C temp &&
+	test_cmp_rev master refs/heads/temp &&
+	echo refs/heads/temp >expected-branch &&
+	git symbolic-ref HEAD >actual-branch &&
+	test_cmp expected-branch actual-branch
+'
+
+test_expect_success 'new orphan branch from empty' '
+	test_when_finished git switch master &&
+	git switch --orphan new-orphan &&
+	test_commit orphan &&
+	git cat-file commit refs/heads/new-orphan >commit &&
+	! grep ^parent commit &&
+	git ls-files >tracked-files &&
+	echo orphan.t >expected &&
+	test_cmp expected tracked-files
+'
+
+test_expect_success 'switching ignores file of same branch name' '
+	test_when_finished git switch master &&
+	: >first-branch &&
+	git switch first-branch &&
+	echo refs/heads/first-branch >expected &&
+	git symbolic-ref HEAD >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'guess and create branch ' '
+	test_when_finished git switch master &&
+	test_must_fail git switch --no-guess foo &&
+	git switch foo &&
+	echo refs/heads/foo >expected &&
+	git symbolic-ref HEAD >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'switching when something is in progress' '
+	test_when_finished rm -f .git/MERGE_HEAD &&
+	# fake a merge-in-progress
+	cp .git/HEAD .git/MERGE_HEAD &&
+	test_must_fail git switch -d @^ &&
+	git switch --ignore-in-progress -d @^ &&
+	git switch -
+'
+
+test_done
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 25/26] completion: support switch

Completion support for --guess could be made better. If no --detach is given, we should only provide a list of refs/heads/* and dwim ones, not the entire ref space. But I still can't penetrate that __git_refs() function yet.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 contrib/completion/git-completion.bash | 37 +++++++++++++++++++++++++-
 1 file changed, 36 insertions(+), 1 deletion(-)
Show changes to contrib/completion/git-completion.bash +36 −1
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 976e4a6548..b24bc48276 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -37,7 +37,8 @@
 #   GIT_COMPLETION_CHECKOUT_NO_GUESS
 #
 #     When set to "1", do not include "DWIM" suggestions in git-checkout
-#     completion (e.g., completing "foo" when "origin/foo" exists).
+#     and git-switch completion (e.g., completing "foo" when "origin/foo"
+#     exists).
 
 case "$COMP_WORDBREAKS" in
 *:*) : great ;;
@@ -2158,6 +2159,40 @@ _git_status ()
 	__git_complete_index_file "$complete_opt"
 }
 
+_git_switch ()
+{
+	case "$cur" in
+	--conflict=*)
+		__gitcomp "diff3 merge" "" "${cur##--conflict=}"
+		;;
+	--*)
+		__gitcomp_builtin switch
+		;;
+	*)
+		# check if --track, --no-track, or --no-guess was specified
+		# if so, disable DWIM mode
+		local track_opt="--track" only_local_ref=n
+		if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ] ||
+		   [ -n "$(__git_find_on_cmdline "--track --no-track --no-guess")" ]; then
+			track_opt=''
+		fi
+		# explicit --guess enables DWIM mode regardless of
+		# $GIT_COMPLETION_CHECKOUT_NO_GUESS
+		if [ -n "$(__git_find_on_cmdline "--guess")" ]; then
+			track_opt='--track'
+		fi
+		if [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then
+			only_local_ref=y
+		fi
+		if [ $only_local_ref = y -a -z "$track_opt" ]; then
+			__gitcomp_direct "$(__git_heads "" "$cur" " ")"
+		else
+			__git_complete_refs $track_opt
+		fi
+		;;
+	esac
+}
+
 __git_config_get_set_variables ()
 {
 	local prevword word config_file= c=$cword
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 17, 2019, 12:49 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v4 26/26] doc: promote "git switch"

The new command "git switch" is added to avoid the confusion of one-command-do-all "git checkout" for new users. They are also helpful to avoid ambiguation context.

For these reasons, promote it everywhere possible. This includes documentation, suggestions/advice from other commands...

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-branch.txt           | 12 +++---
 Documentation/git-check-ref-format.txt |  3 +-
 Documentation/git-format-patch.txt     |  2 +-
 Documentation/git-merge-base.txt       |  2 +-
 Documentation/git-rebase.txt           |  2 +-
 Documentation/git-remote.txt           |  2 +-
 Documentation/git-rerere.txt           | 10 ++---
 Documentation/git-reset.txt            | 20 ++++-----
 Documentation/git-stash.txt            |  9 +++--
 Documentation/gitcore-tutorial.txt     | 19 +++++----
 Documentation/giteveryday.txt          | 24 +++++------
 Documentation/gittutorial.txt          |  4 +-
 Documentation/gitworkflows.txt         |  3 +-
 Documentation/revisions.txt            |  2 +-
 Documentation/user-manual.txt          | 56 +++++++++++++-------------
 advice.c                               | 10 ++---
 sha1-name.c                            |  2 +-
 t/t2020-checkout-detach.sh             | 20 ++++-----
 18 files changed, 101 insertions(+), 101 deletions(-)
Show changes to 18 files +101 −101

Documentation/git-branch.txt, Documentation/git-check-ref-format.txt, Documentation/git-format-patch.txt, Documentation/git-merge-base.txt, Documentation/git-rebase.txt, Documentation/git-remote.txt, Documentation/git-rerere.txt, Documentation/git-reset.txt, Documentation/git-stash.txt, Documentation/gitcore-tutorial.txt, Documentation/giteveryday.txt, Documentation/gittutorial.txt, Documentation/gitworkflows.txt, Documentation/revisions.txt, Documentation/user-manual.txt, advice.c, sha1-name.c, t/t2020-checkout-detach.sh

diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 0cd87ddeff..1e2d89b174 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -48,7 +48,7 @@ The command's second form creates a new branch head named <branchname>
 which points to the current `HEAD`, or <start-point> if given.
 
 Note that this will create the new branch, but it will not switch the
-working tree to it; use "git checkout <newbranch>" to switch to the
+working tree to it; use "git switch <newbranch>" to switch to the
 new branch.
 
 When a local branch is started off a remote-tracking branch, Git sets up the
@@ -198,7 +198,7 @@ This option is only applicable in non-verbose mode.
 +
 This behavior is the default when the start point is a remote-tracking branch.
 Set the branch.autoSetupMerge configuration variable to `false` if you
-want `git checkout` and `git branch` to always behave as if `--no-track`
+want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
 were given. Set it to `always` if you want this behavior when the
 start-point is either a local or remote-tracking branch.
 
@@ -297,7 +297,7 @@ Start development from a known tag::
 $ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6
 $ cd my2.6
 $ git branch my2.6.14 v2.6.14   <1>
-$ git checkout my2.6.14
+$ git switch my2.6.14
 ------------
 +
 <1> This step and the next one could be combined into a single step with
@@ -322,9 +322,9 @@ $ git branch -D test                                    <2>
 NOTES
 -----
 
-If you are creating a branch that you want to checkout immediately, it is
-easier to use the git checkout command with its `-b` option to create
-a branch and check it out with a single command.
+If you are creating a branch that you want to switch to immediately,
+it is easier to use the "git switch" command with its `-c` option to
+do the same thing with a single command.
 
 The options `--contains`, `--no-contains`, `--merged` and `--no-merged`
 serve four related but different purposes:
diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt
index d9de992585..ee6a4144fb 100644
--- a/Documentation/git-check-ref-format.txt
+++ b/Documentation/git-check-ref-format.txt
@@ -88,7 +88,8 @@ but it is explicitly forbidden at the beginning of a branch name).
 When run with `--branch` option in a repository, the input is first
 expanded for the ``previous checkout syntax''
 `@{-n}`.  For example, `@{-1}` is a way to refer the last thing that
-was checked out using "git checkout" operation. This option should be
+was checked out using "git switch" or "git checkout" operation.
+This option should be
 used by porcelains to accept this syntax anywhere a branch name is
 expected, so they can act as if you typed the branch name. As an
 exception note that, the ``previous checkout operation'' might result
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 1af85d404f..0a24a5679e 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -421,7 +421,7 @@ One way to test if your MUA is set up correctly is:
 * Apply it:
 
     $ git fetch <project> master:test-apply
-    $ git checkout test-apply
+    $ git switch test-apply
     $ git reset --hard
     $ git am a.patch
 
diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt
index 9f07f4f6ed..261d5c1164 100644
--- a/Documentation/git-merge-base.txt
+++ b/Documentation/git-merge-base.txt
@@ -149,7 +149,7 @@ instead.
 Discussion on fork-point mode
 -----------------------------
 
-After working on the `topic` branch created with `git checkout -b
+After working on the `topic` branch created with `git switch -c
 topic origin/master`, the history of remote-tracking branch
 `origin/master` may have been rewound and rebuilt, leading to a
 history of this shape:
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 5629ba4c5d..cb6fc166e2 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -17,7 +17,7 @@ SYNOPSIS
 DESCRIPTION
 -----------
 If <branch> is specified, 'git rebase' will perform an automatic
-`git checkout <branch>` before doing anything else.  Otherwise
+`git switch <branch>` before doing anything else.  Otherwise
 it remains on the current branch.
 
 If <upstream> is not specified, the upstream configured in
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index 0cad37fb81..9659abbf8e 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -230,7 +230,7 @@ $ git branch -r
   staging/master
   staging/staging-linus
   staging/staging-next
-$ git checkout -b staging staging/master
+$ git switch -c staging staging/master
 ...
 ------------
 
diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt
index df310d2a58..fe4434ad9d 100644
--- a/Documentation/git-rerere.txt
+++ b/Documentation/git-rerere.txt
@@ -91,7 +91,7 @@ For such a test, you need to merge master and topic somehow.
 One way to do it is to pull master into the topic branch:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 
               o---*---o---+ topic
@@ -113,10 +113,10 @@ the upstream might have been advanced since the test merge `+`,
 in which case the final commit graph would look like this:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 	$ ... work on both topic and master branches
-	$ git checkout master
+	$ git switch master
 	$ git merge topic
 
               o---*---o---+---o---o topic
@@ -136,11 +136,11 @@ merges, you could blow away the test merge, and keep building on
 top of the tip before the test merge:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 	$ git reset --hard HEAD^ ;# rewind the test merge
 	$ ... work on both topic and master branches
-	$ git checkout master
+	$ git switch master
 	$ git merge topic
 
               o---*---o-------o---o topic
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 132f8e55f6..cbf901efb4 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -149,9 +149,9 @@ See also the `--amend` option to linkgit:git-commit[1].
 Undo a commit, making it a topic branch::
 +
 ------------
-$ git branch topic/wip     <1>
-$ git reset --hard HEAD~3  <2>
-$ git checkout topic/wip   <3>
+$ git branch topic/wip          <1>
+$ git reset --hard HEAD~3       <2>
+$ git switch topic/wip          <3>
 ------------
 +
 <1> You have made some commits, but realize they were premature
@@ -232,13 +232,13 @@ working tree are not in any shape to be committed yet, but you
 need to get to the other branch for a quick bugfix.
 +
 ------------
-$ git checkout feature ;# you were working in "feature" branch and
-$ work work work       ;# got interrupted
+$ git switch feature  ;# you were working in "feature" branch and
+$ work work work      ;# got interrupted
 $ git commit -a -m "snapshot WIP"                 <1>
-$ git checkout master
+$ git switch master
 $ fix fix fix
 $ git commit ;# commit with real log
-$ git checkout feature
+$ git switch feature
 $ git reset --soft HEAD^ ;# go back to WIP state  <2>
 $ git reset                                       <3>
 ------------
@@ -279,18 +279,18 @@ reset it while keeping the changes in your working tree.
 +
 ------------
 $ git tag start
-$ git checkout -b branch1
+$ git switch -c branch1
 $ edit
 $ git commit ...                            <1>
 $ edit
-$ git checkout -b branch2                   <2>
+$ git switch -c branch2                     <2>
 $ git reset --keep start                    <3>
 ------------
 +
 <1> This commits your first edits in `branch1`.
 <2> In the ideal world, you could have realized that the earlier
     commit did not belong to the new topic when you created and switched
-    to `branch2` (i.e. `git checkout -b branch2 start`), but nobody is
+    to `branch2` (i.e. `git switch -c branch2 start`), but nobody is
     perfect.
 <3> But you can use `reset --keep` to remove the unwanted commit after
     you switched to `branch2`.
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 7ef8c47911..ebb6282db3 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -235,12 +235,12 @@ return to your original branch to make the emergency fix, like this:
 +
 ----------------------------------------------------------------
 # ... hack hack hack ...
-$ git checkout -b my_wip
+$ git switch -c my_wip
 $ git commit -a -m "WIP"
-$ git checkout master
+$ git switch master
 $ edit emergency fix
 $ git commit -a -m "Fix in a hurry"
-$ git checkout my_wip
+$ git switch my_wip
 $ git reset --soft HEAD^
 # ... continue hacking ...
 ----------------------------------------------------------------
@@ -293,7 +293,8 @@ SEE ALSO
 linkgit:git-checkout[1],
 linkgit:git-commit[1],
 linkgit:git-reflog[1],
-linkgit:git-reset[1]
+linkgit:git-reset[1],
+linkgit:git-switch[1]
 
 GIT
 ---
diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt
index e29a9effcc..f880d21dfb 100644
--- a/Documentation/gitcore-tutorial.txt
+++ b/Documentation/gitcore-tutorial.txt
@@ -741,7 +741,7 @@ used earlier, and create a branch in it. You do that by simply just
 saying that you want to check out a new branch:
 
 ------------
-$ git checkout -b mybranch
+$ git switch -c mybranch
 ------------
 
 will create a new branch based at the current `HEAD` position, and switch
@@ -755,7 +755,7 @@ just telling 'git checkout' what the base of the checkout would be.
 In other words, if you have an earlier tag or branch, you'd just do
 
 ------------
-$ git checkout -b mybranch earlier-commit
+$ git switch -c mybranch earlier-commit
 ------------
 
 and it would create the new branch `mybranch` at the earlier commit,
@@ -765,7 +765,7 @@ and check out the state at that time.
 You can always just jump back to your original `master` branch by doing
 
 ------------
-$ git checkout master
+$ git switch master
 ------------
 
 (or any other branch-name, for that matter) and if you forget which
@@ -794,7 +794,7 @@ $ git branch <branchname> [startingpoint]
 
 which will simply _create_ the branch, but will not do anything further.
 You can then later -- once you decide that you want to actually develop
-on that branch -- switch to that branch with a regular 'git checkout'
+on that branch -- switch to that branch with a regular 'git switch'
 with the branchname as the argument.
 
 
@@ -808,7 +808,7 @@ being the same as the original `master` branch, let's make sure we're in
 that branch, and do some work there.
 
 ------------------------------------------------
-$ git checkout mybranch
+$ git switch mybranch
 $ echo "Work, work, work" >>hello
 $ git commit -m "Some work." -i hello
 ------------------------------------------------
@@ -825,7 +825,7 @@ does some work in the original branch, and simulate that by going back
 to the master branch, and editing the same file differently there:
 
 ------------
-$ git checkout master
+$ git switch master
 ------------
 
 Here, take a moment to look at the contents of `hello`, and notice how they
@@ -958,7 +958,7 @@ to the `master` branch. Let's go back to `mybranch`, and run
 'git merge' to get the "upstream changes" back to your branch.
 
 ------------
-$ git checkout mybranch
+$ git switch mybranch
 $ git merge -m "Merge upstream changes." master
 ------------
 
@@ -1133,9 +1133,8 @@ Remember, before running 'git merge', our `master` head was at
 work." commit.
 
 ------------
-$ git checkout mybranch
-$ git reset --hard master^2
-$ git checkout master
+$ git switch -C mybranch master^2
+$ git switch master
 $ git reset --hard master^
 ------------
 
diff --git a/Documentation/giteveryday.txt b/Documentation/giteveryday.txt
index 9f2528fc8c..ad455f3e39 100644
--- a/Documentation/giteveryday.txt
+++ b/Documentation/giteveryday.txt
@@ -41,7 +41,7 @@ following commands.
 
   * linkgit:git-log[1] to see what happened.
 
-  * linkgit:git-checkout[1] and linkgit:git-branch[1] to switch
+  * linkgit:git-switch[1] and linkgit:git-branch[1] to switch
     branches.
 
   * linkgit:git-add[1] to manage the index file.
@@ -80,7 +80,7 @@ $ git tag v2.43 <2>
 Create a topic branch and develop.::
 +
 ------------
-$ git checkout -b alsa-audio <1>
+$ git switch -c alsa-audio <1>
 $ edit/compile/test
 $ git checkout -- curses/ux_audio_oss.c <2>
 $ git add curses/ux_audio_alsa.c <3>
@@ -90,7 +90,7 @@ $ git commit -a -s <5>
 $ edit/compile/test
 $ git diff HEAD^ <6>
 $ git commit -a --amend <7>
-$ git checkout master <8>
+$ git switch master <8>
 $ git merge alsa-audio <9>
 $ git log --since='3 days ago' <10>
 $ git log v2.43.. curses/ <11>
@@ -148,11 +148,11 @@ Clone the upstream and work on it.  Feed changes to upstream.::
 ------------
 $ git clone git://git.kernel.org/pub/scm/.../torvalds/linux-2.6 my2.6
 $ cd my2.6
-$ git checkout -b mine master <1>
+$ git switch -c mine master <1>
 $ edit/compile/test; git commit -a -s <2>
 $ git format-patch master <3>
 $ git send-email --to="person <email@example.com>" 00*.patch <4>
-$ git checkout master <5>
+$ git switch master <5>
 $ git pull <6>
 $ git log -p ORIG_HEAD.. arch/i386 include/asm-i386 <7>
 $ git ls-remote --heads http://git.kernel.org/.../jgarzik/libata-dev.git <8>
@@ -194,7 +194,7 @@ satellite$ edit/compile/test/commit
 satellite$ git push origin <4>
 
 mothership$ cd frotz
-mothership$ git checkout master
+mothership$ git switch master
 mothership$ git merge satellite/master <5>
 ------------
 +
@@ -216,7 +216,7 @@ machine into the master branch.
 Branch off of a specific tag.::
 +
 ------------
-$ git checkout -b private2.6.14 v2.6.14 <1>
+$ git switch -c private2.6.14 v2.6.14 <1>
 $ edit/compile/test; git commit -a
 $ git checkout master
 $ git cherry-pick v2.6.14..private2.6.14 <2>
@@ -274,14 +274,14 @@ $ mailx <3>
 & s 2 3 4 5 ./+to-apply
 & s 7 8 ./+hold-linus
 & q
-$ git checkout -b topic/one master
+$ git switch -c topic/one master
 $ git am -3 -i -s ./+to-apply <4>
 $ compile/test
-$ git checkout -b hold/linus && git am -3 -i -s ./+hold-linus <5>
-$ git checkout topic/one && git rebase master <6>
-$ git checkout pu && git reset --hard next <7>
+$ git switch -c hold/linus && git am -3 -i -s ./+hold-linus <5>
+$ git switch topic/one && git rebase master <6>
+$ git switch -C pu next <7>
 $ git merge topic/one topic/two && git merge hold/linus <8>
-$ git checkout maint
+$ git switch maint
 $ git cherry-pick master~4 <9>
 $ compile/test
 $ git tag -s -m "GIT 0.99.9x" v0.99.9x <10>
diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt
index 242de31cb6..e6ad6b5f8d 100644
--- a/Documentation/gittutorial.txt
+++ b/Documentation/gittutorial.txt
@@ -207,7 +207,7 @@ automatically.  The asterisk marks the branch you are currently on;
 type
 
 ------------------------------------------------
-$ git checkout experimental
+$ git switch experimental
 ------------------------------------------------
 
 to switch to the experimental branch.  Now edit a file, commit the
@@ -216,7 +216,7 @@ change, and switch back to the master branch:
 ------------------------------------------------
 (edit file)
 $ git commit -a
-$ git checkout master
+$ git switch master
 ------------------------------------------------
 
 Check that the change you made is no longer visible, since it was
diff --git a/Documentation/gitworkflows.txt b/Documentation/gitworkflows.txt
index ca11c7bdaf..abc0dc6bc7 100644
--- a/Documentation/gitworkflows.txt
+++ b/Documentation/gitworkflows.txt
@@ -301,8 +301,7 @@ topics on 'next':
 .Rewind and rebuild next
 [caption="Recipe: "]
 =====================================
-* `git checkout next`
-* `git reset --hard master`
+* `git switch -C next master`
 * `git merge ai/topic_in_next1`
 * `git merge ai/topic_in_next2`
 * ...
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
index 72daa20e76..a1c7a65da6 100644
--- a/Documentation/revisions.txt
+++ b/Documentation/revisions.txt
@@ -115,7 +115,7 @@ Here's an example to make it more clear:
 ------------------------------
 $ git config push.default current
 $ git config remote.pushdefault myfork
-$ git checkout -b mybranch origin/master
+$ git switch -c mybranch origin/master
 
 $ git rev-parse --symbolic-full-name @{upstream}
 refs/remotes/origin/master
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index eff7890274..94799faa2b 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -122,10 +122,10 @@ Tags are expected to always point at the same version of a project,
 while heads are expected to advance as development progresses.
 
 Create a new branch head pointing to one of these versions and check it
-out using linkgit:git-checkout[1]:
+out using linkgit:git-switch[1]:
 
 ------------------------------------------------
-$ git checkout -b new v2.6.13
+$ git switch -c new v2.6.13
 ------------------------------------------------
 
 The working directory then reflects the contents that the project had
@@ -282,10 +282,10 @@ a summary of the commands:
 	this command will fail with a warning.
 `git branch -D <branch>`::
 	delete the branch `<branch>` irrespective of its merged status.
-`git checkout <branch>`::
+`git switch <branch>`::
 	make the current branch `<branch>`, updating the working
 	directory to reflect the version referenced by `<branch>`.
-`git checkout -b <new> <start-point>`::
+`git switch -c <new> <start-point>`::
 	create a new branch `<new>` referencing `<start-point>`, and
 	check it out.
 
@@ -302,22 +302,22 @@ ref: refs/heads/master
 Examining an old version without creating a new branch
 ------------------------------------------------------
 
-The `git checkout` command normally expects a branch head, but will also
-accept an arbitrary commit; for example, you can check out the commit
-referenced by a tag:
+The `git switch` command normally expects a branch head, but will also
+accept an arbitrary commit when invoked with --detach; for example,
+you can check out the commit referenced by a tag:
 
 ------------------------------------------------
-$ git checkout v2.6.17
+$ git switch --detach v2.6.17
 Note: checking out 'v2.6.17'.
 
 You are in 'detached HEAD' state. You can look around, make experimental
 changes and commit them, and you can discard any commits you make in this
-state without impacting any branches by performing another checkout.
+state without impacting any branches by performing another switch.
 
 If you want to create a new branch to retain commits you create, you may
-do so (now or later) by using -b with the checkout command again. Example:
+do so (now or later) by using -c with the switch command again. Example:
 
-  git checkout -b new_branch_name
+  git switch -c new_branch_name
 
 HEAD is now at 427abfa Linux v2.6.17
 ------------------------------------------------
@@ -373,7 +373,7 @@ You might want to build on one of these remote-tracking branches
 on a branch of your own, just as you would for a tag:
 
 ------------------------------------------------
-$ git checkout -b my-todo-copy origin/todo
+$ git switch -c my-todo-copy origin/todo
 ------------------------------------------------
 
 You can also check out `origin/todo` directly to examine it or
@@ -2211,8 +2211,8 @@ $ git branch --track release origin/master
 These can be easily kept up to date using linkgit:git-pull[1].
 
 -------------------------------------------------
-$ git checkout test && git pull
-$ git checkout release && git pull
+$ git switch test && git pull
+$ git switch release && git pull
 -------------------------------------------------
 
 Important note!  If you have any local changes in these branches, then
@@ -2264,7 +2264,7 @@ tested changes
 2) help future bug hunters that use `git bisect` to find problems
 
 -------------------------------------------------
-$ git checkout -b speed-up-spinlocks v2.6.35
+$ git switch -c speed-up-spinlocks v2.6.35
 -------------------------------------------------
 
 Now you apply the patch(es), run some tests, and commit the change(s).  If
@@ -2279,7 +2279,7 @@ When you are happy with the state of this change, you can merge it into the
 "test" branch in preparation to make it public:
 
 -------------------------------------------------
-$ git checkout test && git merge speed-up-spinlocks
+$ git switch test && git merge speed-up-spinlocks
 -------------------------------------------------
 
 It is unlikely that you would have any conflicts here ... but you might if you
@@ -2291,7 +2291,7 @@ see the value of keeping each patch (or patch series) in its own branch.  It
 means that the patches can be moved into the `release` tree in any order.
 
 -------------------------------------------------
-$ git checkout release && git merge speed-up-spinlocks
+$ git switch release && git merge speed-up-spinlocks
 -------------------------------------------------
 
 After a while, you will have a number of branches, and despite the
@@ -2512,7 +2512,7 @@ Suppose that you create a branch `mywork` on a remote-tracking branch
 `origin`, and create some commits on top of it:
 
 -------------------------------------------------
-$ git checkout -b mywork origin
+$ git switch -c mywork origin
 $ vi file.txt
 $ git commit
 $ vi otherfile.txt
@@ -2552,7 +2552,7 @@ commits without any merges, you may instead choose to use
 linkgit:git-rebase[1]:
 
 -------------------------------------------------
-$ git checkout mywork
+$ git switch mywork
 $ git rebase origin
 -------------------------------------------------
 
@@ -3668,13 +3668,13 @@ change within the submodule, and then update the superproject to reference the
 new commit:
 
 -------------------------------------------------
-$ git checkout master
+$ git switch master
 -------------------------------------------------
 
 or
 
 -------------------------------------------------
-$ git checkout -b fix-up
+$ git switch -c fix-up
 -------------------------------------------------
 
 then
@@ -4194,7 +4194,7 @@ start.
 A good place to start is with the contents of the initial commit, with:
 
 ----------------------------------------------------
-$ git checkout e83c5163
+$ git switch --detach e83c5163
 ----------------------------------------------------
 
 The initial revision lays the foundation for almost everything Git has
@@ -4437,10 +4437,10 @@ Managing branches
 -----------------
 
 -----------------------------------------------
-$ git branch	     # list all local branches in this repo
-$ git checkout test  # switch working directory to branch "test"
-$ git branch new     # create branch "new" starting at current HEAD
-$ git branch -d new  # delete branch "new"
+$ git branch			# list all local branches in this repo
+$ git switch test	        # switch working directory to branch "test"
+$ git branch new		# create branch "new" starting at current HEAD
+$ git branch -d new		# delete branch "new"
 -----------------------------------------------
 
 Instead of basing a new branch on current HEAD (the default), use:
@@ -4456,7 +4456,7 @@ $ git branch new test~10 # ten commits before tip of branch "test"
 Create and switch to a new branch at the same time:
 
 -----------------------------------------------
-$ git checkout -b new v2.6.15
+$ git switch -c new v2.6.15
 -----------------------------------------------
 
 Update and examine branches from the repository you cloned from:
@@ -4467,7 +4467,7 @@ $ git branch -r		# list
   origin/master
   origin/next
   ...
-$ git checkout -b masterwork origin/master
+$ git switch -c masterwork origin/master
 -----------------------------------------------
 
 Fetch a branch from a different repository, and give it a new
diff --git a/advice.c b/advice.c
index b224825637..27e39e6514 100644
--- a/advice.c
+++ b/advice.c
@@ -191,20 +191,20 @@ void NORETURN die_conclude_merge(void)
 void detach_advice(const char *new_name)
 {
 	const char *fmt =
-	_("Note: checking out '%s'.\n"
+	_("Note: switching to '%s'.\n"
 	"\n"
 	"You are in 'detached HEAD' state. You can look around, make experimental\n"
 	"changes and commit them, and you can discard any commits you make in this\n"
-	"state without impacting any branches by performing another checkout.\n"
+	"state without impacting any branches by switching back to a branch.\n"
 	"\n"
 	"If you want to create a new branch to retain commits you create, you may\n"
-	"do so (now or later) by using -b with the checkout command again. Example:\n"
+	"do so (now or later) by using -c with the switch command. Example:\n"
 	"\n"
-	"  git checkout -b <new-branch-name>\n"
+	"  git switch -c <new-branch-name>\n"
 	"\n"
 	"Or undo this checkout with:\n"
 	"\n"
-	"  git checkout -\n"
+	"  git switch -\n"
 	"\n"
 	"Turn off this advice by setting config variable advice.detachedHead to false\n\n");
 
diff --git a/sha1-name.c b/sha1-name.c
index 6dda2c16df..da0518c8e3 100644
--- a/sha1-name.c
+++ b/sha1-name.c
@@ -743,7 +743,7 @@ static int get_oid_basic(const char *str, int len, struct object_id *oid,
 	"because it will be ignored when you just specify 40-hex. These refs\n"
 	"may be created by mistake. For example,\n"
 	"\n"
-	"  git checkout -b $br $(git rev-parse ...)\n"
+	"  git switch -c $br $(git rev-parse ...)\n"
 	"\n"
 	"where \"$br\" is somehow empty and a 40-hex ref is created. Please\n"
 	"examine these refs and maybe delete them. Turn this message off by\n"
diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
index 743c7248a2..da6809b48f 100755
--- a/t/t2020-checkout-detach.sh
+++ b/t/t2020-checkout-detach.sh
@@ -195,20 +195,20 @@ test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not as
 
 	# The first detach operation is more chatty than the following ones.
 	cat >1st_detach <<-EOF &&
-	Note: checking out 'HEAD^'.
+	Note: switching to 'HEAD^'.
 
 	You are in 'detached HEAD' state. You can look around, make experimental
 	changes and commit them, and you can discard any commits you make in this
-	state without impacting any branches by performing another checkout.
+	state without impacting any branches by switching back to a branch.
 
 	If you want to create a new branch to retain commits you create, you may
-	do so (now or later) by using -b with the checkout command again. Example:
+	do so (now or later) by using -c with the switch command. Example:
 
-	  git checkout -b <new-branch-name>
+	  git switch -c <new-branch-name>
 
 	Or undo this checkout with:
 
-	  git checkout -
+	  git switch -
 
 	Turn off this advice by setting config variable advice.detachedHead to false
 
@@ -277,20 +277,20 @@ test_expect_success 'describe_detached_head does print SHA-1 ellipsis when asked
 
 	# The first detach operation is more chatty than the following ones.
 	cat >1st_detach <<-EOF &&
-	Note: checking out 'HEAD^'.
+	Note: switching to 'HEAD^'.
 
 	You are in 'detached HEAD' state. You can look around, make experimental
 	changes and commit them, and you can discard any commits you make in this
-	state without impacting any branches by performing another checkout.
+	state without impacting any branches by switching back to a branch.
 
 	If you want to create a new branch to retain commits you create, you may
-	do so (now or later) by using -b with the checkout command again. Example:
+	do so (now or later) by using -c with the switch command. Example:
 
-	  git checkout -b <new-branch-name>
+	  git switch -c <new-branch-name>
 
 	Or undo this checkout with:
 
-	  git checkout -
+	  git switch -
 
 	Turn off this advice by setting config variable advice.detachedHead to false
 
-- 
2.21.0.548.gd3c7d92dc2
Duy Nguyen· Mar 17, 2019, 12:58 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v4 00/26] Add new command 'switch'

On Sun, Mar 17, 2019 at 7:49 PM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 27 quoted lines
>
> Thanks for all the comments from v3 (and before), I didn't expect
> feedback from so many people. v4 fixes most of them, but still leaves
> a couple for v5.
>
> - -C remains because people seem to need it
>
> - --recreate vs --force-create: there was no strong preference for
>   either, so I stick with --force-create
>
> - --guess (dwim mode) is now made default
>
> - --orphan by default switches to an empty tree instead of current HEAD
>   (this is Elijah's alternative approach). This allows you to start
>   fresh, but you can still keep "HEAD" content or even switch to another
>   tree.
>
> - advice.detachedHead text is updated to suggest a way out (currently it
>   only suggests a way forward with 'checkout -b')
>
> - --force has a new alias --discard-changes
>
> - the error message when we expect a branch is improved to help point
>   out why the given argument is not a branch
>
> - more docs update and reorganization, and other silly bugs I made in
>   v3
I did forget one thing:
- 'git switch' refuses to switch in the middle of
rebase/bisect/cherry-pick/... session. Add --ignore-in-progress to
override this

Now that I mention this, --ignore-in-progress should probably be part of --force too. Hmm...

-- 
Duy
Junio C Hamano· Mar 18, 2019, 04:54 UTC · re: Duy Nguyen · lore

Re: [PATCH v4 00/26] Add new command 'switch'

Duy Nguyen <pclouds@gmail.com> writes:
Show 5 quoted lines
> I did forget one thing:
>
> - 'git switch' refuses to switch in the middle of
> rebase/bisect/cherry-pick/... session. Add --ignore-in-progress to
> override this

I think that is a sensible thing to do. Once I did not realize that a "checkout" from a failed cherry-pick with the state files left in the .git/ directory took me to a strange state, causing some later operation to act on these state files as if a cherry-pick were in progress (and somehow the shell prompt support did not report it, perhaps because the copy I use was out of sync with the binary).

Making sure that during a stateful operation the working tree won't get switched to a different branch is probably a good idea.

I often find "git reset --hard <some other starting point>" after a failed "git checkout -b new <starting point> && git am -s3c", so I'd be opposed to a change to refuse a "reset --hard" during the middle of such a stateful operation, but I do not think of a reason why I would want to "switch" in the middle.

> Now that I mention this, --ignore-in-progress should probably be part
> of --force too. Hmm...

Probably. You may want to decline other parts of what --force allows but when you say --force, --ignore-in-progress should be implied.

Duy Nguyen· Mar 18, 2019, 12:13 UTC · re: Junio C Hamano · lore

Re: [PATCH v4 00/26] Add new command 'switch'

On Mon, Mar 18, 2019 at 11:54 AM Junio C Hamano <gitster@pobox.com> wrote:
> I often find "git reset --hard <some other starting point>" after a

It still bugs me that I need to use this to abort some in-progress operation. There's "git X --abort" but I would need to find out what X is first. I would like "git abort" or something (and "git continue") but haven't really thought that through yet. That direction may also lead to "git undo" which is another can of worms.

> failed "git checkout -b new <starting point> && git am -s3c", so I'd
> be opposed to a change to refuse a "reset --hard" during the middle
> of such a stateful operation, but I do not think of a reason why I
> would want to "switch" in the middle.
-- 
Duy
Junio C Hamano· Mar 19, 2019, 00:43 UTC · re: Duy Nguyen · lore

Re: [PATCH v4 00/26] Add new command 'switch'

Duy Nguyen <pclouds@gmail.com> writes:
Show 5 quoted lines
> On Mon, Mar 18, 2019 at 11:54 AM Junio C Hamano <gitster@pobox.com> wrote:
>> I often find "git reset --hard <some other starting point>" after a
>
> It still bugs me that I need to use this to abort some in-progress
> operation.

Note that my comment was *NOT* about aborting the in-progress "am". What happens in that "I often find" situation is that I identify the problem's origin, prepare a branch to apply a fix forking from an old release point that includes the origin, and "am" tells me the code diverged too much and it cannot be applied, or "am -3" tells me that the author sent the patch based on some unknown base. At that point, I "git reset --hard" to some plausible place the author would have based the patch (e.g. "master" pushed out last night) and then say "am" or ("am --no-3") to retry.

So your response is unrelated to what I said, but having said that, it still deserves a comment.

> ... There's "git X --abort" but I would need to find out what X
> is first. I would like "git abort" or something (and "git continue")
> but haven't really thought that through yet. That direction may also
> lead to "git undo" which is another can of worms.

I rarely had "what was I doing" problem; doesn't the command line prompt tell you? It is debatable (and I am pessimistic) if a general blanket "git abort" makes sense, but the info on the current status (i.e. "what was the user doing?") it must gather should be a strict superset as what is needed to give useful info in the command line prompt, so a good place to start may be to think about what's missing (not shown) in the prompt and if that is deliberate omission (avoiding overly verbose prompt) or is coming from lack of enough information. Then we may need to do something about the latter by teaching these commands with state to leave more clue of what we were in the middle of and what the next step would be.

After thinking about that, the answer might turn out to be to unify the backend of these commands with state to a single one, or at least fewer ones, so that we can have better grasp and control over how these backend engines record state when they give control back to the user.

Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 00/26] Add new command 'switch'

This adds a new command 'git-switch' as the half-replacement for 'git-checkout'. Jump to 12/26 as the starting point. The other half is git-restore, which is dealt with separately.

The diff delta is shrinking nicely. The two main changes are
- '--orphan <new-branch> <initial-tree>' is no longer accepted
- --force implies --ignore-in-progress
The rest is the usual typo and bug fixes.

The two "todo" items from v4 will be handled separately and will not be mentioned again in v6 (if there is one):

 - The 'checkout -m' losing staged changes problem has already been
discussed elsewhere and I'm still trying to see if I can improve it.
 - I have some work in progress for improving tracking branch error
message, but it's unlikely to conflict badly with this series.
Nguyễn Thái Ngọc Duy (26):
  git-checkout.txt: spell out --no-option
  git-checkout.txt: fix one syntax line
  doc: document --overwrite-ignore
  git-checkout.txt: fix monospace typeset
  t: rename t2014-switch.sh to t2014-checkout-switch.sh
  checkout: advice how to get out of detached HEAD mode
  checkout: keep most #include sorted
  checkout: factor out some code in parse_branchname_arg()
  checkout: make "opts" in cmd_checkout() a pointer
  checkout: move 'confict_style' and 'dwim_..' to checkout_opts
  checkout: split options[] array in three pieces
  checkout: split part of it to new command 'switch'
  switch: better names for -b and -B
  switch: add --discard-changes
  switch: remove -l
  switch: stop accepting pathspec
  switch: reject "do nothing" case
  switch: only allow explicit detached HEAD
  switch: add short option for --detach
  switch: implicit dwim, use --no-guess to disable it
  switch: no worktree status unless real branch switch happens
  switch: reject if some operation is in progress
  switch: make --orphan switch to an empty tree
  t: add tests for switch
  completion: support switch
  doc: promote "git switch"
 .gitignore                                    |   1 +
 Documentation/config/advice.txt               |  13 +-
 Documentation/config/branch.txt               |   4 +-
 Documentation/config/checkout.txt             |  17 +-
 Documentation/config/diff.txt                 |   3 +-
 Documentation/git-branch.txt                  |  12 +-
 Documentation/git-check-ref-format.txt        |   3 +-
 Documentation/git-checkout.txt                | 221 ++++---
 Documentation/git-format-patch.txt            |   2 +-
 Documentation/git-merge-base.txt              |   2 +-
 Documentation/git-merge.txt                   |   5 +
 Documentation/git-rebase.txt                  |   2 +-
 Documentation/git-remote.txt                  |   2 +-
 Documentation/git-rerere.txt                  |  10 +-
 Documentation/git-reset.txt                   |  20 +-
 Documentation/git-stash.txt                   |   9 +-
 Documentation/git-switch.txt (new)            | 276 ++++++++
 Documentation/gitattributes.txt               |   3 +-
 Documentation/gitcore-tutorial.txt            |  19 +-
 Documentation/giteveryday.txt                 |  24 +-
 Documentation/githooks.txt                    |   8 +-
 Documentation/gittutorial.txt                 |   4 +-
 Documentation/gitworkflows.txt                |   3 +-
 Documentation/revisions.txt                   |   2 +-
 Documentation/user-manual.txt                 |  56 +-
 Makefile                                      |   1 +
 advice.c                                      |  17 +-
 builtin.h                                     |   1 +
 builtin/checkout.c                            | 605 ++++++++++--------
 command-list.txt                              |   1 +
 contrib/completion/git-completion.bash        |  37 +-
 git.c                                         |   1 +
 parse-options-cb.c                            |  17 +
 parse-options.h                               |   1 +
 sha1-name.c                                   |   2 +-
 t/t1090-sparse-checkout-scope.sh              |  14 -
 ...014-switch.sh => t2014-checkout-switch.sh} |   0
 t/t2020-checkout-detach.sh                    |  28 +-
 t/t2060-switch.sh (new +x)                    |  98 +++
 39 files changed, 1051 insertions(+), 493 deletions(-)
 create mode 100644 Documentation/git-switch.txt
 rename t/{t2014-switch.sh => t2014-checkout-switch.sh} (100%)
 create mode 100755 t/t2060-switch.sh
Range-diff dựa trên v4:
 1:  535dc1f310 !  1:  7bcb4b0ff8 doc: document --overwrite-ignore
    @@ -22,20 +22,7 @@
     +
      --recurse-submodules::
      --no-recurse-submodules::
    --	Using --recurse-submodules will update the content of all initialized
    -+	Using `--recurse-submodules` will update the content of all initialized
    - 	submodules according to the commit recorded in the superproject. If
    - 	local modifications in a submodule would be overwritten the checkout
    --	will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
    -+	will fail unless `-f` is used. If nothing (or `--no-recurse-submodules`)
    - 	is used, the work trees of submodules will not be updated.
    --	Just like linkgit:git-submodule[1], this will detach the
    --	submodules HEAD.
    -+	Just like linkgit:git-submodule[1], this will detach `HEAD` of the
    -+	submodule.
    - 
    - --no-guess::
    - 	Do not attempt to create a branch if a remote tracking branch
    + 	Using --recurse-submodules will update the content of all initialized
     
      diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
      --- a/Documentation/git-merge.txt
 2:  b6305d2871 !  2:  f2d77152eb git-checkout.txt: fix monospace typeset
    @@ -4,8 +4,7 @@
     
         Add backticks where we have none, replace single quotes with backticks
         and replace double-quotes. Drop double-quotes from nested constructions
    -    such as `"@{-1}"`. Add a missing possessive apostrophe after the word
    -    "submodules" while at it.
    +    such as `"@{-1}"`.
     
         Helped-by: Martin Ågren <martin.agren@gmail.com>
         Signed-off-by: Martin Ågren <martin.agren@gmail.com>
    @@ -207,6 +206,24 @@
      This means that you can use `git checkout -p` to selectively discard
      edits from your current working tree. See the ``Interactive Mode''
     @@
    + 
    + --recurse-submodules::
    + --no-recurse-submodules::
    +-	Using --recurse-submodules will update the content of all initialized
    ++	Using `--recurse-submodules` will update the content of all initialized
    + 	submodules according to the commit recorded in the superproject. If
    + 	local modifications in a submodule would be overwritten the checkout
    +-	will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
    ++	will fail unless `-f` is used. If nothing (or `--no-recurse-submodules`)
    + 	is used, the work trees of submodules will not be updated.
    +-	Just like linkgit:git-submodule[1], this will detach the
    +-	submodules HEAD.
    ++	Just like linkgit:git-submodule[1], this will detach `HEAD` of the
    ++	submodule.
    + 
    + --no-guess::
    + 	Do not attempt to create a branch if a remote tracking branch
    +@@
      	In the default overlay mode, `git checkout` never
      	removes files from the index or the working tree.  When
      	specifying `--no-overlay`, files that appear in the index and
 3:  bb56e45457 =  3:  cbe630fb4a t: rename t2014-switch.sh to t2014-checkout-switch.sh
 4:  123392757a =  4:  6bc8a1052f checkout: advice how to get out of detached HEAD mode
 5:  d1ec6b4ce0 =  5:  0bb2aefb85 checkout: keep most #include sorted
 6:  4b1742672b =  6:  ace82aa26f checkout: factor out some code in parse_branchname_arg()
 7:  e0bcc3a4dd =  7:  6e13efcba8 checkout: make "opts" in cmd_checkout() a pointer
 8:  ca5b4d7db0 =  8:  f61a042eb8 checkout: move 'confict_style' and 'dwim_..' to checkout_opts
 9:  13c03997f0 =  9:  972cebc568 checkout: split options[] array in three pieces
10:  24d532b276 ! 10:  970c727b24 checkout: split part of it to new command 'switch'
    @@ -147,9 +147,10 @@
     +SYNOPSIS
     +--------
     +[verse]
    -+'git switch' [<options>] [--guess] <branch>
    ++'git switch' [<options>] [--no-guess] <branch>
     +'git switch' [<options>] --detach [<start-point>]
    -+'git switch' [<options>] (-c|-C|--orphan) <new-branch> [<start-point>]
    ++'git switch' [<options>] (-c|-C) <new-branch> [<start-point>]
    ++'git switch' [<options>] --orphan <new-branch>
     +
     +DESCRIPTION
     +-----------
    @@ -164,8 +165,8 @@
     +
     +Switching branches does not require a clean index and working tree
     +(i.e. no differences compared to `HEAD`). The operation is aborted
    -+however if the switch leads to loss of local changes, unless told
    -+otherwise.
    ++however if the operation leads to loss of local changes, unless told
    ++otherwise with `--discard-changes` or `--merge`.
     +
     +OPTIONS
     +-------
    @@ -183,7 +184,7 @@
     +	from some other point.)
     ++
     +You can use the `@{-N}` syntax to refer to the N-th last
    -+branch/commit switched to "git switch" or "git checkout"
    ++branch/commit switched to using "git switch" or "git checkout"
     +operation. You may also specify `-` which is synonymous to `@{-1}`.
     +This is often used to switch quickly between two branches, or to undo
     +a branch switch by mistake.
    @@ -242,7 +243,7 @@
     +
     +-f::
     +--force::
    -+	An alias for `--discard-changes`.
    ++	An alias for `--discard-changes` and `--ignore-in-progress`.
     +
     +--discard-changes::
     +	Proceed even if the index or the working tree differs from
    @@ -250,6 +251,11 @@
     +	the switching target. This is used to throw away local
     +	changes.
     +
    ++--ignore-in-progress::
    ++	`git switch` by default refuses when some operation is in
    ++	progress (e.g. "git rebase", "git am" ...). This option
    ++	overrides this safety check and allows switching.
    ++
     +-m::
     +--merge::
     +	If you have local modifications to one or more files that are
    @@ -304,10 +310,8 @@
     +	`branch.autoSetupMerge` configuration variable is true.
     +
     +--orphan <new-branch>::
    -+	Create a new 'orphan' branch, named `<new-branch>`. If
    -+	`<start-point>` is specified, the index and working tree are
    -+	adjusted to match it. Otherwise both are adjusted to contain no
    -+	tracked files.
    ++	Create a new 'orphan' branch, named `<new-branch>`. All
    ++	tracked files are removed.
     +
     +--ignore-other-worktrees::
     +	`git switch` refuses when the wanted ref is already
    @@ -315,11 +319,6 @@
     +	the ref out anyway. In other words, the ref can be held by
     +	more than one worktree.
     +
    -+--ignore-in-progress::
    -+	`git switch` by default refuses when some operation is in
    -+	progress (e.g. "git rebase", "git am" ...). This option
    -+	overrides this safety check and allows switching.
    -+
     +--recurse-submodules::
     +--no-recurse-submodules::
     +	Using `--recurse-submodules` will update the content of all initialized
11:  c966bacfcc = 11:  676f5df0fd switch: better names for -b and -B
12:  bdb88bf9a9 ! 12:  4e37c0659e switch: add --discard-changes
    @@ -4,8 +4,8 @@
     
         --discard-changes is a better name than --force for this option since
         it's what really happens. --force is turned to an alias for
    -    --discard-changes. But it's meant to an alias for potentially more force
    -    options in the future.
    +    --discard-changes. But it's meant to be an alias for potentially more
    +    force options in the future.
     
      diff --git a/builtin/checkout.c b/builtin/checkout.c
      --- a/builtin/checkout.c
13:  d5fe7f4bd0 = 13:  a57208d137 switch: remove -l
14:  3bce4c521e = 14:  3de6f95bf2 switch: stop accepting pathspec
15:  dad0063fc4 = 15:  ad225517cd switch: reject "do nothing" case
16:  41ca042917 = 16:  583cfd5cc4 switch: only allow explicit detached HEAD
17:  a0b9f1b285 = 17:  1c5aee658d switch: add short option for --detach
18:  3d254df104 = 18:  d942ac52e2 switch: implicit dwim, use --no-guess to disable it
19:  c6ea203f36 = 19:  37eb152c0d switch: no worktree status unless real branch switch happens
20:  5c4effc7fd ! 20:  50d6768afd switch: reject if some operation is in progress
    @@ -12,6 +12,9 @@
         that separate thing you want to work on and leave this worktree
         alone (unless of course creating or preparing worktrees are not cheap).
     
    +    --force is updated to also imply --ignore-in-progress because it is
    +    supposed to be the "just do your things and don't bother me" option.
    +
      diff --git a/builtin/checkout.c b/builtin/checkout.c
      --- a/builtin/checkout.c
      +++ b/builtin/checkout.c
    @@ -45,7 +48,7 @@
     +			die(_("cannot switch branch while merging"));
     +		if (state.am_in_progress)
     +			die(_("cannot switch branch in the middle of an am session"));
    -+		if (state.rebase_in_progress || state.rebase_in_progress)
    ++		if (state.rebase_interactive_in_progress || state.rebase_in_progress)
     +			die(_("cannot switch branch while rebasing"));
     +		if (state.cherry_pick_in_progress)
     +			die(_("cannot switch branch while cherry-picking"));
    @@ -58,6 +61,18 @@
      	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
      	    !opts->ignore_other_worktrees) {
      		int flag;
    +@@
    + 		opts->merge = 1; /* implied */
    + 		git_xmerge_config("merge.conflictstyle", opts->conflict_style, NULL);
    + 	}
    +-	if (opts->force)
    ++	if (opts->force) {
    + 		opts->discard_changes = 1;
    ++		opts->can_switch_when_in_progress = 1;
    ++	}
    + 
    + 	if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
    + 		die(_("-b, -B and --orphan are mutually exclusive"));
     @@
      	opts.only_merge_on_switching_branches = 0;
      	opts.accept_pathspec = 1;
21:  a4afe6a999 ! 21:  af8bb710c8 switch: --orphan defaults to empty tree as HEAD
    @@ -1,15 +1,16 @@
     Author: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
     
    -    switch: --orphan defaults to empty tree as HEAD
    +    switch: make --orphan switch to an empty tree
     
         Switching and creating branches always involves knowing the
         <start-point> to begin the new branch from. Sometimes, people want to
         create a new branch that does not have any commits yet; --orphan is a
         flag to allow that.
     
    -    If <start-point> is not specified, instead of leaving index/worktree
    -    unchanged, default to "empty tree" starting point, allowing a clean
    -    start.
    +    --orphan overrides the default of HEAD for <start-point> instead causing
    +    us to start from an empty history with all tracked files removed from
    +    the index and working tree. The use of --orphan is incompatible with
    +    specifying a <start-point>.
     
         A note on the implementation. An alternative is just create a dummy
         commit in-core with empty tree and switch to it. But there's a chance
    @@ -23,7 +24,7 @@
      	int switch_branch_doing_nothing_is_ok;
      	int only_merge_on_switching_branches;
      	int can_switch_when_in_progress;
    -+	int orphan_default_empty_tree;
    ++	int orphan_from_empty_tree;
      
      	const char *new_branch;
      	const char *new_branch_force;
    @@ -38,9 +39,11 @@
      		return error(_("index file corrupt"));
      
      	resolve_undo_clear();
    -+	if (opts->orphan_default_empty_tree && !new_branch_info->commit)
    ++	if (opts->new_orphan_branch && opts->orphan_from_empty_tree) {
    ++		if (new_branch_info->commit)
    ++			BUG("'switch --orphan' should never accept a commit as starting point");
     +		new_tree = parse_tree_indirect(the_hash_algo->empty_tree);
    -+	else
    ++	} else
     +		new_tree = get_commit_tree(new_branch_info->commit);
      	if (opts->discard_changes) {
     -		ret = reset_tree(get_commit_tree(new_branch_info->commit),
    @@ -106,23 +109,34 @@
      	if (prepare_revision_walk(&revs))
      		die(_("internal error in revision walk"));
     @@
    + 	if (old_branch_info.path)
    + 		skip_prefix(old_branch_info.path, "refs/heads/", &old_branch_info.name);
      
    - 		if (opts->only_merge_on_switching_branches)
    - 			do_merge = 0;
    ++	if (opts->new_orphan_branch && opts->orphan_from_empty_tree) {
    ++		if (new_branch_info->name)
    ++			BUG("'switch --orphan' should never accept a commit as starting point");
    ++		new_branch_info->commit = NULL;
    ++		new_branch_info->name = "(empty)";
    ++		do_merge = 1;
    ++	}
     +
    -+		if (opts->new_orphan_branch && opts->orphan_default_empty_tree) {
    -+			new_branch_info->commit = NULL;
    -+			new_branch_info->name = "(empty)";
    -+			do_merge = 1;
    -+		}
    - 	}
    - 
    - 	if (do_merge) {
    + 	if (!new_branch_info->name) {
    + 		new_branch_info->name = "HEAD";
    + 		new_branch_info->commit = old_branch_info.commit;
    +@@
    + 	if (opts->new_orphan_branch) {
    + 		if (opts->track != BRANCH_TRACK_UNSPECIFIED)
    + 			die(_("'%s' cannot be used with '%s'"), "--orphan", "-t");
    ++		if (opts->orphan_from_empty_tree && new_branch_info->name)
    ++			die(_("'%s' cannot take <start-point>"), "--orphan");
    + 	} else if (opts->force_detach) {
    + 		if (opts->track != BRANCH_TRACK_UNSPECIFIED)
    + 			die(_("'%s' cannot be used with '%s'"), "--detach", "-t");
     @@
      	opts.accept_pathspec = 1;
      	opts.implicit_detach = 1;
      	opts.can_switch_when_in_progress = 1;
    -+	opts.orphan_default_empty_tree = 0;
    ++	opts.orphan_from_empty_tree = 0;
      
      	options = parse_options_dup(checkout_options);
      	options = add_common_options(&opts, options);
    @@ -130,7 +144,7 @@
      	opts.only_merge_on_switching_branches = 1;
      	opts.implicit_detach = 0;
      	opts.can_switch_when_in_progress = 0;
    -+	opts.orphan_default_empty_tree = 1;
    ++	opts.orphan_from_empty_tree = 1;
      
      	options = parse_options_dup(switch_options);
      	options = add_common_options(&opts, options);
22:  6cca78f835 ! 22:  4a293a3d53 t: add tests for switch
    @@ -67,6 +67,7 @@
     +
     +test_expect_success 'new orphan branch from empty' '
     +	test_when_finished git switch master &&
    ++	test_must_fail git switch --orphan new-orphan HEAD &&
     +	git switch --orphan new-orphan &&
     +	test_commit orphan &&
     +	git cat-file commit refs/heads/new-orphan >commit &&
23:  e5e6f9d6f1 = 23:  b10290b378 completion: support switch
24:  807e8bc50b = 24:  67c5175e5f doc: promote "git switch"
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 01/26] git-checkout.txt: spell out --no-option

It's easier to search for and also less cryptic.
---
 Documentation/git-checkout.txt | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)
Show changes to Documentation/git-checkout.txt +7 −1
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index f179b43732..99c8c0dc0f 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -118,7 +118,8 @@ OPTIONS
 --quiet::
 	Quiet, suppress feedback messages.
 
---[no-]progress::
+--progress::
+--no-progress::
 	Progress status is reported on the standard error stream
 	by default when it is attached to a terminal, unless `--quiet`
 	is specified. This flag enables progress reporting even if not
@@ -262,7 +263,7 @@ edits from your current working tree. See the ``Interactive Mode''
 section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
 +
 Note that this option uses the no overlay mode by default (see also
-`--[no-]overlay`), and currently doesn't support overlay mode.
+`--overlay`), and currently doesn't support overlay mode.
 
 --ignore-other-worktrees::
 	`git checkout` refuses when the wanted ref is already checked
@@ -270,7 +271,8 @@ Note that this option uses the no overlay mode by default (see also
 	out anyway. In other words, the ref can be held by more than one
 	worktree.
 
---[no-]recurse-submodules::
+--recurse-submodules::
+--no-recurse-submodules::
 	Using --recurse-submodules will update the content of all initialized
 	submodules according to the commit recorded in the superproject. If
 	local modifications in a submodule would be overwritten the checkout
@@ -283,7 +285,8 @@ Note that this option uses the no overlay mode by default (see also
 	Do not attempt to create a branch if a remote tracking branch
 	of the same name exists.
 
---[no-]overlay::
+--overlay::
+--no-overlay::
 	In the default overlay mode, `git checkout` never
 	removes files from the index or the working tree.  When
 	specifying `--no-overlay`, files that appear in the index and
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 02/26] git-checkout.txt: fix one syntax line

<branch> can be omitted in this syntax, and it's actually documented a few paragraphs down:

  You could omit <branch>, in which case the command degenerates to
  "check out the current branch", which is a glorified no-op with
  rather expensive side-effects to show only the tracking information,
  if exists, for the current branch.
---
 Documentation/git-checkout.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to Documentation/git-checkout.txt +1 −1
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 99c8c0dc0f..28817cfa41 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -23,7 +23,7 @@ or the specified tree.  If no paths are given, 'git checkout' will
 also update `HEAD` to set the specified branch as the current
 branch.
 
-'git checkout' <branch>::
+'git checkout' [<branch>]::
 	To prepare for working on <branch>, switch to it by updating
 	the index and the files in the working tree, and by pointing
 	HEAD at the branch. Local modifications to the files in the
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 03/26] doc: document --overwrite-ignore

I added this option in git-checkout and git-merge in c1d7036b6b
(checkout,merge: disallow overwriting ignored files with
--no-overwrite-ignore - 2011-11-27) but did not remember to update
documentation. This completes that commit.
---
 Documentation/git-checkout.txt | 6 ++++++
 Documentation/git-merge.txt    | 5 +++++
 2 files changed, 11 insertions(+)
Show changes to 2 files +11 −0

Documentation/git-checkout.txt, Documentation/git-merge.txt

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 28817cfa41..5280d1f9ed 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -271,6 +271,12 @@ Note that this option uses the no overlay mode by default (see also
 	out anyway. In other words, the ref can be held by more than one
 	worktree.
 
+--overwrite-ignore::
+--no-overwrite-ignore::
+	Silently overwrite ignored files when switching branches. This
+	is the default behavior. Use `--no-overwrite-ignore` to abort
+	the operation when the new branch contains ignored files.
+
 --recurse-submodules::
 --no-recurse-submodules::
 	Using --recurse-submodules will update the content of all initialized
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index 4cc86469f3..6a9163d8fe 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -87,6 +87,11 @@ will be appended to the specified message.
 	Allow the rerere mechanism to update the index with the
 	result of auto-conflict resolution if possible.
 
+--overwrite-ignore::
+--no-overwrite-ignore::
+	Silently overwrite ignored files from the merge result. This
+	is the default behavior. Use `--no-overwrite-ignore` to abort.
+
 --abort::
 	Abort the current conflict resolution process, and
 	try to reconstruct the pre-merge state.
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 04/26] git-checkout.txt: fix monospace typeset

Add backticks where we have none, replace single quotes with backticks and replace double-quotes. Drop double-quotes from nested constructions such as `"@{-1}"`.

Helped-by: Martin Ågren <martin.agren@gmail.com>
Signed-off-by: Martin Ågren <martin.agren@gmail.com>
---
 Documentation/git-checkout.txt | 162 ++++++++++++++++-----------------
 1 file changed, 81 insertions(+), 81 deletions(-)
Show changes to Documentation/git-checkout.txt +81 −81
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 5280d1f9ed..bf90966c95 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -24,14 +24,14 @@ also update `HEAD` to set the specified branch as the current
 branch.
 
 'git checkout' [<branch>]::
-	To prepare for working on <branch>, switch to it by updating
+	To prepare for working on `<branch>`, switch to it by updating
 	the index and the files in the working tree, and by pointing
-	HEAD at the branch. Local modifications to the files in the
+	`HEAD` at the branch. Local modifications to the files in the
 	working tree are kept, so that they can be committed to the
-	<branch>.
+	`<branch>`.
 +
-If <branch> is not found but there does exist a tracking branch in
-exactly one remote (call it <remote>) with a matching name, treat as
+If `<branch>` is not found but there does exist a tracking branch in
+exactly one remote (call it `<remote>`) with a matching name, treat as
 equivalent to
 +
 ------------
@@ -47,7 +47,7 @@ branches from there if `<branch>` is ambiguous but exists on the
 'origin' remote. See also `checkout.defaultRemote` in
 linkgit:git-config[1].
 +
-You could omit <branch>, in which case the command degenerates to
+You could omit `<branch>`, in which case the command degenerates to
 "check out the current branch", which is a glorified no-op with
 rather expensive side-effects to show only the tracking information,
 if exists, for the current branch.
@@ -61,7 +61,7 @@ if exists, for the current branch.
 	`--track` without `-b` implies branch creation; see the
 	description of `--track` below.
 +
-If `-B` is given, <new_branch> is created if it doesn't exist; otherwise, it
+If `-B` is given, `<new_branch>` is created if it doesn't exist; otherwise, it
 is reset. This is the transactional equivalent of
 +
 ------------
@@ -75,25 +75,25 @@ successful.
 'git checkout' --detach [<branch>]::
 'git checkout' [--detach] <commit>::
 
-	Prepare to work on top of <commit>, by detaching HEAD at it
+	Prepare to work on top of `<commit>`, by detaching `HEAD` at it
 	(see "DETACHED HEAD" section), and updating the index and the
 	files in the working tree.  Local modifications to the files
 	in the working tree are kept, so that the resulting working
 	tree will be the state recorded in the commit plus the local
 	modifications.
 +
-When the <commit> argument is a branch name, the `--detach` option can
-be used to detach HEAD at the tip of the branch (`git checkout
-<branch>` would check out that branch without detaching HEAD).
+When the `<commit>` argument is a branch name, the `--detach` option can
+be used to detach `HEAD` at the tip of the branch (`git checkout
+<branch>` would check out that branch without detaching `HEAD`).
 +
-Omitting <branch> detaches HEAD at the tip of the current branch.
+Omitting `<branch>` detaches `HEAD` at the tip of the current branch.
 
 'git checkout' [<tree-ish>] [--] <pathspec>...::
 
 	Overwrite paths in the working tree by replacing with the
-	contents in the index or in the <tree-ish> (most often a
-	commit).  When a <tree-ish> is given, the paths that
-	match the <pathspec> are updated both in the index and in
+	contents in the index or in the `<tree-ish>` (most often a
+	commit).  When a `<tree-ish>` is given, the paths that
+	match the `<pathspec>` are updated both in the index and in
 	the working tree.
 +
 The index may contain unmerged entries because of a previous failed merge.
@@ -128,7 +128,7 @@ OPTIONS
 -f::
 --force::
 	When switching branches, proceed even if the index or the
-	working tree differs from HEAD.  This is used to throw away
+	working tree differs from `HEAD`.  This is used to throw away
 	local changes.
 +
 When checking out paths from the index, do not fail upon unmerged
@@ -155,12 +155,12 @@ on your side branch as `theirs` (i.e. "one contributor's work on top
 of it").
 
 -b <new_branch>::
-	Create a new branch named <new_branch> and start it at
-	<start_point>; see linkgit:git-branch[1] for details.
+	Create a new branch named `<new_branch>` and start it at
+	`<start_point>`; see linkgit:git-branch[1] for details.
 
 -B <new_branch>::
-	Creates the branch <new_branch> and start it at <start_point>;
-	if it already exists, then reset it to <start_point>. This is
+	Creates the branch `<new_branch>` and start it at `<start_point>`;
+	if it already exists, then reset it to `<start_point>`. This is
 	equivalent to running "git branch" with "-f"; see
 	linkgit:git-branch[1] for details.
 
@@ -173,15 +173,15 @@ If no `-b` option is given, the name of the new branch will be
 derived from the remote-tracking branch, by looking at the local part of
 the refspec configured for the corresponding remote, and then stripping
 the initial part up to the "*".
-This would tell us to use "hack" as the local branch when branching
-off of "origin/hack" (or "remotes/origin/hack", or even
-"refs/remotes/origin/hack").  If the given name has no slash, or the above
+This would tell us to use `hack` as the local branch when branching
+off of `origin/hack` (or `remotes/origin/hack`, or even
+`refs/remotes/origin/hack`).  If the given name has no slash, or the above
 guessing results in an empty name, the guessing is aborted.  You can
 explicitly give a name with `-b` in such a case.
 
 --no-track::
 	Do not set up "upstream" configuration, even if the
-	branch.autoSetupMerge configuration variable is true.
+	`branch.autoSetupMerge` configuration variable is true.
 
 -l::
 	Create the new branch's reflog; see linkgit:git-branch[1] for
@@ -190,21 +190,21 @@ explicitly give a name with `-b` in such a case.
 --detach::
 	Rather than checking out a branch to work on it, check out a
 	commit for inspection and discardable experiments.
-	This is the default behavior of "git checkout <commit>" when
-	<commit> is not a branch name.  See the "DETACHED HEAD" section
+	This is the default behavior of `git checkout <commit>` when
+	`<commit>` is not a branch name.  See the "DETACHED HEAD" section
 	below for details.
 
 --orphan <new_branch>::
-	Create a new 'orphan' branch, named <new_branch>, started from
-	<start_point> and switch to it.  The first commit made on this
+	Create a new 'orphan' branch, named `<new_branch>`, started from
+	`<start_point>` and switch to it.  The first commit made on this
 	new branch will have no parents and it will be the root of a new
 	history totally disconnected from all the other branches and
 	commits.
 +
 The index and the working tree are adjusted as if you had previously run
-"git checkout <start_point>".  This allows you to start a new history
-that records a set of paths similar to <start_point> by easily running
-"git commit -a" to make the root commit.
+`git checkout <start_point>`.  This allows you to start a new history
+that records a set of paths similar to `<start_point>` by easily running
+`git commit -a` to make the root commit.
 +
 This can be useful when you want to publish the tree from a commit
 without exposing its full history. You might want to do this to publish
@@ -213,17 +213,17 @@ whose full history contains proprietary or otherwise encumbered bits of
 code.
 +
 If you want to start a disconnected history that records a set of paths
-that is totally different from the one of <start_point>, then you should
+that is totally different from the one of `<start_point>`, then you should
 clear the index and the working tree right after creating the orphan
-branch by running "git rm -rf ." from the top level of the working tree.
+branch by running `git rm -rf .` from the top level of the working tree.
 Afterwards you will be ready to prepare your new files, repopulating the
 working tree, by copying them from elsewhere, extracting a tarball, etc.
 
 --ignore-skip-worktree-bits::
 	In sparse checkout mode, `git checkout -- <paths>` would
-	update only entries matched by <paths> and sparse patterns
-	in $GIT_DIR/info/sparse-checkout. This option ignores
-	the sparse patterns and adds back any files in <paths>.
+	update only entries matched by `<paths>` and sparse patterns
+	in `$GIT_DIR/info/sparse-checkout`. This option ignores
+	the sparse patterns and adds back any files in `<paths>`.
 
 -m::
 --merge::
@@ -245,18 +245,18 @@ When checking out paths from the index, this option lets you recreate
 the conflicted merge in the specified paths.
 
 --conflict=<style>::
-	The same as --merge option above, but changes the way the
+	The same as `--merge` option above, but changes the way the
 	conflicting hunks are presented, overriding the
-	merge.conflictStyle configuration variable.  Possible values are
+	`merge.conflictStyle` configuration variable.  Possible values are
 	"merge" (default) and "diff3" (in addition to what is shown by
 	"merge" style, shows the original contents).
 
 -p::
 --patch::
 	Interactively select hunks in the difference between the
-	<tree-ish> (or the index, if unspecified) and the working
+	`<tree-ish>` (or the index, if unspecified) and the working
 	tree.  The chosen hunks are then applied in reverse to the
-	working tree (and if a <tree-ish> was specified, the index).
+	working tree (and if a `<tree-ish>` was specified, the index).
 +
 This means that you can use `git checkout -p` to selectively discard
 edits from your current working tree. See the ``Interactive Mode''
@@ -279,13 +279,13 @@ Note that this option uses the no overlay mode by default (see also
 
 --recurse-submodules::
 --no-recurse-submodules::
-	Using --recurse-submodules will update the content of all initialized
+	Using `--recurse-submodules` will update the content of all initialized
 	submodules according to the commit recorded in the superproject. If
 	local modifications in a submodule would be overwritten the checkout
-	will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
+	will fail unless `-f` is used. If nothing (or `--no-recurse-submodules`)
 	is used, the work trees of submodules will not be updated.
-	Just like linkgit:git-submodule[1], this will detach the
-	submodules HEAD.
+	Just like linkgit:git-submodule[1], this will detach `HEAD` of the
+	submodule.
 
 --no-guess::
 	Do not attempt to create a branch if a remote tracking branch
@@ -296,21 +296,21 @@ Note that this option uses the no overlay mode by default (see also
 	In the default overlay mode, `git checkout` never
 	removes files from the index or the working tree.  When
 	specifying `--no-overlay`, files that appear in the index and
-	working tree, but not in <tree-ish> are removed, to make them
-	match <tree-ish> exactly.
+	working tree, but not in `<tree-ish>` are removed, to make them
+	match `<tree-ish>` exactly.
 
 <branch>::
 	Branch to checkout; if it refers to a branch (i.e., a name that,
 	when prepended with "refs/heads/", is a valid ref), then that
 	branch is checked out. Otherwise, if it refers to a valid
-	commit, your HEAD becomes "detached" and you are no longer on
+	commit, your `HEAD` becomes "detached" and you are no longer on
 	any branch (see below for details).
 +
-You can use the `"@{-N}"` syntax to refer to the N-th last
+You can use the `@{-N}` syntax to refer to the N-th last
 branch/commit checked out using "git checkout" operation. You may
-also specify `-` which is synonymous to `"@{-1}"`.
+also specify `-` which is synonymous to `@{-1}`.
 +
-As a special case, you may use `"A...B"` as a shortcut for the
+As a special case, you may use `A...B` as a shortcut for the
 merge base of `A` and `B` if there is exactly one merge base. You can
 leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 
@@ -319,7 +319,7 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 
 <start_point>::
 	The name of a commit at which to start the new branch; see
-	linkgit:git-branch[1] for details. Defaults to HEAD.
+	linkgit:git-branch[1] for details. Defaults to `HEAD`.
 
 <tree-ish>::
 	Tree to checkout from (when paths are given). If not specified,
@@ -329,9 +329,9 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 
 DETACHED HEAD
 -------------
-HEAD normally refers to a named branch (e.g. 'master'). Meanwhile, each
+`HEAD` normally refers to a named branch (e.g. `master`). Meanwhile, each
 branch refers to a specific commit. Let's look at a repo with three
-commits, one of them tagged, and with branch 'master' checked out:
+commits, one of them tagged, and with branch `master` checked out:
 
 ------------
            HEAD (refers to branch 'master')
@@ -344,10 +344,10 @@ a---b---c  branch 'master' (refers to commit 'c')
 ------------
 
 When a commit is created in this state, the branch is updated to refer to
-the new commit. Specifically, 'git commit' creates a new commit 'd', whose
-parent is commit 'c', and then updates branch 'master' to refer to new
-commit 'd'. HEAD still refers to branch 'master' and so indirectly now refers
-to commit 'd':
+the new commit. Specifically, 'git commit' creates a new commit `d`, whose
+parent is commit `c`, and then updates branch `master` to refer to new
+commit `d`. `HEAD` still refers to branch `master` and so indirectly now refers
+to commit `d`:
 
 ------------
 $ edit; git add; git commit
@@ -364,7 +364,7 @@ a---b---c---d  branch 'master' (refers to commit 'd')
 It is sometimes useful to be able to checkout a commit that is not at
 the tip of any named branch, or even to create a new commit that is not
 referenced by a named branch. Let's look at what happens when we
-checkout commit 'b' (here we show two ways this may be done):
+checkout commit `b` (here we show two ways this may be done):
 
 ------------
 $ git checkout v2.0  # or
@@ -379,9 +379,9 @@ a---b---c---d  branch 'master' (refers to commit 'd')
   tag 'v2.0' (refers to commit 'b')
 ------------
 
-Notice that regardless of which checkout command we use, HEAD now refers
-directly to commit 'b'. This is known as being in detached HEAD state.
-It means simply that HEAD refers to a specific commit, as opposed to
+Notice that regardless of which checkout command we use, `HEAD` now refers
+directly to commit `b`. This is known as being in detached `HEAD` state.
+It means simply that `HEAD` refers to a specific commit, as opposed to
 referring to a named branch. Let's see what happens when we create a commit:
 
 ------------
@@ -398,7 +398,7 @@ a---b---c---d  branch 'master' (refers to commit 'd')
   tag 'v2.0' (refers to commit 'b')
 ------------
 
-There is now a new commit 'e', but it is referenced only by HEAD. We can
+There is now a new commit `e`, but it is referenced only by `HEAD`. We can
 of course add yet another commit in this state:
 
 ------------
@@ -416,7 +416,7 @@ a---b---c---d  branch 'master' (refers to commit 'd')
 ------------
 
 In fact, we can perform all the normal Git operations. But, let's look
-at what happens when we then checkout master:
+at what happens when we then checkout `master`:
 
 ------------
 $ git checkout master
@@ -431,9 +431,9 @@ a---b---c---d  branch 'master' (refers to commit 'd')
 ------------
 
 It is important to realize that at this point nothing refers to commit
-'f'. Eventually commit 'f' (and by extension commit 'e') will be deleted
+`f`. Eventually commit `f` (and by extension commit `e`) will be deleted
 by the routine Git garbage collection process, unless we create a reference
-before that happens. If we have not yet moved away from commit 'f',
+before that happens. If we have not yet moved away from commit `f`,
 any of these will create a reference to it:
 
 ------------
@@ -442,19 +442,19 @@ $ git branch foo        <2>
 $ git tag foo           <3>
 ------------
 
-<1> creates a new branch 'foo', which refers to commit 'f', and then
-    updates HEAD to refer to branch 'foo'. In other words, we'll no longer
-    be in detached HEAD state after this command.
+<1> creates a new branch `foo`, which refers to commit `f`, and then
+    updates `HEAD` to refer to branch `foo`. In other words, we'll no longer
+    be in detached `HEAD` state after this command.
 
-<2> similarly creates a new branch 'foo', which refers to commit 'f',
-    but leaves HEAD detached.
+<2> similarly creates a new branch `foo`, which refers to commit `f`,
+    but leaves `HEAD` detached.
 
-<3> creates a new tag 'foo', which refers to commit 'f',
-    leaving HEAD detached.
+<3> creates a new tag `foo`, which refers to commit `f`,
+    leaving `HEAD` detached.
 
-If we have moved away from commit 'f', then we must first recover its object
+If we have moved away from commit `f`, then we must first recover its object
 name (typically by using git reflog), and then we can create a reference to
-it. For example, to see the last two commits to which HEAD referred, we
+it. For example, to see the last two commits to which `HEAD` referred, we
 can use either of these commands:
 
 ------------
@@ -465,12 +465,12 @@ $ git log -g -2 HEAD
 ARGUMENT DISAMBIGUATION
 -----------------------
 
-When there is only one argument given and it is not `--` (e.g. "git
-checkout abc"), and when the argument is both a valid `<tree-ish>`
-(e.g. a branch "abc" exists) and a valid `<pathspec>` (e.g. a file
+When there is only one argument given and it is not `--` (e.g. `git
+checkout abc`), and when the argument is both a valid `<tree-ish>`
+(e.g. a branch `abc` exists) and a valid `<pathspec>` (e.g. a file
 or a directory whose name is "abc" exists), Git would usually ask
 you to disambiguate.  Because checking out a branch is so common an
-operation, however, "git checkout abc" takes "abc" as a `<tree-ish>`
+operation, however, `git checkout abc` takes "abc" as a `<tree-ish>`
 in such a situation.  Use `git checkout -- <pathspec>` if you want
 to checkout these paths out of the index.
 
@@ -478,7 +478,7 @@ EXAMPLES
 --------
 
 . The following sequence checks out the `master` branch, reverts
-  the `Makefile` to two revisions back, deletes hello.c by
+  the `Makefile` to two revisions back, deletes `hello.c` by
   mistake, and gets it back from the index.
 +
 ------------
@@ -490,7 +490,7 @@ $ git checkout hello.c            <3>
 +
 <1> switch branch
 <2> take a file out of another commit
-<3> restore hello.c from the index
+<3> restore `hello.c` from the index
 +
 If you want to check out _all_ C source files out of the index,
 you can say
@@ -519,7 +519,7 @@ $ git checkout -- hello.c
 $ git checkout mytopic
 ------------
 +
-However, your "wrong" branch and correct "mytopic" branch may
+However, your "wrong" branch and correct `mytopic` branch may
 differ in files that you have modified locally, in which case
 the above checkout would fail like this:
 +
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 05/26] t: rename t2014-switch.sh to t2014-checkout-switch.sh

The old name does not really say that this is about 'checkout -b'. See
49d833dc07 (Revert "checkout branch: prime cache-tree fully" -
2009-05-12) for more information
---
 t/{t2014-switch.sh => t2014-checkout-switch.sh} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
Show changes to t/t2014-switch.sh +0 −0
diff --git a/t/t2014-switch.sh b/t/t2014-checkout-switch.sh
similarity index 100%
rename from t/t2014-switch.sh
rename to t/t2014-checkout-switch.sh
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 06/26] checkout: advice how to get out of detached HEAD mode

Detached HEAD mode is considered dangerous and confusing for newcomers and we print a big block of warning how to move forward. But we should also suggest the user the way to get out of it if they get into detached HEAD by mistake.

While at there, I also suggest how to turn the advice off. This is
another thing I find annoying with advices and should be dealt with in a
more generic way. But that may require some refactoring in advice.c
first.
---
 advice.c                   | 17 +++++++++++++----
 t/t2020-checkout-detach.sh | 12 ++++++++++++
 2 files changed, 25 insertions(+), 4 deletions(-)
Show changes to 2 files +25 −4

advice.c, t/t2020-checkout-detach.sh

diff --git a/advice.c b/advice.c
index 567209aa79..b224825637 100644
--- a/advice.c
+++ b/advice.c
@@ -191,13 +191,22 @@ void NORETURN die_conclude_merge(void)
 void detach_advice(const char *new_name)
 {
 	const char *fmt =
-	_("Note: checking out '%s'.\n\n"
+	_("Note: checking out '%s'.\n"
+	"\n"
 	"You are in 'detached HEAD' state. You can look around, make experimental\n"
 	"changes and commit them, and you can discard any commits you make in this\n"
-	"state without impacting any branches by performing another checkout.\n\n"
+	"state without impacting any branches by performing another checkout.\n"
+	"\n"
 	"If you want to create a new branch to retain commits you create, you may\n"
-	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
-	"  git checkout -b <new-branch-name>\n\n");
+	"do so (now or later) by using -b with the checkout command again. Example:\n"
+	"\n"
+	"  git checkout -b <new-branch-name>\n"
+	"\n"
+	"Or undo this checkout with:\n"
+	"\n"
+	"  git checkout -\n"
+	"\n"
+	"Turn off this advice by setting config variable advice.detachedHead to false\n\n");
 
 	fprintf(stderr, fmt, new_name);
 }
diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
index 1fa670625c..743c7248a2 100755
--- a/t/t2020-checkout-detach.sh
+++ b/t/t2020-checkout-detach.sh
@@ -206,6 +206,12 @@ test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not as
 
 	  git checkout -b <new-branch-name>
 
+	Or undo this checkout with:
+
+	  git checkout -
+
+	Turn off this advice by setting config variable advice.detachedHead to false
+
 	HEAD is now at \$commit three
 	EOF
 
@@ -282,6 +288,12 @@ test_expect_success 'describe_detached_head does print SHA-1 ellipsis when asked
 
 	  git checkout -b <new-branch-name>
 
+	Or undo this checkout with:
+
+	  git checkout -
+
+	Turn off this advice by setting config variable advice.detachedHead to false
+
 	HEAD is now at \$commit... three
 	EOF
 
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 07/26] checkout: keep most #include sorted

The include list becomes very long and frankly a bit unorganized. With
the exception of builtin.h, cache.h or git-compat-util.h which have to
come first, keep the rest sorted.
---
 builtin/checkout.c | 34 +++++++++++++++++-----------------
 1 file changed, 17 insertions(+), 17 deletions(-)
Show changes to builtin/checkout.c +17 −17
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 0e6037b296..f7801db2fb 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1,30 +1,30 @@
 #define USE_THE_INDEX_COMPATIBILITY_MACROS
 #include "builtin.h"
-#include "config.h"
+#include "advice.h"
+#include "blob.h"
+#include "branch.h"
+#include "cache-tree.h"
 #include "checkout.h"
+#include "commit.h"
+#include "config.h"
+#include "diff.h"
+#include "dir.h"
+#include "ll-merge.h"
 #include "lockfile.h"
+#include "merge-recursive.h"
+#include "object-store.h"
 #include "parse-options.h"
 #include "refs.h"
-#include "object-store.h"
-#include "commit.h"
+#include "remote.h"
+#include "resolve-undo.h"
+#include "revision.h"
+#include "run-command.h"
+#include "submodule.h"
+#include "submodule-config.h"
 #include "tree.h"
 #include "tree-walk.h"
-#include "cache-tree.h"
 #include "unpack-trees.h"
-#include "dir.h"
-#include "run-command.h"
-#include "merge-recursive.h"
-#include "branch.h"
-#include "diff.h"
-#include "revision.h"
-#include "remote.h"
-#include "blob.h"
 #include "xdiff-interface.h"
-#include "ll-merge.h"
-#include "resolve-undo.h"
-#include "submodule-config.h"
-#include "submodule.h"
-#include "advice.h"
 
 static int checkout_optimize_new_branch;
 
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 08/26] checkout: factor out some code in parse_branchname_arg()

This is in preparation for the new command restore, which also
needs to parse opts->source_tree but does not need all the
disambiguation logic.
---
 builtin/checkout.c | 51 ++++++++++++++++++++++++++++------------------
 1 file changed, 31 insertions(+), 20 deletions(-)
Show changes to builtin/checkout.c +31 −20
diff --git a/builtin/checkout.c b/builtin/checkout.c
index f7801db2fb..1ce0446bc6 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1081,6 +1081,34 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
 	return git_xmerge_config(var, value, NULL);
 }
 
+static void setup_new_branch_info_and_source_tree(
+	struct branch_info *new_branch_info,
+	struct checkout_opts *opts,
+	struct object_id *rev,
+	const char *arg)
+{
+	struct tree **source_tree = &opts->source_tree;
+	struct object_id branch_rev;
+
+	new_branch_info->name = arg;
+	setup_branch_path(new_branch_info);
+
+	if (!check_refname_format(new_branch_info->path, 0) &&
+	    !read_ref(new_branch_info->path, &branch_rev))
+		oidcpy(rev, &branch_rev);
+	else
+		new_branch_info->path = NULL; /* not an existing branch */
+
+	new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
+	if (!new_branch_info->commit) {
+		/* not a commit */
+		*source_tree = parse_tree_indirect(rev);
+	} else {
+		parse_commit_or_die(new_branch_info->commit);
+		*source_tree = get_commit_tree(new_branch_info->commit);
+	}
+}
+
 static int parse_branchname_arg(int argc, const char **argv,
 				int dwim_new_local_branch_ok,
 				struct branch_info *new_branch_info,
@@ -1088,10 +1116,8 @@ static int parse_branchname_arg(int argc, const char **argv,
 				struct object_id *rev,
 				int *dwim_remotes_matched)
 {
-	struct tree **source_tree = &opts->source_tree;
 	const char **new_branch = &opts->new_branch;
 	int argcount = 0;
-	struct object_id branch_rev;
 	const char *arg;
 	int dash_dash_pos;
 	int has_dash_dash = 0;
@@ -1213,26 +1239,11 @@ static int parse_branchname_arg(int argc, const char **argv,
 	argv++;
 	argc--;
 
-	new_branch_info->name = arg;
-	setup_branch_path(new_branch_info);
-
-	if (!check_refname_format(new_branch_info->path, 0) &&
-	    !read_ref(new_branch_info->path, &branch_rev))
-		oidcpy(rev, &branch_rev);
-	else
-		new_branch_info->path = NULL; /* not an existing branch */
+	setup_new_branch_info_and_source_tree(new_branch_info, opts, rev, arg);
 
-	new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
-	if (!new_branch_info->commit) {
-		/* not a commit */
-		*source_tree = parse_tree_indirect(rev);
-	} else {
-		parse_commit_or_die(new_branch_info->commit);
-		*source_tree = get_commit_tree(new_branch_info->commit);
-	}
-
-	if (!*source_tree)                   /* case (1): want a tree */
+	if (!opts->source_tree)                   /* case (1): want a tree */
 		die(_("reference is not a tree: %s"), arg);
+
 	if (!has_dash_dash) {	/* case (3).(d) -> (1) */
 		/*
 		 * Do not complain the most common case
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 09/26] checkout: make "opts" in cmd_checkout() a pointer

"opts" will soon be moved out of cmd_checkout(). To keep changes in
that patch smaller, convert "opts" to a pointer and keep the real
thing behind "real_opts".
---
 builtin/checkout.c | 115 +++++++++++++++++++++++----------------------
 1 file changed, 58 insertions(+), 57 deletions(-)
Show changes to builtin/checkout.c +58 −57
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 1ce0446bc6..963f10b05b 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1341,82 +1341,83 @@ static int checkout_branch(struct checkout_opts *opts,
 
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
-	struct checkout_opts opts;
+	struct checkout_opts real_opts;
+	struct checkout_opts *opts = &real_opts;
 	struct branch_info new_branch_info;
 	char *conflict_style = NULL;
 	int dwim_new_local_branch, no_dwim_new_local_branch = 0;
 	int dwim_remotes_matched = 0;
 	struct option options[] = {
-		OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
-		OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
+		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
+		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"),
+		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"),
+		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),
-		OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
-		OPT_SET_INT_F('2', "ours", &opts.writeout_stage,
+		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
+		OPT_SET_INT_F('2', "ours", &opts->writeout_stage,
 			      N_("checkout our version for unmerged files"),
 			      2, PARSE_OPT_NONEG),
-		OPT_SET_INT_F('3', "theirs", &opts.writeout_stage,
+		OPT_SET_INT_F('3', "theirs", &opts->writeout_stage,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
-		OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)"),
+		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")),
-		OPT_BOOL_F(0, "overwrite-ignore", &opts.overwrite_ignore,
+		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
+		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
 		OPT_STRING(0, "conflict", &conflict_style, N_("style"),
 			   N_("conflict style (merge or diff3)")),
-		OPT_BOOL('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
-		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree,
+		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_BOOL(0, "no-guess", &no_dwim_new_local_branch,
 			 N_("do not second guess 'git checkout <no-such-branch>'")),
-		OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
+		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
 		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
 			    "checkout", "control recursive updating of submodules",
 			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
-		OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
-		OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")),
+		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
+		OPT_BOOL(0, "overlay", &opts->overlay_mode, N_("use overlay mode (default)")),
 		OPT_END(),
 	};
 
-	memset(&opts, 0, sizeof(opts));
+	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->overwrite_ignore = 1;
+	opts->prefix = prefix;
+	opts->show_progress = -1;
+	opts->overlay_mode = -1;
 
-	git_config(git_checkout_config, &opts);
+	git_config(git_checkout_config, opts);
 
-	opts.track = BRANCH_TRACK_UNSPECIFIED;
+	opts->track = BRANCH_TRACK_UNSPECIFIED;
 
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
 	dwim_new_local_branch = !no_dwim_new_local_branch;
-	if (opts.show_progress < 0) {
-		if (opts.quiet)
-			opts.show_progress = 0;
+	if (opts->show_progress < 0) {
+		if (opts->quiet)
+			opts->show_progress = 0;
 		else
-			opts.show_progress = isatty(2);
+			opts->show_progress = isatty(2);
 	}
 
 	if (conflict_style) {
-		opts.merge = 1; /* implied */
+		opts->merge = 1; /* implied */
 		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
 	}
 
-	if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)
+	if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
 		die(_("-b, -B and --orphan are mutually exclusive"));
 
-	if (opts.overlay_mode == 1 && opts.patch_mode)
+	if (opts->overlay_mode == 1 && opts->patch_mode)
 		die(_("-p and --overlay are mutually exclusive"));
 
 	/*
@@ -1424,14 +1425,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	 * and new_branch_force and new_orphan_branch will tell us which one of
 	 * -b/-B/--orphan is being used.
 	 */
-	if (opts.new_branch_force)
-		opts.new_branch = opts.new_branch_force;
+	if (opts->new_branch_force)
+		opts->new_branch = opts->new_branch_force;
 
-	if (opts.new_orphan_branch)
-		opts.new_branch = opts.new_orphan_branch;
+	if (opts->new_orphan_branch)
+		opts->new_branch = opts->new_orphan_branch;
 
 	/* --track without -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];
 		if (!argc || !strcmp(argv0, "--"))
 			die(_("--track needs a branch name"));
@@ -1440,7 +1441,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		argv0 = strchr(argv0, '/');
 		if (!argv0 || !argv0[1])
 			die(_("missing branch name; try -b"));
-		opts.new_branch = argv0 + 1;
+		opts->new_branch = argv0 + 1;
 	}
 
 	/*
@@ -1459,56 +1460,56 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	if (argc) {
 		struct object_id rev;
 		int dwim_ok =
-			!opts.patch_mode &&
+			!opts->patch_mode &&
 			dwim_new_local_branch &&
-			opts.track == BRANCH_TRACK_UNSPECIFIED &&
-			!opts.new_branch;
+			opts->track == BRANCH_TRACK_UNSPECIFIED &&
+			!opts->new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
-					     &new_branch_info, &opts, &rev,
+					     &new_branch_info, opts, &rev,
 					     &dwim_remotes_matched);
 		argv += n;
 		argc -= n;
 	}
 
 	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)
+		if (opts->new_branch && argc == 1)
 			die(_("'%s' is not a commit and a branch '%s' cannot be created from it"),
-				argv[0], opts.new_branch);
+				argv[0], opts->new_branch);
 
-		if (opts.force_detach)
+		if (opts->force_detach)
 			die(_("git checkout: --detach does not take a path argument '%s'"),
 			    argv[0]);
 
-		if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
+		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.new_branch) {
+	if (opts->new_branch) {
 		struct strbuf buf = STRBUF_INIT;
 
-		if (opts.new_branch_force)
-			opts.branch_exists = validate_branchname(opts.new_branch, &buf);
+		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);
+			opts->branch_exists =
+				validate_new_branchname(opts->new_branch, &buf, 0);
 		strbuf_release(&buf);
 	}
 
 	UNLEAK(opts);
-	if (opts.patch_mode || opts.pathspec.nr) {
-		int ret = checkout_paths(&opts, new_branch_info.name);
+	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"
@@ -1527,6 +1528,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			       dwim_remotes_matched);
 		return ret;
 	} else {
-		return checkout_branch(&opts, &new_branch_info);
+		return checkout_branch(opts, &new_branch_info);
 	}
 }
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 10/26] checkout: move 'confict_style' and 'dwim_..' to checkout_opts

These local variables are referenced by struct option[]. This struct
will soon be broken down, moved away and we can't rely on local
variables anymore. Move these two to struct checkout_opts in
preparation for that.
---
 builtin/checkout.c | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)
Show changes to builtin/checkout.c +10 −7
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 963f10b05b..b6fc1fc69f 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -47,6 +47,8 @@ struct checkout_opts {
 	int show_progress;
 	int count_checkout_paths;
 	int overlay_mode;
+	int no_dwim_new_local_branch;
+
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
 	 * should be updated accordingly.
@@ -58,6 +60,7 @@ struct checkout_opts {
 	int new_branch_log;
 	enum branch_track track;
 	struct diff_options diff_options;
+	char *conflict_style;
 
 	int branch_exists;
 	const char *prefix;
@@ -1344,8 +1347,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	struct checkout_opts real_opts;
 	struct checkout_opts *opts = &real_opts;
 	struct branch_info new_branch_info;
-	char *conflict_style = NULL;
-	int dwim_new_local_branch, no_dwim_new_local_branch = 0;
+	int dwim_new_local_branch;
 	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
@@ -1370,12 +1372,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_STRING(0, "conflict", &conflict_style, N_("style"),
+		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
 			   N_("conflict style (merge or diff3)")),
 		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_BOOL(0, "no-guess", &no_dwim_new_local_branch,
+		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
 			 N_("do not second guess 'git checkout <no-such-branch>'")),
 		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
@@ -1393,6 +1395,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts->prefix = prefix;
 	opts->show_progress = -1;
 	opts->overlay_mode = -1;
+	opts->no_dwim_new_local_branch = 0;
 
 	git_config(git_checkout_config, opts);
 
@@ -1401,7 +1404,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
-	dwim_new_local_branch = !no_dwim_new_local_branch;
+	dwim_new_local_branch = !opts->no_dwim_new_local_branch;
 	if (opts->show_progress < 0) {
 		if (opts->quiet)
 			opts->show_progress = 0;
@@ -1409,9 +1412,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			opts->show_progress = isatty(2);
 	}
 
-	if (conflict_style) {
+	if (opts->conflict_style) {
 		opts->merge = 1; /* implied */
-		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
+		git_xmerge_config("merge.conflictstyle", opts->conflict_style, NULL);
 	}
 
 	if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 12/26] checkout: split part of it to new command 'switch'

"git checkout" doing too many things is a source of confusion for many users (and it even bites old timers sometimes). To remedy that, the command will be split into two new ones: switch and restore. The good old "git checkout" command is still here and will be until all (or most of users) are sick of it.

See the new man page for the final design of switch. The actual
implementation though is still pretty much the same as "git checkout"
and not completely aligned with the man page. Following patches will
adjust their behavior to match the man page.
---
 .gitignore                         |   1 +
 Documentation/config/advice.txt    |  13 +-
 Documentation/config/branch.txt    |   4 +-
 Documentation/config/checkout.txt  |   9 +-
 Documentation/config/diff.txt      |   3 +-
 Documentation/git-checkout.txt     |   4 +
 Documentation/git-switch.txt (new) | 276 +++++++++++++++++++++++++++++
 Documentation/gitattributes.txt    |   3 +-
 Documentation/githooks.txt         |   8 +-
 Makefile                           |   1 +
 builtin.h                          |   1 +
 builtin/checkout.c                 |  60 +++++--
 command-list.txt                   |   1 +
 git.c                              |   1 +
 14 files changed, 358 insertions(+), 27 deletions(-)
Show changes to 14 files +358 −27

.gitignore, Documentation/config/advice.txt, Documentation/config/branch.txt, Documentation/config/checkout.txt, Documentation/config/diff.txt, Documentation/git-checkout.txt, Documentation/git-switch.txt, Documentation/gitattributes.txt, Documentation/githooks.txt, Makefile, builtin.h, builtin/checkout.c, command-list.txt, git.c

diff --git a/.gitignore b/.gitignore
index 7374587f9d..c687b92b1c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -167,6 +167,7 @@
 /git-submodule
 /git-submodule--helper
 /git-svn
+/git-switch
 /git-symbolic-ref
 /git-tag
 /git-unpack-file
diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
index 88620429ea..239d479506 100644
--- a/Documentation/config/advice.txt
+++ b/Documentation/config/advice.txt
@@ -42,7 +42,8 @@ advice.*::
 		state in the output of linkgit:git-status[1], in
 		the template shown when writing commit messages in
 		linkgit:git-commit[1], and in the help message shown
-		by linkgit:git-checkout[1] when switching branch.
+		by linkgit:git-switch[1] or
+		linkgit:git-checkout[1] when switching branch.
 	statusUoption::
 		Advise to consider using the `-u` option to linkgit:git-status[1]
 		when the command takes more than 2 seconds to enumerate untracked
@@ -62,12 +63,14 @@ advice.*::
 		your information is guessed from the system username and
 		domain name.
 	detachedHead::
-		Advice shown when you used linkgit:git-checkout[1] to
-		move to the detach HEAD state, to instruct how to create
-		a local branch after the fact.
+		Advice shown when you used
+		linkgit:git-switch[1] or linkgit:git-checkout[1]
+		to move to the detach HEAD state, to instruct how to
+		create a local branch after the fact.
 	checkoutAmbiguousRemoteBranchName::
 		Advice shown when the argument to
-		linkgit:git-checkout[1] ambiguously resolves to a
+		linkgit:git-checkout[1] and linkgit:git-switch[1]
+		ambiguously resolves to a
 		remote tracking branch on more than one remote in
 		situations where an unambiguous argument would have
 		otherwise caused a remote-tracking branch to be
diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index 019d60ede2..8050466159 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -1,5 +1,5 @@
 branch.autoSetupMerge::
-	Tells 'git branch' and 'git checkout' to set up new branches
+	Tells 'git branch', 'git switch' and 'git checkout' to set up new branches
 	so that linkgit:git-pull[1] will appropriately merge from the
 	starting point branch. Note that even if this option is not set,
 	this behavior can be chosen per-branch using the `--track`
@@ -11,7 +11,7 @@ branch.autoSetupMerge::
 	branch. This option defaults to true.
 
 branch.autoSetupRebase::
-	When a new branch is created with 'git branch' or 'git checkout'
+	When a new branch is created with 'git branch', 'git switch' or 'git checkout'
 	that tracks another branch, this variable tells Git to set
 	up pull to rebase instead of merge (see "branch.<name>.rebase").
 	When `never`, rebase is never automatically set to true.
diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt
index c4118fa196..d6872ffa83 100644
--- a/Documentation/config/checkout.txt
+++ b/Documentation/config/checkout.txt
@@ -1,5 +1,6 @@
 checkout.defaultRemote::
-	When you run 'git checkout <something>' and only have one
+	When you run 'git checkout <something>'
+	or 'git switch <something>' and only have one
 	remote, it may implicitly fall back on checking out and
 	tracking e.g. 'origin/<something>'. This stops working as soon
 	as you have more than one remote with a '<something>'
@@ -8,8 +9,10 @@ checkout.defaultRemote::
 	disambiguation. The typical use-case is to set this to
 	`origin`.
 +
-Currently this is used by linkgit:git-checkout[1] when 'git checkout
-<something>' will checkout the '<something>' branch on another remote,
+Currently this is used by linkgit:git-switch[1] and
+linkgit:git-checkout[1] when 'git checkout <something>'
+or 'git switch <something>'
+will checkout the '<something>' branch on another remote,
 and by linkgit:git-worktree[1] when 'git worktree add' refers to a
 remote branch. This setting might be used for other checkout-like
 commands or functionality in the future.
diff --git a/Documentation/config/diff.txt b/Documentation/config/diff.txt
index e48bb987d7..b3b304ee12 100644
--- a/Documentation/config/diff.txt
+++ b/Documentation/config/diff.txt
@@ -78,7 +78,8 @@ diff.external::
 diff.ignoreSubmodules::
 	Sets the default value of --ignore-submodules. Note that this
 	affects only 'git diff' Porcelain, and not lower level 'diff'
-	commands such as 'git diff-files'. 'git checkout' also honors
+	commands such as 'git diff-files'. 'git checkout'
+	and 'git switch' also honor
 	this setting when reporting uncommitted changes. Setting it to
 	'all' disables the submodule summary normally shown by 'git commit'
 	and 'git status' when `status.submoduleSummary` is set unless it is
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index bf90966c95..c7192bdefe 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -560,6 +560,10 @@ $ edit frotz
 $ git add frotz
 ------------
 
+SEE ALSO
+--------
+linkgit:git-switch[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
new file mode 100644
index 0000000000..da2cbaf890
--- /dev/null
+++ b/Documentation/git-switch.txt
@@ -0,0 +1,276 @@
+git-switch(1)
+=============
+
+NAME
+----
+git-switch - Switch branches
+
+SYNOPSIS
+--------
+[verse]
+'git switch' [<options>] [--no-guess] <branch>
+'git switch' [<options>] --detach [<start-point>]
+'git switch' [<options>] (-c|-C) <new-branch> [<start-point>]
+'git switch' [<options>] --orphan <new-branch>
+
+DESCRIPTION
+-----------
+Switch to a specified branch. The working tree and the index are
+updated to match the branch. All new commits will be added to the tip
+of this branch.
+
+Optionally a new branch could be created with either `-c`, `-C`,
+automatically from a remote branch of same name (see `--guess`), or
+detach the working tree from any branch with `--detach`, along with
+switching.
+
+Switching branches does not require a clean index and working tree
+(i.e. no differences compared to `HEAD`). The operation is aborted
+however if the operation leads to loss of local changes, unless told
+otherwise with `--discard-changes` or `--merge`.
+
+OPTIONS
+-------
+<branch>::
+	Branch to switch to.
+
+<new-branch>::
+	Name for the new branch.
+
+<start-point>::
+	The starting point for the new branch. Specifying a
+	`<start-point>` allows you to create a branch based on some
+	other point in history than where HEAD currently points. (Or,
+	in the case of `--detach`, allows you to inspect and detach
+	from some other point.)
++
+You can use the `@{-N}` syntax to refer to the N-th last
+branch/commit switched to using "git switch" or "git checkout"
+operation. You may also specify `-` which is synonymous to `@{-1}`.
+This is often used to switch quickly between two branches, or to undo
+a branch switch by mistake.
++
+As a special case, you may use `A...B` as a shortcut for the merge
+base of `A` and `B` if there is exactly one merge base. You can leave
+out at most one of `A` and `B`, in which case it defaults to `HEAD`.
+
+-c <new-branch>::
+--create <new-branch>::
+	Create a new branch named `<new-branch>` starting at
+	`<start-point>` before switching to the branch. This is a
+	convenient shortcut for:
++
+------------
+$ git branch <new-branch>
+$ git switch <new-branch>
+------------
+
+-C <new-branch>::
+--force-create <new-branch>::
+	Similar to `--create` except that if `<new-branch>` already
+	exists, it will be reset to `<start-point>`. This is a
+	convenient shortcut for:
++
+------------
+$ git branch -f <new-branch>
+$ git switch <new-branch>
+------------
+
+-d::
+--detach::
+	Switch to a commit for inspection and discardable
+	experiments. See the "DETACHED HEAD" section in
+	linkgit:git-checkout[1] for details.
+
+--guess::
+--no-guess::
+	If `<branch>` is not found but there does exist a tracking
+	branch in exactly one remote (call it `<remote>`) with a
+	matching name, treat as equivalent to
++
+------------
+$ git switch -c <branch> --track <remote>/<branch>
+------------
++
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to e.g. `checkout.defaultRemote=origin`
+to always checkout remote branches from there if `<branch>` is
+ambiguous but exists on the 'origin' remote. See also
+`checkout.defaultRemote` in linkgit:git-config[1].
++
+`--guess` is the default behavior. Use `--no-guess` to disable it.
+
+-f::
+--force::
+	An alias for `--discard-changes` and `--ignore-in-progress`.
+
+--discard-changes::
+	Proceed even if the index or the working tree differs from
+	`HEAD`. Both the index and working tree are restored to match
+	the switching target. This is used to throw away local
+	changes.
+
+--ignore-in-progress::
+	`git switch` by default refuses when some operation is in
+	progress (e.g. "git rebase", "git am" ...). This option
+	overrides this safety check and allows switching.
+
+-m::
+--merge::
+	If you have local modifications to one or more files that are
+	different between the current branch and the branch to which
+	you are switching, the command refuses to switch branches in
+	order to preserve your modifications in context.  However,
+	with this option, a three-way merge between the current
+	branch, your working tree contents, and the new branch is
+	done, and you will be on the new branch.
++
+When a merge conflict happens, the index entries for conflicting
+paths are left unmerged, and you need to resolve the conflicts
+and mark the resolved paths with `git add` (or `git rm` if the merge
+should result in deletion of the path).
+
+--conflict=<style>::
+	The same as `--merge` option above, but changes the way the
+	conflicting hunks are presented, overriding the
+	`merge.conflictStyle` configuration variable.  Possible values are
+	"merge" (default) and "diff3" (in addition to what is shown by
+	"merge" style, shows the original contents).
+
+-q::
+--quiet::
+	Quiet, suppress feedback messages.
+
+--progress::
+--no-progress::
+	Progress status is reported on the standard error stream
+	by default when it is attached to a terminal, unless `--quiet`
+	is specified. This flag enables progress reporting even if not
+	attached to a terminal, regardless of `--quiet`.
+
+-t::
+--track::
+	When creating a new branch, set up "upstream" configuration.
+	`-c` is implied. See `--track` in linkgit:git-branch[1] for
+	details.
++
+If no `-c` option is given, the name of the new branch will be derived
+from the remote-tracking branch, by looking at the local part of the
+refspec configured for the corresponding remote, and then stripping
+the initial part up to the "*".  This would tell us to use `hack` as
+the local branch when branching off of `origin/hack` (or
+`remotes/origin/hack`, or even `refs/remotes/origin/hack`).  If the
+given name has no slash, or the above guessing results in an empty
+name, the guessing is aborted.  You can explicitly give a name with
+`-c` in such a case.
+
+--no-track::
+	Do not set up "upstream" configuration, even if the
+	`branch.autoSetupMerge` configuration variable is true.
+
+--orphan <new-branch>::
+	Create a new 'orphan' branch, named `<new-branch>`. All
+	tracked files are removed.
+
+--ignore-other-worktrees::
+	`git switch` refuses when the wanted ref is already
+	checked out by another worktree. This option makes it check
+	the ref out anyway. In other words, the ref can be held by
+	more than one worktree.
+
+--recurse-submodules::
+--no-recurse-submodules::
+	Using `--recurse-submodules` will update the content of all initialized
+	submodules according to the commit recorded in the superproject. If
+	local modifications in a submodule would be overwritten the checkout
+	will fail unless `-f` is used. If nothing (or `--no-recurse-submodules`)
+	is used, the work trees of submodules will not be updated.
+	Just like linkgit:git-submodule[1], this will detach `HEAD` of the
+	submodule.
+
+EXAMPLES
+--------
+
+The following command switches to the "master" branch:
+
+------------
+$ git switch master
+------------
+
+After working in the wrong branch, switching to the correct branch
+would be done using:
+
+------------
+$ git switch mytopic
+------------
+
+However, your "wrong" branch and correct "mytopic" branch may differ
+in files that you have modified locally, in which case the above
+switch would fail like this:
+
+------------
+$ git switch mytopic
+error: You have local changes to 'frotz'; not switching branches.
+------------
+
+You can give the `-m` flag to the command, which would try a three-way
+merge:
+
+------------
+$ git switch -m mytopic
+Auto-merging frotz
+------------
+
+After this three-way merge, the local modifications are _not_
+registered in your index file, so `git diff` would show you what
+changes you made since the tip of the new branch.
+
+To switch back to the previous branch before we switched to mytopic
+(i.e. "master" branch):
+
+------------
+$ git switch -
+------------
+
+You can grow a new branch from any commit. For example, switch to
+"HEAD~3" and create branch "fixup":
+
+------------
+$ git switch -c fixup HEAD~3
+Switched to a new branch 'fixup'
+------------
+
+If you want to start a new branch from a remote branch of the same
+name:
+
+------------
+$ git switch -g new-topic
+Branch 'new-topic' set up to track remote branch 'new-topic' from 'origin'
+Switched to a new branch 'new-topic'
+------------
+
+To check out commit `HEAD~3` for temporary inspection or experiment
+without creating a new branch:
+
+------------
+$ git switch --detach HEAD~3
+HEAD is now at 9fc9555312 Merge branch 'cc/shared-index-permbits'
+------------
+
+If it turns out whatever you have done is worth keeping, you can
+always create a new name for it (without switching away):
+
+------------
+$ git switch -c good-surprises
+------------
+
+SEE ALSO
+--------
+linkgit:git-checkout[1],
+linkgit:git-branch[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 9b41f81c06..cd0f9fa507 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -112,7 +112,8 @@ Checking-out and checking-in
 
 These attributes affect how the contents stored in the
 repository are copied to the working tree files when commands
-such as 'git checkout' and 'git merge' run.  They also affect how
+such as 'git switch', 'git checkout'  and 'git merge' run.
+They also affect how
 Git stores the contents you prepare in the working tree in the
 repository upon 'git add' and 'git commit'.
 
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 5bf653c111..8ff72f0613 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -165,12 +165,13 @@ rebased, and is not set when rebasing the current branch.
 post-checkout
 ~~~~~~~~~~~~~
 
-This hook is invoked when a linkgit:git-checkout[1] is run after having updated the
+This hook is invoked when a linkgit:git-checkout[1] or
+linkgit:git-switch[1] is run after having updated the
 worktree.  The hook is given three parameters: the ref of the previous HEAD,
 the ref of the new HEAD (which may or may not have changed), and a flag
 indicating whether the checkout was a branch checkout (changing branches,
 flag=1) or a file checkout (retrieving a file from the index, flag=0).
-This hook cannot affect the outcome of `git checkout`.
+This hook cannot affect the outcome of `git switch` or `git checkout`.
 
 It is also run after linkgit:git-clone[1], unless the `--no-checkout` (`-n`) option is
 used. The first parameter given to the hook is the null-ref, the second the
@@ -406,7 +407,8 @@ exit with a zero status.
 For example, the hook can simply run `git read-tree -u -m HEAD "$1"`
 in order to emulate `git fetch` that is run in the reverse direction
 with `git push`, as the two-tree form of `git read-tree -u -m` is
-essentially the same as `git checkout` that switches branches while
+essentially the same as `git switch` or `git checkout`
+that switches branches while
 keeping the local changes in the working tree that do not interfere
 with the difference between the branches.
 
diff --git a/Makefile b/Makefile
index 148668368b..8e91db73ad 100644
--- a/Makefile
+++ b/Makefile
@@ -802,6 +802,7 @@ BUILT_INS += git-merge-subtree$X
 BUILT_INS += git-show$X
 BUILT_INS += git-stage$X
 BUILT_INS += git-status$X
+BUILT_INS += git-switch$X
 BUILT_INS += git-whatchanged$X
 
 # what 'all' will build and 'install' will install in gitexecdir,
diff --git a/builtin.h b/builtin.h
index 6538932e99..c64e44450e 100644
--- a/builtin.h
+++ b/builtin.h
@@ -227,6 +227,7 @@ extern int cmd_show_index(int argc, const char **argv, const char *prefix);
 extern int cmd_status(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
+extern int cmd_switch(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index cf1d70511e..dd3c200cb5 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -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;
@@ -1411,33 +1416,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;
@@ -1570,3 +1567,42 @@ 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 = NULL;
+	int ret;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.no_dwim_new_local_branch = 0;
+
+	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);
+
+	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;
+	int ret;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.no_dwim_new_local_branch = 0;
+
+	options = parse_options_dup(options);
+	options = add_common_options(&opts, options);
+	options = add_switch_branch_options(&opts, options);
+
+	ret = checkout_main(argc, argv, prefix, &opts,
+			    options, switch_branch_usage);
+	FREE_AND_NULL(options);
+	return ret;
+}
diff --git a/command-list.txt b/command-list.txt
index 3a9af104b5..13317f47d4 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -171,6 +171,7 @@ git-status                              mainporcelain           info
 git-stripspace                          purehelpers
 git-submodule                           mainporcelain
 git-svn                                 foreignscminterface
+git-switch                              mainporcelain           history
 git-symbolic-ref                        plumbingmanipulators
 git-tag                                 mainporcelain           history
 git-unpack-file                         plumbinginterrogators
diff --git a/git.c b/git.c
index 2014aab6b8..39582cf511 100644
--- a/git.c
+++ b/git.c
@@ -573,6 +573,7 @@ static struct cmd_struct commands[] = {
 	{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
 	{ "stripspace", cmd_stripspace },
 	{ "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
+	{ "switch", cmd_switch, RUN_SETUP | NEED_WORK_TREE },
 	{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
 	{ "tag", cmd_tag, RUN_SETUP | DELAY_PAGER_CONFIG },
 	{ "unpack-file", cmd_unpack_file, RUN_SETUP | NO_PARSEOPT },
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 13/26] switch: better names for -b and -B

The shortcut of these options do not make much sense when used with
switch. And their descriptions are also tied to checkout. Move -b/-B
to cmd_checkout() and new -c/-C with the same functionality in
cmd_switch_branch()
---
 builtin/checkout.c | 32 +++++++++++++++++++++-----------
 1 file changed, 21 insertions(+), 11 deletions(-)
Show changes to builtin/checkout.c +21 −11
diff --git a/builtin/checkout.c b/builtin/checkout.c
index dd3c200cb5..400a0d5620 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1368,14 +1368,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"),
@@ -1571,15 +1567,22 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
 	struct checkout_opts opts;
-	struct option *options = NULL;
+	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_END()
+	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
 
-	options = parse_options_dup(options);
+	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
-	options = add_switch_branch_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,
@@ -1592,14 +1595,21 @@ 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_END()
+	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
 
-	options = parse_options_dup(options);
+	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-	options = add_switch_branch_options(&opts, options);
+	options = add_common_switch_branch_options(&opts, options);
 
 	ret = checkout_main(argc, argv, prefix, &opts,
 			    options, switch_branch_usage);
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 14/26] switch: add --discard-changes

--discard-changes is a better name than --force for this option since
it's what really happens. --force is turned to an alias for
--discard-changes. But it's meant to be an alias for potentially more
force options in the future.
---
 builtin/checkout.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)
Show changes to builtin/checkout.c +10 −2
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 400a0d5620..bd94b04303 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -53,6 +53,7 @@ struct checkout_opts {
 	int count_checkout_paths;
 	int overlay_mode;
 	int no_dwim_new_local_branch;
+	int discard_changes;
 
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
@@ -680,7 +681,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)
@@ -802,7 +803,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;
@@ -1309,6 +1310,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");
@@ -1445,6 +1449,8 @@ static int checkout_main(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"));
@@ -1600,6 +1606,8 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 			   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;
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 15/26] switch: remove -l

This option is ancient. Nowadays reflog is enabled by default and
automatically created for new branches. Keep it in git-checkout only.
---
 builtin/checkout.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/checkout.c +1 −1
diff --git a/builtin/checkout.c b/builtin/checkout.c
index bd94b04303..85fc60f5f2 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1376,7 +1376,6 @@ static struct option *add_common_switch_branch_options(
 	struct checkout_opts *opts, struct option *prevopts)
 {
 	struct option options[] = {
-		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),
@@ -1579,6 +1578,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			   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;
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 16/26] switch: stop accepting pathspec

This command is about switching branch (or creating a new one) and
should not accept pathspec. This helps simplify ambiguation
handling. The other two ("git checkout" and "git restore") of
course do accept pathspec as before.
---
 builtin/checkout.c | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)
Show changes to builtin/checkout.c +15 −5
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 85fc60f5f2..2c4cab6406 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -54,6 +54,7 @@ struct checkout_opts {
 	int overlay_mode;
 	int no_dwim_new_local_branch;
 	int discard_changes;
+	int accept_pathspec;
 
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
@@ -1176,10 +1177,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;
 		}
@@ -1213,11 +1220,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) {
@@ -1262,7 +1270,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--;
@@ -1585,6 +1593,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
+	opts.accept_pathspec = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1614,6 +1623,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
+	opts.accept_pathspec = 0;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 11/26] checkout: split options[] array in three pieces

This is a preparation step for introducing new commands that do parts
of what checkout does. There will be two new commands, one is about
switching branches, detaching HEAD... one about checking out
paths. These share the a subset of command line options. The rest of
command line options are separate.
---
 builtin/checkout.c | 82 +++++++++++++++++++++++++++++++++-------------
 parse-options-cb.c | 17 ++++++++++
 parse-options.h    |  1 +
 3 files changed, 77 insertions(+), 23 deletions(-)
Show changes to 3 files +77 −23

builtin/checkout.c, parse-options-cb.c, parse-options.h

diff --git a/builtin/checkout.c b/builtin/checkout.c
index b6fc1fc69f..cf1d70511e 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1342,15 +1342,31 @@ static int checkout_branch(struct checkout_opts *opts,
 	return switch_branches(opts, new_branch_info);
 }
 
-int cmd_checkout(int argc, const char **argv, const char *prefix)
+static struct option *add_common_options(struct checkout_opts *opts,
+					 struct option *prevopts)
 {
-	struct checkout_opts real_opts;
-	struct checkout_opts *opts = &real_opts;
-	struct branch_info new_branch_info;
-	int dwim_new_local_branch;
-	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
+		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
+			    "checkout", "control recursive updating of submodules",
+			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
+		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
+		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
+			   PARSE_OPT_NOCOMPLETE),
+		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
+		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
+			   N_("conflict style (merge or diff3)")),
+		OPT_END()
+	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+static struct option *add_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"),
@@ -1360,34 +1376,49 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
 			BRANCH_TRACK_EXPLICIT),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
+		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
+			   N_("update ignored files (default)"),
+			   PARSE_OPT_NOCOMPLETE),
+		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
+			 N_("second guess 'git checkout <no-such-branch>'")),
+		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
+			 N_("do not check if another worktree is holding the given ref")),
+		OPT_END()
+	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+static struct option *add_checkout_path_options(struct checkout_opts *opts,
+						struct option *prevopts)
+{
+	struct option options[] = {
 		OPT_SET_INT_F('2', "ours", &opts->writeout_stage,
 			      N_("checkout our version for unmerged files"),
 			      2, PARSE_OPT_NONEG),
 		OPT_SET_INT_F('3', "theirs", &opts->writeout_stage,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
-		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
-			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
-		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
-			   N_("update ignored files (default)"),
-			   PARSE_OPT_NOCOMPLETE),
-		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
-			   N_("conflict style (merge or diff3)")),
 		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_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
-			 N_("do not second guess 'git checkout <no-such-branch>'")),
-		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
-			 N_("do not check if another worktree is holding the given ref")),
-		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
-			    "checkout", "control recursive updating of submodules",
-			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
-		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
 		OPT_BOOL(0, "overlay", &opts->overlay_mode, N_("use overlay mode (default)")),
-		OPT_END(),
+		OPT_END()
 	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+int cmd_checkout(int argc, const char **argv, const char *prefix)
+{
+	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));
@@ -1401,6 +1432,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	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,
 			     PARSE_OPT_KEEP_DASHDASH);
 
diff --git a/parse-options-cb.c b/parse-options-cb.c
index 2733393546..caaeed896f 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -122,6 +122,23 @@ int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
 	return 0;
 }
 
+struct option *parse_options_dup(const struct option *o)
+{
+	struct option *opts;
+	int nr = 0;
+
+	while (o && o->type != OPTION_END) {
+		nr++;
+		o++;
+	}
+
+	ALLOC_ARRAY(opts, nr + 1);
+	memcpy(opts, o - nr, sizeof(*o) * nr);
+	memset(opts + nr, 0, sizeof(*opts));
+	opts[nr].type = OPTION_END;
+	return opts;
+}
+
 struct option *parse_options_concat(struct option *a, struct option *b)
 {
 	struct option *ret;
diff --git a/parse-options.h b/parse-options.h
index 7d83e2971d..9a90c332a5 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -257,6 +257,7 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
 
 int parse_options_end(struct parse_opt_ctx_t *ctx);
 
+struct option *parse_options_dup(const struct option *a);
 struct option *parse_options_concat(struct option *a, struct option *b);
 
 /*----- some often used options -----*/
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 17/26] switch: reject "do nothing" case

"git checkout" can be executed without any arguments. What it does is not exactly great: it switches from HEAD to HEAD and shows worktree modification as a side effect.

Make switch reject this case. Just use "git status" if you want that side effect. For switch, you have to either

- really switch a branch
- (explicitly) detach from the current branch
- create a new branch
---
 builtin/checkout.c | 9 +++++++++
 1 file changed, 9 insertions(+)
Show changes to builtin/checkout.c +9 −0
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2c4cab6406..73b7373414 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -55,6 +55,7 @@ struct checkout_opts {
 	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
@@ -1338,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;
@@ -1593,6 +1600,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	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);
@@ -1624,6 +1632,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	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);
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 18/26] switch: only allow explicit detached HEAD

"git checkout <commit>" will checkout the commit in question and detach HEAD from the current branch. It is naturally a right thing to do once you get git references. But detached HEAD is a scary concept to new users because we show a lot of warnings and stuff, and it could be hard to get out of (until you know better).

To keep switch a bit more friendly to new users, we only allow
entering detached HEAD mode when --detach is given. "git
switch" must take a branch (unless you create a new branch,
then of course switch can take any commit-ish)
---
 builtin/checkout.c | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)
Show changes to builtin/checkout.c +34 −0
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 73b7373414..d72e28aabb 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -45,6 +45,7 @@ struct checkout_opts {
 	int merge;
 	int force;
 	int force_detach;
+	int implicit_detach;
 	int writeout_stage;
 	int overwrite_ignore;
 	int ignore_skipworktree;
@@ -1298,6 +1299,29 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)
 	return status;
 }
 
+static void die_expecting_a_branch(const struct branch_info *branch_info)
+{
+	struct object_id oid;
+	char *to_free;
+
+	if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free) == 1) {
+		const char *ref = to_free;
+
+		if (skip_prefix(ref, "refs/tags/", &ref))
+			die(_("a branch is expected, got tag '%s'"), ref);
+		if (skip_prefix(ref, "refs/remotes/", &ref))
+			die(_("a branch is expected, got remote branch '%s'"), ref);
+		die(_("a branch is expected, got '%s'"), ref);
+	}
+	if (branch_info->commit)
+		die(_("a branch is expected, got commit '%s'"), branch_info->name);
+	/*
+	 * This case should never happen because we already die() on
+	 * non-commit, but just in case.
+	 */
+	die(_("a branch is expected, got '%s'"), branch_info->name);
+}
+
 static int checkout_branch(struct checkout_opts *opts,
 			   struct branch_info *new_branch_info)
 {
@@ -1345,6 +1369,14 @@ static int checkout_branch(struct checkout_opts *opts,
 	    !opts->force_detach)
 		die(_("missing branch or commit argument"));
 
+	if (!opts->implicit_detach &&
+	    !opts->force_detach &&
+	    !opts->new_branch &&
+	    !opts->new_branch_force &&
+	    new_branch_info->name &&
+	    !new_branch_info->path)
+		die_expecting_a_branch(new_branch_info);
+
 	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
 	    !opts->ignore_other_worktrees) {
 		int flag;
@@ -1602,6 +1634,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.no_dwim_new_local_branch = 0;
 	opts.switch_branch_doing_nothing_is_ok = 1;
 	opts.accept_pathspec = 1;
+	opts.implicit_detach = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1633,6 +1666,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	opts.no_dwim_new_local_branch = 0;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
+	opts.implicit_detach = 0;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 19/26] switch: add short option for --detach

"git checkout" automatically detaches branches and --detach is not
that useful (--no-detach is more likely). But for "switch", you
may want to use it more often once you're used to detached HEAD. This
of course adds -d to git-checkout but it does not harm (yet?) to do it.
---
 builtin/checkout.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/checkout.c +1 −1
diff --git a/builtin/checkout.c b/builtin/checkout.c
index d72e28aabb..a14133b474 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1423,7 +1423,7 @@ static struct option *add_common_switch_branch_options(
 	struct checkout_opts *opts, struct option *prevopts)
 {
 	struct option options[] = {
-		OPT_BOOL(0, "detach", &opts->force_detach, N_("detach HEAD at named commit")),
+		OPT_BOOL('d', "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),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 20/26] switch: implicit dwim, use --no-guess to disable it

This is already the default in git-checkout. The real change in here is just minor cleanup. The main excuse is to explain why dwim is kept default.

Contrary to detach mode that is easy to get into and confusing to get
back out. Automatically creating a tracking branch often does not kick
in as often (you would need a branch of the same name on a remote). And
since the branch creation is reported clearly, the user should be able
to undo/delete it if it's unwanted.
---
 Documentation/git-checkout.txt | 38 ++++++++++++++++++++--------------
 builtin/checkout.c             | 16 +++++++-------
 2 files changed, 31 insertions(+), 23 deletions(-)
Show changes to 2 files +31 −22

Documentation/git-checkout.txt, builtin/checkout.c

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index c7192bdefe..58f18a0842 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -31,22 +31,13 @@ branch.
 	`<branch>`.
 +
 If `<branch>` is not found but there does exist a tracking branch in
-exactly one remote (call it `<remote>`) with a matching name, treat as
-equivalent to
+exactly one remote (call it `<remote>`) with a matching name and
+`--no-guess` is not specified, treat as equivalent to
 +
 ------------
 $ git checkout -b <branch> --track <remote>/<branch>
 ------------
 +
-If the branch exists in multiple remotes and one of them is named by
-the `checkout.defaultRemote` configuration variable, we'll use that
-one for the purposes of disambiguation, even if the `<branch>` isn't
-unique across all remotes. Set it to
-e.g. `checkout.defaultRemote=origin` to always checkout remote
-branches from there if `<branch>` is ambiguous but exists on the
-'origin' remote. See also `checkout.defaultRemote` in
-linkgit:git-config[1].
-+
 You could omit `<branch>`, in which case the command degenerates to
 "check out the current branch", which is a glorified no-op with
 rather expensive side-effects to show only the tracking information,
@@ -183,6 +174,27 @@ explicitly give a name with `-b` in such a case.
 	Do not set up "upstream" configuration, even if the
 	`branch.autoSetupMerge` configuration variable is true.
 
+--guess::
+--no-guess::
+	If `<branch>` is not found but there does exist a tracking
+	branch in exactly one remote (call it `<remote>`) with a
+	matching name, treat as equivalent to
++
+------------
+$ git checkout -b <branch> --track <remote>/<branch>
+------------
++
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to
+e.g. `checkout.defaultRemote=origin` to always checkout remote
+branches from there if `<branch>` is ambiguous but exists on the
+'origin' remote. See also `checkout.defaultRemote` in
+linkgit:git-config[1].
++
+Use `--no-guess` to disable this.
+
 -l::
 	Create the new branch's reflog; see linkgit:git-branch[1] for
 	details.
@@ -287,10 +299,6 @@ Note that this option uses the no overlay mode by default (see also
 	Just like linkgit:git-submodule[1], this will detach `HEAD` of the
 	submodule.
 
---no-guess::
-	Do not attempt to create a branch if a remote tracking branch
-	of the same name exists.
-
 --overlay::
 --no-overlay::
 	In the default overlay mode, `git checkout` never
diff --git a/builtin/checkout.c b/builtin/checkout.c
index a14133b474..ee74379038 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -53,7 +53,7 @@ struct checkout_opts {
 	int show_progress;
 	int count_checkout_paths;
 	int overlay_mode;
-	int no_dwim_new_local_branch;
+	int dwim_new_local_branch;
 	int discard_changes;
 	int accept_pathspec;
 	int switch_branch_doing_nothing_is_ok;
@@ -1430,8 +1430,6 @@ static struct option *add_common_switch_branch_options(
 		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
-			 N_("second guess 'git checkout <no-such-branch>'")),
 		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
 		OPT_END()
@@ -1468,7 +1466,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 {
 	struct branch_info new_branch_info;
 	int dwim_remotes_matched = 0;
-	int dwim_new_local_branch;
 
 	memset(&new_branch_info, 0, sizeof(new_branch_info));
 	opts->overwrite_ignore = 1;
@@ -1483,7 +1480,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 	argc = parse_options(argc, argv, prefix, options, usagestr,
 			     PARSE_OPT_KEEP_DASHDASH);
 
-	dwim_new_local_branch = !opts->no_dwim_new_local_branch;
 	if (opts->show_progress < 0) {
 		if (opts->quiet)
 			opts->show_progress = 0;
@@ -1545,7 +1541,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 		struct object_id rev;
 		int dwim_ok =
 			!opts->patch_mode &&
-			dwim_new_local_branch &&
+			opts->dwim_new_local_branch &&
 			opts->track == BRANCH_TRACK_UNSPECIFIED &&
 			!opts->new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
@@ -1626,12 +1622,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		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, "guess", &opts.dwim_new_local_branch,
+			 N_("second guess 'git checkout <no-such-branch>' (default)")),
 		OPT_END()
 	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
-	opts.no_dwim_new_local_branch = 0;
+	opts.dwim_new_local_branch = 1;
 	opts.switch_branch_doing_nothing_is_ok = 1;
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
@@ -1656,6 +1654,8 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 			   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()
@@ -1663,7 +1663,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
-	opts.no_dwim_new_local_branch = 0;
+	opts.dwim_new_local_branch = 1;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
 	opts.implicit_detach = 0;
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 21/26] switch: no worktree status unless real branch switch happens

When we switch from one branch to another, it makes sense to show a summary of local changes since there could be conflicts, or some files left modified.... When switch is used solely for creating a new branch (and "switch" to the same commit) or detaching, we don't really need to show anything.

"git checkout" does it anyway for historical reasons. But we can start with a clean slate with switch and don't have to.

This essentially reverts fa655d8411 (checkout: optimize "git checkout
-b <new_branch>" - 2018-08-16) and make it default for switch,
but also for -B and --detach. Users of big repos are encouraged to
move to switch.
---
 Documentation/config/checkout.txt |   8 --
 builtin/checkout.c                | 134 ++----------------------------
 t/t1090-sparse-checkout-scope.sh  |  14 ----
 3 files changed, 8 insertions(+), 148 deletions(-)
Show changes to 3 files +8 −148

Documentation/config/checkout.txt, builtin/checkout.c, t/t1090-sparse-checkout-scope.sh

diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt
index d6872ffa83..6b646813ab 100644
--- a/Documentation/config/checkout.txt
+++ b/Documentation/config/checkout.txt
@@ -16,11 +16,3 @@ will checkout the '<something>' branch on another remote,
 and by linkgit:git-worktree[1] when 'git worktree add' refers to a
 remote branch. This setting might be used for other checkout-like
 commands or functionality in the future.
-
-checkout.optimizeNewBranch::
-	Optimizes the performance of "git checkout -b <new_branch>" when
-	using sparse-checkout.  When set to true, git will not update the
-	repo based on the current sparse-checkout settings.  This means it
-	will not update the skip-worktree bit in the index nor add/remove
-	files in the working directory to reflect the current sparse checkout
-	settings nor will it show the local changes.
diff --git a/builtin/checkout.c b/builtin/checkout.c
index ee74379038..2aeaac092d 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -26,8 +26,6 @@
 #include "unpack-trees.h"
 #include "xdiff-interface.h"
 
-static int checkout_optimize_new_branch;
-
 static const char * const checkout_usage[] = {
 	N_("git checkout [<options>] <branch>"),
 	N_("git checkout [<options>] [<branch>] -- <file>..."),
@@ -57,11 +55,7 @@ struct checkout_opts {
 	int discard_changes;
 	int accept_pathspec;
 	int switch_branch_doing_nothing_is_ok;
-
-	/*
-	 * If new checkout options are added, skip_merge_working_tree
-	 * should be updated accordingly.
-	 */
+	int only_merge_on_switching_branches;
 
 	const char *new_branch;
 	const char *new_branch_force;
@@ -565,112 +559,6 @@ static void setup_branch_path(struct branch_info *branch)
 	branch->path = strbuf_detach(&buf, NULL);
 }
 
-/*
- * Skip merging the trees, updating the index and working directory if and
- * only if we are creating a new branch via "git checkout -b <new_branch>."
- */
-static int skip_merge_working_tree(const struct checkout_opts *opts,
-	const struct branch_info *old_branch_info,
-	const struct branch_info *new_branch_info)
-{
-	/*
-	 * Do the merge if sparse checkout is on and the user has not opted in
-	 * to the optimized behavior
-	 */
-	if (core_apply_sparse_checkout && !checkout_optimize_new_branch)
-		return 0;
-
-	/*
-	 * We must do the merge if we are actually moving to a new commit.
-	 */
-	if (!old_branch_info->commit || !new_branch_info->commit ||
-		!oideq(&old_branch_info->commit->object.oid,
-		       &new_branch_info->commit->object.oid))
-		return 0;
-
-	/*
-	 * opts->patch_mode cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * opts->quiet only impacts output so doesn't require a merge
-	 */
-
-	/*
-	 * Honor the explicit request for a three-way merge or to throw away
-	 * local changes
-	 */
-	if (opts->merge || opts->force)
-		return 0;
-
-	/*
-	 * --detach is documented as "updating the index and the files in the
-	 * working tree" but this optimization skips those steps so fall through
-	 * to the regular code path.
-	 */
-	if (opts->force_detach)
-		return 0;
-
-	/*
-	 * opts->writeout_stage cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * Honor the explicit ignore requests
-	 */
-	if (!opts->overwrite_ignore || opts->ignore_skipworktree ||
-		opts->ignore_other_worktrees)
-		return 0;
-
-	/*
-	 * opts->show_progress only impacts output so doesn't require a merge
-	 */
-
-	/*
-	 * opts->overlay_mode cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * If we aren't creating a new branch any changes or updates will
-	 * happen in the existing branch.  Since that could only be updating
-	 * the index and working directory, we don't want to skip those steps
-	 * or we've defeated any purpose in running the command.
-	 */
-	if (!opts->new_branch)
-		return 0;
-
-	/*
-	 * new_branch_force is defined to "create/reset and checkout a branch"
-	 * so needs to go through the merge to do the reset
-	 */
-	if (opts->new_branch_force)
-		return 0;
-
-	/*
-	 * A new orphaned branch requrires the index and the working tree to be
-	 * adjusted to <start_point>
-	 */
-	if (opts->new_orphan_branch)
-		return 0;
-
-	/*
-	 * Remaining variables are not checkout options but used to track state
-	 */
-
-	 /*
-	  * Do the merge if this is the initial checkout. We cannot use
-	  * is_cache_unborn() here because the index hasn't been loaded yet
-	  * so cache_nr and timestamp.sec are always zero.
-	  */
-	if (!file_exists(get_index_file()))
-		return 0;
-
-	return 1;
-}
-
 static int merge_working_tree(const struct checkout_opts *opts,
 			      struct branch_info *old_branch_info,
 			      struct branch_info *new_branch_info,
@@ -1028,6 +916,7 @@ static int switch_branches(const struct checkout_opts *opts,
 	void *path_to_free;
 	struct object_id rev;
 	int flag, writeout_error = 0;
+	int do_merge = 1;
 
 	trace2_cmd_mode("branch");
 
@@ -1047,16 +936,12 @@ static int switch_branches(const struct checkout_opts *opts,
 		if (!new_branch_info->commit)
 			die(_("You are on a branch yet to be born"));
 		parse_commit_or_die(new_branch_info->commit);
+
+		if (opts->only_merge_on_switching_branches)
+			do_merge = 0;
 	}
 
-	/* optimize the "checkout -b <new_branch> path */
-	if (skip_merge_working_tree(opts, &old_branch_info, new_branch_info)) {
-		if (!checkout_optimize_new_branch && !opts->quiet) {
-			if (read_cache_preload(NULL) < 0)
-				return error(_("index file corrupt"));
-			show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
-		}
-	} else {
+	if (do_merge) {
 		ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
 		if (ret) {
 			free(path_to_free);
@@ -1076,11 +961,6 @@ static int switch_branches(const struct checkout_opts *opts,
 
 static int git_checkout_config(const char *var, const char *value, void *cb)
 {
-	if (!strcmp(var, "checkout.optimizenewbranch")) {
-		checkout_optimize_new_branch = git_config_bool(var, value);
-		return 0;
-	}
-
 	if (!strcmp(var, "diff.ignoresubmodules")) {
 		struct checkout_opts *opts = cb;
 		handle_ignore_submodules_arg(&opts->diff_options, value);
@@ -1631,6 +1511,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	memset(&opts, 0, sizeof(opts));
 	opts.dwim_new_local_branch = 1;
 	opts.switch_branch_doing_nothing_is_ok = 1;
+	opts.only_merge_on_switching_branches = 0;
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
 
@@ -1666,6 +1547,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	opts.dwim_new_local_branch = 1;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
+	opts.only_merge_on_switching_branches = 1;
 	opts.implicit_detach = 0;
 
 	options = parse_options_dup(switch_options);
diff --git a/t/t1090-sparse-checkout-scope.sh b/t/t1090-sparse-checkout-scope.sh
index 090b7fc3d3..40cc004326 100755
--- a/t/t1090-sparse-checkout-scope.sh
+++ b/t/t1090-sparse-checkout-scope.sh
@@ -31,20 +31,6 @@ test_expect_success 'perform sparse checkout of master' '
 	test_path_is_file c
 '
 
-test_expect_success 'checkout -b checkout.optimizeNewBranch interaction' '
-	cp .git/info/sparse-checkout .git/info/sparse-checkout.bak &&
-	test_when_finished "
-		mv -f .git/info/sparse-checkout.bak .git/info/sparse-checkout
-		git checkout master
-	" &&
-	echo "/b" >>.git/info/sparse-checkout &&
-	test "$(git ls-files -t b)" = "S b" &&
-	git -c checkout.optimizeNewBranch=true checkout -b fast &&
-	test "$(git ls-files -t b)" = "S b" &&
-	git checkout -b slow &&
-	test "$(git ls-files -t b)" = "H b"
-'
-
 test_expect_success 'merge feature branch into sparse checkout of master' '
 	git merge feature &&
 	test_path_is_file a &&
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 22/26] switch: reject if some operation is in progress

Unless you know what you're doing, switching to another branch to do something then switching back could be confusing. Worse, you may even forget that you're in the middle of something. By the time you realize, you may have done a ton of work and it gets harder to go back.

The new option --ignore-in-progress is added to override this check. However it's probably just safer to create a separate worktree for that separate thing you want to work on and leave this worktree alone (unless of course creating or preparing worktrees are not cheap).

--force is updated to also imply --ignore-in-progress because it is
supposed to be the "just do your things and don't bother me" option.
---
 builtin/checkout.c | 30 +++++++++++++++++++++++++++++-
 1 file changed, 29 insertions(+), 1 deletion(-)
Show changes to builtin/checkout.c +29 −1
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2aeaac092d..407ed8be94 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -24,6 +24,7 @@
 #include "tree.h"
 #include "tree-walk.h"
 #include "unpack-trees.h"
+#include "wt-status.h"
 #include "xdiff-interface.h"
 
 static const char * const checkout_usage[] = {
@@ -56,6 +57,7 @@ struct checkout_opts {
 	int accept_pathspec;
 	int switch_branch_doing_nothing_is_ok;
 	int only_merge_on_switching_branches;
+	int can_switch_when_in_progress;
 
 	const char *new_branch;
 	const char *new_branch_force;
@@ -1257,6 +1259,26 @@ static int checkout_branch(struct checkout_opts *opts,
 	    !new_branch_info->path)
 		die_expecting_a_branch(new_branch_info);
 
+	if (!opts->can_switch_when_in_progress) {
+		struct wt_status_state state;
+
+		memset(&state, 0, sizeof(state));
+		wt_status_get_state(the_repository, &state, 0);
+
+		if (state.merge_in_progress)
+			die(_("cannot switch branch while merging"));
+		if (state.am_in_progress)
+			die(_("cannot switch branch in the middle of an am session"));
+		if (state.rebase_interactive_in_progress || state.rebase_in_progress)
+			die(_("cannot switch branch while rebasing"));
+		if (state.cherry_pick_in_progress)
+			die(_("cannot switch branch while cherry-picking"));
+		if (state.revert_in_progress)
+			die(_("cannot switch branch while reverting"));
+		if (state.bisect_in_progress)
+			die(_("cannot switch branch while bisecting"));
+	}
+
 	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
 	    !opts->ignore_other_worktrees) {
 		int flag;
@@ -1371,8 +1393,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 		opts->merge = 1; /* implied */
 		git_xmerge_config("merge.conflictstyle", opts->conflict_style, NULL);
 	}
-	if (opts->force)
+	if (opts->force) {
 		opts->discard_changes = 1;
+		opts->can_switch_when_in_progress = 1;
+	}
 
 	if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
 		die(_("-b, -B and --orphan are mutually exclusive"));
@@ -1514,6 +1538,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.only_merge_on_switching_branches = 0;
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
+	opts.can_switch_when_in_progress = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1539,6 +1564,8 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 			 N_("second guess 'git switch <no-such-branch>'")),
 		OPT_BOOL(0, "discard-changes", &opts.discard_changes,
 			 N_("throw away local modifications")),
+		OPT_BOOL(0, "ignore-in-progress", &opts.can_switch_when_in_progress,
+			 N_("allow to switch branch when some operation is still in progress")),
 		OPT_END()
 	};
 	int ret;
@@ -1549,6 +1576,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	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;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 23/26] switch: make --orphan switch to an empty tree

Switching and creating branches always involves knowing the <start-point> to begin the new branch from. Sometimes, people want to create a new branch that does not have any commits yet; --orphan is a flag to allow that.

--orphan overrides the default of HEAD for <start-point> instead causing us to start from an empty history with all tracked files removed from the index and working tree. The use of --orphan is incompatible with specifying a <start-point>.

A note on the implementation. An alternative is just create a dummy
commit in-core with empty tree and switch to it. But there's a chance
the commit's SHA-1 may end up somewhere permanent like reflog. It's best
to make sure "commit" pointer is NULL to avoid it.
---
 builtin/checkout.c | 39 +++++++++++++++++++++++++++++++--------
 1 file changed, 31 insertions(+), 8 deletions(-)
Show changes to builtin/checkout.c +31 −8
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 407ed8be94..c7f4b5e3c0 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -58,6 +58,7 @@ struct checkout_opts {
 	int switch_branch_doing_nothing_is_ok;
 	int only_merge_on_switching_branches;
 	int can_switch_when_in_progress;
+	int orphan_from_empty_tree;
 
 	const char *new_branch;
 	const char *new_branch_force;
@@ -568,15 +569,21 @@ static int merge_working_tree(const struct checkout_opts *opts,
 {
 	int ret;
 	struct lock_file lock_file = LOCK_INIT;
+	struct tree *new_tree;
 
 	hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
 	if (read_cache_preload(NULL) < 0)
 		return error(_("index file corrupt"));
 
 	resolve_undo_clear();
+	if (opts->new_orphan_branch && opts->orphan_from_empty_tree) {
+		if (new_branch_info->commit)
+			BUG("'switch --orphan' should never accept a commit as starting point");
+		new_tree = parse_tree_indirect(the_hash_algo->empty_tree);
+	} else
+		new_tree = get_commit_tree(new_branch_info->commit);
 	if (opts->discard_changes) {
-		ret = reset_tree(get_commit_tree(new_branch_info->commit),
-				 opts, 1, writeout_error);
+		ret = reset_tree(new_tree, opts, 1, writeout_error);
 		if (ret)
 			return ret;
 	} else {
@@ -614,7 +621,8 @@ static int merge_working_tree(const struct checkout_opts *opts,
 					   &old_branch_info->commit->object.oid :
 					   the_hash_algo->empty_tree);
 		init_tree_desc(&trees[0], tree->buffer, tree->size);
-		tree = parse_tree_indirect(&new_branch_info->commit->object.oid);
+		parse_tree(new_tree);
+		tree = new_tree;
 		init_tree_desc(&trees[1], tree->buffer, tree->size);
 
 		ret = unpack_trees(2, trees, &topts);
@@ -663,7 +671,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			o.verbosity = 0;
 			work = write_tree_from_memory(&o);
 
-			ret = reset_tree(get_commit_tree(new_branch_info->commit),
+			ret = reset_tree(new_tree,
 					 opts, 1,
 					 writeout_error);
 			if (ret)
@@ -672,13 +680,13 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			o.branch1 = new_branch_info->name;
 			o.branch2 = "local";
 			ret = merge_trees(&o,
-					  get_commit_tree(new_branch_info->commit),
+					  new_tree,
 					  work,
 					  get_commit_tree(old_branch_info->commit),
 					  &result);
 			if (ret < 0)
 				exit(128);
-			ret = reset_tree(get_commit_tree(new_branch_info->commit),
+			ret = reset_tree(new_tree,
 					 opts, 0,
 					 writeout_error);
 			strbuf_release(&o.obuf);
@@ -696,7 +704,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->discard_changes && !opts->quiet)
+	if (!opts->discard_changes && !opts->quiet && new_branch_info->commit)
 		show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
 
 	return 0;
@@ -897,7 +905,10 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne
 	add_pending_object(&revs, object, oid_to_hex(&object->oid));
 
 	for_each_ref(add_pending_uninteresting_ref, &revs);
-	add_pending_oid(&revs, "HEAD", &new_commit->object.oid, UNINTERESTING);
+	if (new_commit)
+		add_pending_oid(&revs, "HEAD",
+				&new_commit->object.oid,
+				UNINTERESTING);
 
 	if (prepare_revision_walk(&revs))
 		die(_("internal error in revision walk"));
@@ -932,6 +943,14 @@ static int switch_branches(const struct checkout_opts *opts,
 	if (old_branch_info.path)
 		skip_prefix(old_branch_info.path, "refs/heads/", &old_branch_info.name);
 
+	if (opts->new_orphan_branch && opts->orphan_from_empty_tree) {
+		if (new_branch_info->name)
+			BUG("'switch --orphan' should never accept a commit as starting point");
+		new_branch_info->commit = NULL;
+		new_branch_info->name = "(empty)";
+		do_merge = 1;
+	}
+
 	if (!new_branch_info->name) {
 		new_branch_info->name = "HEAD";
 		new_branch_info->commit = old_branch_info.commit;
@@ -1235,6 +1254,8 @@ static int checkout_branch(struct checkout_opts *opts,
 	if (opts->new_orphan_branch) {
 		if (opts->track != BRANCH_TRACK_UNSPECIFIED)
 			die(_("'%s' cannot be used with '%s'"), "--orphan", "-t");
+		if (opts->orphan_from_empty_tree && new_branch_info->name)
+			die(_("'%s' cannot take <start-point>"), "--orphan");
 	} else if (opts->force_detach) {
 		if (opts->track != BRANCH_TRACK_UNSPECIFIED)
 			die(_("'%s' cannot be used with '%s'"), "--detach", "-t");
@@ -1539,6 +1560,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
 	opts.can_switch_when_in_progress = 1;
+	opts.orphan_from_empty_tree = 0;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1577,6 +1599,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	opts.only_merge_on_switching_branches = 1;
 	opts.implicit_detach = 0;
 	opts.can_switch_when_in_progress = 0;
+	opts.orphan_from_empty_tree = 1;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 24/26] t: add tests for switch

---
 t/t2060-switch.sh (new +x) | 98 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 98 insertions(+)
Show changes to t/t2060-switch.sh +98 −0
diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
new file mode 100755
index 0000000000..81e13cf7f0
--- /dev/null
+++ b/t/t2060-switch.sh
@@ -0,0 +1,98 @@
+#!/bin/sh
+
+test_description='switch basic functionality'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit first &&
+	git branch first-branch &&
+	test_commit second &&
+	test_commit third &&
+	git remote add origin nohost:/nopath &&
+	git update-ref refs/remotes/origin/foo first-branch
+'
+
+test_expect_success 'switch branch no arguments' '
+	test_must_fail git switch
+'
+
+test_expect_success 'switch branch' '
+	git switch first-branch &&
+	test_path_is_missing second.t
+'
+
+test_expect_success 'switch and detach' '
+	test_when_finished git switch master &&
+	test_must_fail git switch master^{commit} &&
+	git switch --detach master^{commit} &&
+	test_must_fail git symbolic-ref HEAD
+'
+
+test_expect_success 'switch and detach current branch' '
+	test_when_finished git switch master &&
+	git switch master &&
+	git switch --detach &&
+	test_must_fail git symbolic-ref HEAD
+'
+
+test_expect_success 'switch and create branch' '
+	test_when_finished git switch master &&
+	git switch -c temp master^ &&
+	test_cmp_rev master^ refs/heads/temp &&
+	echo refs/heads/temp >expected-branch &&
+	git symbolic-ref HEAD >actual-branch &&
+	test_cmp expected-branch actual-branch
+'
+
+test_expect_success 'force create branch from HEAD' '
+	test_when_finished git switch master &&
+	git switch --detach master &&
+	test_must_fail git switch -c temp &&
+	git switch -C temp &&
+	test_cmp_rev master refs/heads/temp &&
+	echo refs/heads/temp >expected-branch &&
+	git symbolic-ref HEAD >actual-branch &&
+	test_cmp expected-branch actual-branch
+'
+
+test_expect_success 'new orphan branch from empty' '
+	test_when_finished git switch master &&
+	test_must_fail git switch --orphan new-orphan HEAD &&
+	git switch --orphan new-orphan &&
+	test_commit orphan &&
+	git cat-file commit refs/heads/new-orphan >commit &&
+	! grep ^parent commit &&
+	git ls-files >tracked-files &&
+	echo orphan.t >expected &&
+	test_cmp expected tracked-files
+'
+
+test_expect_success 'switching ignores file of same branch name' '
+	test_when_finished git switch master &&
+	: >first-branch &&
+	git switch first-branch &&
+	echo refs/heads/first-branch >expected &&
+	git symbolic-ref HEAD >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'guess and create branch ' '
+	test_when_finished git switch master &&
+	test_must_fail git switch --no-guess foo &&
+	git switch foo &&
+	echo refs/heads/foo >expected &&
+	git symbolic-ref HEAD >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'switching when something is in progress' '
+	test_when_finished rm -f .git/MERGE_HEAD &&
+	# fake a merge-in-progress
+	cp .git/HEAD .git/MERGE_HEAD &&
+	test_must_fail git switch -d @^ &&
+	git switch --ignore-in-progress -d @^ &&
+	git switch -
+'
+
+test_done
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 25/26] completion: support switch

Completion support for --guess could be made better. If no --detach is
given, we should only provide a list of refs/heads/* and dwim ones,
not the entire ref space. But I still can't penetrate that
__git_refs() function yet.
---
 contrib/completion/git-completion.bash | 37 +++++++++++++++++++++++++-
 1 file changed, 36 insertions(+), 1 deletion(-)
Show changes to contrib/completion/git-completion.bash +36 −1
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 976e4a6548..b24bc48276 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -37,7 +37,8 @@
 #   GIT_COMPLETION_CHECKOUT_NO_GUESS
 #
 #     When set to "1", do not include "DWIM" suggestions in git-checkout
-#     completion (e.g., completing "foo" when "origin/foo" exists).
+#     and git-switch completion (e.g., completing "foo" when "origin/foo"
+#     exists).
 
 case "$COMP_WORDBREAKS" in
 *:*) : great ;;
@@ -2158,6 +2159,40 @@ _git_status ()
 	__git_complete_index_file "$complete_opt"
 }
 
+_git_switch ()
+{
+	case "$cur" in
+	--conflict=*)
+		__gitcomp "diff3 merge" "" "${cur##--conflict=}"
+		;;
+	--*)
+		__gitcomp_builtin switch
+		;;
+	*)
+		# check if --track, --no-track, or --no-guess was specified
+		# if so, disable DWIM mode
+		local track_opt="--track" only_local_ref=n
+		if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ] ||
+		   [ -n "$(__git_find_on_cmdline "--track --no-track --no-guess")" ]; then
+			track_opt=''
+		fi
+		# explicit --guess enables DWIM mode regardless of
+		# $GIT_COMPLETION_CHECKOUT_NO_GUESS
+		if [ -n "$(__git_find_on_cmdline "--guess")" ]; then
+			track_opt='--track'
+		fi
+		if [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then
+			only_local_ref=y
+		fi
+		if [ $only_local_ref = y -a -z "$track_opt" ]; then
+			__gitcomp_direct "$(__git_heads "" "$cur" " ")"
+		else
+			__git_complete_refs $track_opt
+		fi
+		;;
+	esac
+}
+
 __git_config_get_set_variables ()
 {
 	local prevword word config_file= c=$cword
-- 
2.21.0.548.gd3c7d92dc2
Nguyễn Thái Ngọc Duy· Mar 21, 2019, 13:16 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v5 26/26] doc: promote "git switch"

The new command "git switch" is added to avoid the confusion of one-command-do-all "git checkout" for new users. They are also helpful to avoid ambiguation context.

For these reasons, promote it everywhere possible. This includes
documentation, suggestions/advice from other commands...
---
 Documentation/git-branch.txt           | 12 +++---
 Documentation/git-check-ref-format.txt |  3 +-
 Documentation/git-format-patch.txt     |  2 +-
 Documentation/git-merge-base.txt       |  2 +-
 Documentation/git-rebase.txt           |  2 +-
 Documentation/git-remote.txt           |  2 +-
 Documentation/git-rerere.txt           | 10 ++---
 Documentation/git-reset.txt            | 20 ++++-----
 Documentation/git-stash.txt            |  9 +++--
 Documentation/gitcore-tutorial.txt     | 19 +++++----
 Documentation/giteveryday.txt          | 24 +++++------
 Documentation/gittutorial.txt          |  4 +-
 Documentation/gitworkflows.txt         |  3 +-
 Documentation/revisions.txt            |  2 +-
 Documentation/user-manual.txt          | 56 +++++++++++++-------------
 advice.c                               | 10 ++---
 sha1-name.c                            |  2 +-
 t/t2020-checkout-detach.sh             | 20 ++++-----
 18 files changed, 101 insertions(+), 101 deletions(-)
Show changes to 18 files +101 −101

Documentation/git-branch.txt, Documentation/git-check-ref-format.txt, Documentation/git-format-patch.txt, Documentation/git-merge-base.txt, Documentation/git-rebase.txt, Documentation/git-remote.txt, Documentation/git-rerere.txt, Documentation/git-reset.txt, Documentation/git-stash.txt, Documentation/gitcore-tutorial.txt, Documentation/giteveryday.txt, Documentation/gittutorial.txt, Documentation/gitworkflows.txt, Documentation/revisions.txt, Documentation/user-manual.txt, advice.c, sha1-name.c, t/t2020-checkout-detach.sh

diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 0cd87ddeff..1e2d89b174 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -48,7 +48,7 @@ The command's second form creates a new branch head named <branchname>
 which points to the current `HEAD`, or <start-point> if given.
 
 Note that this will create the new branch, but it will not switch the
-working tree to it; use "git checkout <newbranch>" to switch to the
+working tree to it; use "git switch <newbranch>" to switch to the
 new branch.
 
 When a local branch is started off a remote-tracking branch, Git sets up the
@@ -198,7 +198,7 @@ This option is only applicable in non-verbose mode.
 +
 This behavior is the default when the start point is a remote-tracking branch.
 Set the branch.autoSetupMerge configuration variable to `false` if you
-want `git checkout` and `git branch` to always behave as if `--no-track`
+want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
 were given. Set it to `always` if you want this behavior when the
 start-point is either a local or remote-tracking branch.
 
@@ -297,7 +297,7 @@ Start development from a known tag::
 $ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6
 $ cd my2.6
 $ git branch my2.6.14 v2.6.14   <1>
-$ git checkout my2.6.14
+$ git switch my2.6.14
 ------------
 +
 <1> This step and the next one could be combined into a single step with
@@ -322,9 +322,9 @@ $ git branch -D test                                    <2>
 NOTES
 -----
 
-If you are creating a branch that you want to checkout immediately, it is
-easier to use the git checkout command with its `-b` option to create
-a branch and check it out with a single command.
+If you are creating a branch that you want to switch to immediately,
+it is easier to use the "git switch" command with its `-c` option to
+do the same thing with a single command.
 
 The options `--contains`, `--no-contains`, `--merged` and `--no-merged`
 serve four related but different purposes:
diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt
index d9de992585..ee6a4144fb 100644
--- a/Documentation/git-check-ref-format.txt
+++ b/Documentation/git-check-ref-format.txt
@@ -88,7 +88,8 @@ but it is explicitly forbidden at the beginning of a branch name).
 When run with `--branch` option in a repository, the input is first
 expanded for the ``previous checkout syntax''
 `@{-n}`.  For example, `@{-1}` is a way to refer the last thing that
-was checked out using "git checkout" operation. This option should be
+was checked out using "git switch" or "git checkout" operation.
+This option should be
 used by porcelains to accept this syntax anywhere a branch name is
 expected, so they can act as if you typed the branch name. As an
 exception note that, the ``previous checkout operation'' might result
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 1af85d404f..0a24a5679e 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -421,7 +421,7 @@ One way to test if your MUA is set up correctly is:
 * Apply it:
 
     $ git fetch <project> master:test-apply
-    $ git checkout test-apply
+    $ git switch test-apply
     $ git reset --hard
     $ git am a.patch
 
diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt
index 9f07f4f6ed..261d5c1164 100644
--- a/Documentation/git-merge-base.txt
+++ b/Documentation/git-merge-base.txt
@@ -149,7 +149,7 @@ instead.
 Discussion on fork-point mode
 -----------------------------
 
-After working on the `topic` branch created with `git checkout -b
+After working on the `topic` branch created with `git switch -c
 topic origin/master`, the history of remote-tracking branch
 `origin/master` may have been rewound and rebuilt, leading to a
 history of this shape:
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 5629ba4c5d..cb6fc166e2 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -17,7 +17,7 @@ SYNOPSIS
 DESCRIPTION
 -----------
 If <branch> is specified, 'git rebase' will perform an automatic
-`git checkout <branch>` before doing anything else.  Otherwise
+`git switch <branch>` before doing anything else.  Otherwise
 it remains on the current branch.
 
 If <upstream> is not specified, the upstream configured in
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index 0cad37fb81..9659abbf8e 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -230,7 +230,7 @@ $ git branch -r
   staging/master
   staging/staging-linus
   staging/staging-next
-$ git checkout -b staging staging/master
+$ git switch -c staging staging/master
 ...
 ------------
 
diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt
index df310d2a58..fe4434ad9d 100644
--- a/Documentation/git-rerere.txt
+++ b/Documentation/git-rerere.txt
@@ -91,7 +91,7 @@ For such a test, you need to merge master and topic somehow.
 One way to do it is to pull master into the topic branch:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 
               o---*---o---+ topic
@@ -113,10 +113,10 @@ the upstream might have been advanced since the test merge `+`,
 in which case the final commit graph would look like this:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 	$ ... work on both topic and master branches
-	$ git checkout master
+	$ git switch master
 	$ git merge topic
 
               o---*---o---+---o---o topic
@@ -136,11 +136,11 @@ merges, you could blow away the test merge, and keep building on
 top of the tip before the test merge:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 	$ git reset --hard HEAD^ ;# rewind the test merge
 	$ ... work on both topic and master branches
-	$ git checkout master
+	$ git switch master
 	$ git merge topic
 
               o---*---o-------o---o topic
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 132f8e55f6..cbf901efb4 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -149,9 +149,9 @@ See also the `--amend` option to linkgit:git-commit[1].
 Undo a commit, making it a topic branch::
 +
 ------------
-$ git branch topic/wip     <1>
-$ git reset --hard HEAD~3  <2>
-$ git checkout topic/wip   <3>
+$ git branch topic/wip          <1>
+$ git reset --hard HEAD~3       <2>
+$ git switch topic/wip          <3>
 ------------
 +
 <1> You have made some commits, but realize they were premature
@@ -232,13 +232,13 @@ working tree are not in any shape to be committed yet, but you
 need to get to the other branch for a quick bugfix.
 +
 ------------
-$ git checkout feature ;# you were working in "feature" branch and
-$ work work work       ;# got interrupted
+$ git switch feature  ;# you were working in "feature" branch and
+$ work work work      ;# got interrupted
 $ git commit -a -m "snapshot WIP"                 <1>
-$ git checkout master
+$ git switch master
 $ fix fix fix
 $ git commit ;# commit with real log
-$ git checkout feature
+$ git switch feature
 $ git reset --soft HEAD^ ;# go back to WIP state  <2>
 $ git reset                                       <3>
 ------------
@@ -279,18 +279,18 @@ reset it while keeping the changes in your working tree.
 +
 ------------
 $ git tag start
-$ git checkout -b branch1
+$ git switch -c branch1
 $ edit
 $ git commit ...                            <1>
 $ edit
-$ git checkout -b branch2                   <2>
+$ git switch -c branch2                     <2>
 $ git reset --keep start                    <3>
 ------------
 +
 <1> This commits your first edits in `branch1`.
 <2> In the ideal world, you could have realized that the earlier
     commit did not belong to the new topic when you created and switched
-    to `branch2` (i.e. `git checkout -b branch2 start`), but nobody is
+    to `branch2` (i.e. `git switch -c branch2 start`), but nobody is
     perfect.
 <3> But you can use `reset --keep` to remove the unwanted commit after
     you switched to `branch2`.
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 7ef8c47911..ebb6282db3 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -235,12 +235,12 @@ return to your original branch to make the emergency fix, like this:
 +
 ----------------------------------------------------------------
 # ... hack hack hack ...
-$ git checkout -b my_wip
+$ git switch -c my_wip
 $ git commit -a -m "WIP"
-$ git checkout master
+$ git switch master
 $ edit emergency fix
 $ git commit -a -m "Fix in a hurry"
-$ git checkout my_wip
+$ git switch my_wip
 $ git reset --soft HEAD^
 # ... continue hacking ...
 ----------------------------------------------------------------
@@ -293,7 +293,8 @@ SEE ALSO
 linkgit:git-checkout[1],
 linkgit:git-commit[1],
 linkgit:git-reflog[1],
-linkgit:git-reset[1]
+linkgit:git-reset[1],
+linkgit:git-switch[1]
 
 GIT
 ---
diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt
index e29a9effcc..f880d21dfb 100644
--- a/Documentation/gitcore-tutorial.txt
+++ b/Documentation/gitcore-tutorial.txt
@@ -741,7 +741,7 @@ used earlier, and create a branch in it. You do that by simply just
 saying that you want to check out a new branch:
 
 ------------
-$ git checkout -b mybranch
+$ git switch -c mybranch
 ------------
 
 will create a new branch based at the current `HEAD` position, and switch
@@ -755,7 +755,7 @@ just telling 'git checkout' what the base of the checkout would be.
 In other words, if you have an earlier tag or branch, you'd just do
 
 ------------
-$ git checkout -b mybranch earlier-commit
+$ git switch -c mybranch earlier-commit
 ------------
 
 and it would create the new branch `mybranch` at the earlier commit,
@@ -765,7 +765,7 @@ and check out the state at that time.
 You can always just jump back to your original `master` branch by doing
 
 ------------
-$ git checkout master
+$ git switch master
 ------------
 
 (or any other branch-name, for that matter) and if you forget which
@@ -794,7 +794,7 @@ $ git branch <branchname> [startingpoint]
 
 which will simply _create_ the branch, but will not do anything further.
 You can then later -- once you decide that you want to actually develop
-on that branch -- switch to that branch with a regular 'git checkout'
+on that branch -- switch to that branch with a regular 'git switch'
 with the branchname as the argument.
 
 
@@ -808,7 +808,7 @@ being the same as the original `master` branch, let's make sure we're in
 that branch, and do some work there.
 
 ------------------------------------------------
-$ git checkout mybranch
+$ git switch mybranch
 $ echo "Work, work, work" >>hello
 $ git commit -m "Some work." -i hello
 ------------------------------------------------
@@ -825,7 +825,7 @@ does some work in the original branch, and simulate that by going back
 to the master branch, and editing the same file differently there:
 
 ------------
-$ git checkout master
+$ git switch master
 ------------
 
 Here, take a moment to look at the contents of `hello`, and notice how they
@@ -958,7 +958,7 @@ to the `master` branch. Let's go back to `mybranch`, and run
 'git merge' to get the "upstream changes" back to your branch.
 
 ------------
-$ git checkout mybranch
+$ git switch mybranch
 $ git merge -m "Merge upstream changes." master
 ------------
 
@@ -1133,9 +1133,8 @@ Remember, before running 'git merge', our `master` head was at
 work." commit.
 
 ------------
-$ git checkout mybranch
-$ git reset --hard master^2
-$ git checkout master
+$ git switch -C mybranch master^2
+$ git switch master
 $ git reset --hard master^
 ------------
 
diff --git a/Documentation/giteveryday.txt b/Documentation/giteveryday.txt
index 9f2528fc8c..ad455f3e39 100644
--- a/Documentation/giteveryday.txt
+++ b/Documentation/giteveryday.txt
@@ -41,7 +41,7 @@ following commands.
 
   * linkgit:git-log[1] to see what happened.
 
-  * linkgit:git-checkout[1] and linkgit:git-branch[1] to switch
+  * linkgit:git-switch[1] and linkgit:git-branch[1] to switch
     branches.
 
   * linkgit:git-add[1] to manage the index file.
@@ -80,7 +80,7 @@ $ git tag v2.43 <2>
 Create a topic branch and develop.::
 +
 ------------
-$ git checkout -b alsa-audio <1>
+$ git switch -c alsa-audio <1>
 $ edit/compile/test
 $ git checkout -- curses/ux_audio_oss.c <2>
 $ git add curses/ux_audio_alsa.c <3>
@@ -90,7 +90,7 @@ $ git commit -a -s <5>
 $ edit/compile/test
 $ git diff HEAD^ <6>
 $ git commit -a --amend <7>
-$ git checkout master <8>
+$ git switch master <8>
 $ git merge alsa-audio <9>
 $ git log --since='3 days ago' <10>
 $ git log v2.43.. curses/ <11>
@@ -148,11 +148,11 @@ Clone the upstream and work on it.  Feed changes to upstream.::
 ------------
 $ git clone git://git.kernel.org/pub/scm/.../torvalds/linux-2.6 my2.6
 $ cd my2.6
-$ git checkout -b mine master <1>
+$ git switch -c mine master <1>
 $ edit/compile/test; git commit -a -s <2>
 $ git format-patch master <3>
 $ git send-email --to="person <email@example.com>" 00*.patch <4>
-$ git checkout master <5>
+$ git switch master <5>
 $ git pull <6>
 $ git log -p ORIG_HEAD.. arch/i386 include/asm-i386 <7>
 $ git ls-remote --heads http://git.kernel.org/.../jgarzik/libata-dev.git <8>
@@ -194,7 +194,7 @@ satellite$ edit/compile/test/commit
 satellite$ git push origin <4>
 
 mothership$ cd frotz
-mothership$ git checkout master
+mothership$ git switch master
 mothership$ git merge satellite/master <5>
 ------------
 +
@@ -216,7 +216,7 @@ machine into the master branch.
 Branch off of a specific tag.::
 +
 ------------
-$ git checkout -b private2.6.14 v2.6.14 <1>
+$ git switch -c private2.6.14 v2.6.14 <1>
 $ edit/compile/test; git commit -a
 $ git checkout master
 $ git cherry-pick v2.6.14..private2.6.14 <2>
@@ -274,14 +274,14 @@ $ mailx <3>
 & s 2 3 4 5 ./+to-apply
 & s 7 8 ./+hold-linus
 & q
-$ git checkout -b topic/one master
+$ git switch -c topic/one master
 $ git am -3 -i -s ./+to-apply <4>
 $ compile/test
-$ git checkout -b hold/linus && git am -3 -i -s ./+hold-linus <5>
-$ git checkout topic/one && git rebase master <6>
-$ git checkout pu && git reset --hard next <7>
+$ git switch -c hold/linus && git am -3 -i -s ./+hold-linus <5>
+$ git switch topic/one && git rebase master <6>
+$ git switch -C pu next <7>
 $ git merge topic/one topic/two && git merge hold/linus <8>
-$ git checkout maint
+$ git switch maint
 $ git cherry-pick master~4 <9>
 $ compile/test
 $ git tag -s -m "GIT 0.99.9x" v0.99.9x <10>
diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt
index 242de31cb6..e6ad6b5f8d 100644
--- a/Documentation/gittutorial.txt
+++ b/Documentation/gittutorial.txt
@@ -207,7 +207,7 @@ automatically.  The asterisk marks the branch you are currently on;
 type
 
 ------------------------------------------------
-$ git checkout experimental
+$ git switch experimental
 ------------------------------------------------
 
 to switch to the experimental branch.  Now edit a file, commit the
@@ -216,7 +216,7 @@ change, and switch back to the master branch:
 ------------------------------------------------
 (edit file)
 $ git commit -a
-$ git checkout master
+$ git switch master
 ------------------------------------------------
 
 Check that the change you made is no longer visible, since it was
diff --git a/Documentation/gitworkflows.txt b/Documentation/gitworkflows.txt
index ca11c7bdaf..abc0dc6bc7 100644
--- a/Documentation/gitworkflows.txt
+++ b/Documentation/gitworkflows.txt
@@ -301,8 +301,7 @@ topics on 'next':
 .Rewind and rebuild next
 [caption="Recipe: "]
 =====================================
-* `git checkout next`
-* `git reset --hard master`
+* `git switch -C next master`
 * `git merge ai/topic_in_next1`
 * `git merge ai/topic_in_next2`
 * ...
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
index 72daa20e76..a1c7a65da6 100644
--- a/Documentation/revisions.txt
+++ b/Documentation/revisions.txt
@@ -115,7 +115,7 @@ Here's an example to make it more clear:
 ------------------------------
 $ git config push.default current
 $ git config remote.pushdefault myfork
-$ git checkout -b mybranch origin/master
+$ git switch -c mybranch origin/master
 
 $ git rev-parse --symbolic-full-name @{upstream}
 refs/remotes/origin/master
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index eff7890274..94799faa2b 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -122,10 +122,10 @@ Tags are expected to always point at the same version of a project,
 while heads are expected to advance as development progresses.
 
 Create a new branch head pointing to one of these versions and check it
-out using linkgit:git-checkout[1]:
+out using linkgit:git-switch[1]:
 
 ------------------------------------------------
-$ git checkout -b new v2.6.13
+$ git switch -c new v2.6.13
 ------------------------------------------------
 
 The working directory then reflects the contents that the project had
@@ -282,10 +282,10 @@ a summary of the commands:
 	this command will fail with a warning.
 `git branch -D <branch>`::
 	delete the branch `<branch>` irrespective of its merged status.
-`git checkout <branch>`::
+`git switch <branch>`::
 	make the current branch `<branch>`, updating the working
 	directory to reflect the version referenced by `<branch>`.
-`git checkout -b <new> <start-point>`::
+`git switch -c <new> <start-point>`::
 	create a new branch `<new>` referencing `<start-point>`, and
 	check it out.
 
@@ -302,22 +302,22 @@ ref: refs/heads/master
 Examining an old version without creating a new branch
 ------------------------------------------------------
 
-The `git checkout` command normally expects a branch head, but will also
-accept an arbitrary commit; for example, you can check out the commit
-referenced by a tag:
+The `git switch` command normally expects a branch head, but will also
+accept an arbitrary commit when invoked with --detach; for example,
+you can check out the commit referenced by a tag:
 
 ------------------------------------------------
-$ git checkout v2.6.17
+$ git switch --detach v2.6.17
 Note: checking out 'v2.6.17'.
 
 You are in 'detached HEAD' state. You can look around, make experimental
 changes and commit them, and you can discard any commits you make in this
-state without impacting any branches by performing another checkout.
+state without impacting any branches by performing another switch.
 
 If you want to create a new branch to retain commits you create, you may
-do so (now or later) by using -b with the checkout command again. Example:
+do so (now or later) by using -c with the switch command again. Example:
 
-  git checkout -b new_branch_name
+  git switch -c new_branch_name
 
 HEAD is now at 427abfa Linux v2.6.17
 ------------------------------------------------
@@ -373,7 +373,7 @@ You might want to build on one of these remote-tracking branches
 on a branch of your own, just as you would for a tag:
 
 ------------------------------------------------
-$ git checkout -b my-todo-copy origin/todo
+$ git switch -c my-todo-copy origin/todo
 ------------------------------------------------
 
 You can also check out `origin/todo` directly to examine it or
@@ -2211,8 +2211,8 @@ $ git branch --track release origin/master
 These can be easily kept up to date using linkgit:git-pull[1].
 
 -------------------------------------------------
-$ git checkout test && git pull
-$ git checkout release && git pull
+$ git switch test && git pull
+$ git switch release && git pull
 -------------------------------------------------
 
 Important note!  If you have any local changes in these branches, then
@@ -2264,7 +2264,7 @@ tested changes
 2) help future bug hunters that use `git bisect` to find problems
 
 -------------------------------------------------
-$ git checkout -b speed-up-spinlocks v2.6.35
+$ git switch -c speed-up-spinlocks v2.6.35
 -------------------------------------------------
 
 Now you apply the patch(es), run some tests, and commit the change(s).  If
@@ -2279,7 +2279,7 @@ When you are happy with the state of this change, you can merge it into the
 "test" branch in preparation to make it public:
 
 -------------------------------------------------
-$ git checkout test && git merge speed-up-spinlocks
+$ git switch test && git merge speed-up-spinlocks
 -------------------------------------------------
 
 It is unlikely that you would have any conflicts here ... but you might if you
@@ -2291,7 +2291,7 @@ see the value of keeping each patch (or patch series) in its own branch.  It
 means that the patches can be moved into the `release` tree in any order.
 
 -------------------------------------------------
-$ git checkout release && git merge speed-up-spinlocks
+$ git switch release && git merge speed-up-spinlocks
 -------------------------------------------------
 
 After a while, you will have a number of branches, and despite the
@@ -2512,7 +2512,7 @@ Suppose that you create a branch `mywork` on a remote-tracking branch
 `origin`, and create some commits on top of it:
 
 -------------------------------------------------
-$ git checkout -b mywork origin
+$ git switch -c mywork origin
 $ vi file.txt
 $ git commit
 $ vi otherfile.txt
@@ -2552,7 +2552,7 @@ commits without any merges, you may instead choose to use
 linkgit:git-rebase[1]:
 
 -------------------------------------------------
-$ git checkout mywork
+$ git switch mywork
 $ git rebase origin
 -------------------------------------------------
 
@@ -3668,13 +3668,13 @@ change within the submodule, and then update the superproject to reference the
 new commit:
 
 -------------------------------------------------
-$ git checkout master
+$ git switch master
 -------------------------------------------------
 
 or
 
 -------------------------------------------------
-$ git checkout -b fix-up
+$ git switch -c fix-up
 -------------------------------------------------
 
 then
@@ -4194,7 +4194,7 @@ start.
 A good place to start is with the contents of the initial commit, with:
 
 ----------------------------------------------------
-$ git checkout e83c5163
+$ git switch --detach e83c5163
 ----------------------------------------------------
 
 The initial revision lays the foundation for almost everything Git has
@@ -4437,10 +4437,10 @@ Managing branches
 -----------------
 
 -----------------------------------------------
-$ git branch	     # list all local branches in this repo
-$ git checkout test  # switch working directory to branch "test"
-$ git branch new     # create branch "new" starting at current HEAD
-$ git branch -d new  # delete branch "new"
+$ git branch			# list all local branches in this repo
+$ git switch test	        # switch working directory to branch "test"
+$ git branch new		# create branch "new" starting at current HEAD
+$ git branch -d new		# delete branch "new"
 -----------------------------------------------
 
 Instead of basing a new branch on current HEAD (the default), use:
@@ -4456,7 +4456,7 @@ $ git branch new test~10 # ten commits before tip of branch "test"
 Create and switch to a new branch at the same time:
 
 -----------------------------------------------
-$ git checkout -b new v2.6.15
+$ git switch -c new v2.6.15
 -----------------------------------------------
 
 Update and examine branches from the repository you cloned from:
@@ -4467,7 +4467,7 @@ $ git branch -r		# list
   origin/master
   origin/next
   ...
-$ git checkout -b masterwork origin/master
+$ git switch -c masterwork origin/master
 -----------------------------------------------
 
 Fetch a branch from a different repository, and give it a new
diff --git a/advice.c b/advice.c
index b224825637..27e39e6514 100644
--- a/advice.c
+++ b/advice.c
@@ -191,20 +191,20 @@ void NORETURN die_conclude_merge(void)
 void detach_advice(const char *new_name)
 {
 	const char *fmt =
-	_("Note: checking out '%s'.\n"
+	_("Note: switching to '%s'.\n"
 	"\n"
 	"You are in 'detached HEAD' state. You can look around, make experimental\n"
 	"changes and commit them, and you can discard any commits you make in this\n"
-	"state without impacting any branches by performing another checkout.\n"
+	"state without impacting any branches by switching back to a branch.\n"
 	"\n"
 	"If you want to create a new branch to retain commits you create, you may\n"
-	"do so (now or later) by using -b with the checkout command again. Example:\n"
+	"do so (now or later) by using -c with the switch command. Example:\n"
 	"\n"
-	"  git checkout -b <new-branch-name>\n"
+	"  git switch -c <new-branch-name>\n"
 	"\n"
 	"Or undo this checkout with:\n"
 	"\n"
-	"  git checkout -\n"
+	"  git switch -\n"
 	"\n"
 	"Turn off this advice by setting config variable advice.detachedHead to false\n\n");
 
diff --git a/sha1-name.c b/sha1-name.c
index 6dda2c16df..da0518c8e3 100644
--- a/sha1-name.c
+++ b/sha1-name.c
@@ -743,7 +743,7 @@ static int get_oid_basic(const char *str, int len, struct object_id *oid,
 	"because it will be ignored when you just specify 40-hex. These refs\n"
 	"may be created by mistake. For example,\n"
 	"\n"
-	"  git checkout -b $br $(git rev-parse ...)\n"
+	"  git switch -c $br $(git rev-parse ...)\n"
 	"\n"
 	"where \"$br\" is somehow empty and a 40-hex ref is created. Please\n"
 	"examine these refs and maybe delete them. Turn this message off by\n"
diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
index 743c7248a2..da6809b48f 100755
--- a/t/t2020-checkout-detach.sh
+++ b/t/t2020-checkout-detach.sh
@@ -195,20 +195,20 @@ test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not as
 
 	# The first detach operation is more chatty than the following ones.
 	cat >1st_detach <<-EOF &&
-	Note: checking out 'HEAD^'.
+	Note: switching to 'HEAD^'.
 
 	You are in 'detached HEAD' state. You can look around, make experimental
 	changes and commit them, and you can discard any commits you make in this
-	state without impacting any branches by performing another checkout.
+	state without impacting any branches by switching back to a branch.
 
 	If you want to create a new branch to retain commits you create, you may
-	do so (now or later) by using -b with the checkout command again. Example:
+	do so (now or later) by using -c with the switch command. Example:
 
-	  git checkout -b <new-branch-name>
+	  git switch -c <new-branch-name>
 
 	Or undo this checkout with:
 
-	  git checkout -
+	  git switch -
 
 	Turn off this advice by setting config variable advice.detachedHead to false
 
@@ -277,20 +277,20 @@ test_expect_success 'describe_detached_head does print SHA-1 ellipsis when asked
 
 	# The first detach operation is more chatty than the following ones.
 	cat >1st_detach <<-EOF &&
-	Note: checking out 'HEAD^'.
+	Note: switching to 'HEAD^'.
 
 	You are in 'detached HEAD' state. You can look around, make experimental
 	changes and commit them, and you can discard any commits you make in this
-	state without impacting any branches by performing another checkout.
+	state without impacting any branches by switching back to a branch.
 
 	If you want to create a new branch to retain commits you create, you may
-	do so (now or later) by using -b with the checkout command again. Example:
+	do so (now or later) by using -c with the switch command. Example:
 
-	  git checkout -b <new-branch-name>
+	  git switch -c <new-branch-name>
 
 	Or undo this checkout with:
 
-	  git checkout -
+	  git switch -
 
 	Turn off this advice by setting config variable advice.detachedHead to false
 
-- 
2.21.0.548.gd3c7d92dc2
Andrei Rybak· Mar 22, 2019, 08:00 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v5 26/26] doc: promote "git switch"

On 3/21/19 2:16 PM, Nguyễn Thái Ngọc Duy wrote:
Show 26 quoted lines
> ...
>
> diff --git a/advice.c b/advice.c
> index b224825637..27e39e6514 100644
> --- a/advice.c
> +++ b/advice.c
> @@ -191,20 +191,20 @@ void NORETURN die_conclude_merge(void)
>  void detach_advice(const char *new_name)
>  {
>  	const char *fmt =
> -	_("Note: checking out '%s'.\n"
> +	_("Note: switching to '%s'.\n"
>  	"\n"
>  	"You are in 'detached HEAD' state. You can look around, make experimental\n"
>  	"changes and commit them, and you can discard any commits you make in this\n"
> -	"state without impacting any branches by performing another checkout.\n"
> +	"state without impacting any branches by switching back to a branch.\n"
>  	"\n"
>  	"If you want to create a new branch to retain commits you create, you may\n"
> -	"do so (now or later) by using -b with the checkout command again. Example:\n"
> +	"do so (now or later) by using -c with the switch command. Example:\n"
>  	"\n"
> -	"  git checkout -b <new-branch-name>\n"
> +	"  git switch -c <new-branch-name>\n"
>  	"\n"
>  	"Or undo this checkout with:\n"

With the start of the message being "switching to ..." this part could probably be also updated to something like "Or undo this switch with" or "Or undo this switch or checkout with".

Show 6 quoted lines
>  	"\n"
> -	"  git checkout -\n"
> +	"  git switch -\n"
>  	"\n"
>  	"Turn off this advice by setting config variable advice.detachedHead to false\n\n");
>  
Duy Nguyen· Mar 22, 2019, 09:12 UTC · re: Andrei Rybak · lore

Re: [PATCH v5 26/26] doc: promote "git switch"

On Fri, Mar 22, 2019 at 3:00 PM Andrei Rybak <rybak.a.v@gmail.com> wrote:
Show 32 quoted lines
>
> On 3/21/19 2:16 PM, Nguyễn Thái Ngọc Duy wrote:
> > ...
> >
> > diff --git a/advice.c b/advice.c
> > index b224825637..27e39e6514 100644
> > --- a/advice.c
> > +++ b/advice.c
> > @@ -191,20 +191,20 @@ void NORETURN die_conclude_merge(void)
> >  void detach_advice(const char *new_name)
> >  {
> >       const char *fmt =
> > -     _("Note: checking out '%s'.\n"
> > +     _("Note: switching to '%s'.\n"
> >       "\n"
> >       "You are in 'detached HEAD' state. You can look around, make experimental\n"
> >       "changes and commit them, and you can discard any commits you make in this\n"
> > -     "state without impacting any branches by performing another checkout.\n"
> > +     "state without impacting any branches by switching back to a branch.\n"
> >       "\n"
> >       "If you want to create a new branch to retain commits you create, you may\n"
> > -     "do so (now or later) by using -b with the checkout command again. Example:\n"
> > +     "do so (now or later) by using -c with the switch command. Example:\n"
> >       "\n"
> > -     "  git checkout -b <new-branch-name>\n"
> > +     "  git switch -c <new-branch-name>\n"
> >       "\n"
> >       "Or undo this checkout with:\n"
>
> With the start of the message being "switching to ..." this part could probably
> be also updated to something like "Or undo this switch with" or "Or undo this
> switch or checkout with".
Maybe the neutral "operation"? E.g. "Or undo this operation with:"
Show 8 quoted lines
>
> >       "\n"
> > -     "  git checkout -\n"
> > +     "  git switch -\n"
> >       "\n"
> >       "Turn off this advice by setting config variable advice.detachedHead to false\n\n");
> >
>
-- 
Duy
Elijah Newren· Mar 21, 2019, 13:32 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v5 00/26] Add new command 'switch'

On Thu, Mar 21, 2019 at 6:17 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 6 quoted lines
>
> This adds a new command 'git-switch' as the half-replacement for
> 'git-checkout'. Jump to 12/26 as the starting point. The other half is
> git-restore, which is dealt with separately.
>
> The diff delta is shrinking nicely. The two main changes are

I only looked over the range-diff this time, but this round looks good to me. Thanks for working on this.

Junio C Hamano· Mar 22, 2019, 04:26 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v5 00/26] Add new command 'switch'

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
Show 10 quoted lines
> This adds a new command 'git-switch' as the half-replacement for
> 'git-checkout'. Jump to 12/26 as the starting point. The other half is
> git-restore, which is dealt with separately.
>
> The diff delta is shrinking nicely. The two main changes are
>
> - '--orphan <new-branch> <initial-tree>' is no longer accepted
> - --force implies --ignore-in-progress
>
> The rest is the usual typo and bug fixes.
And droppage of sign-off?

Other than that, the changes relative to the previous round looked all reasonable.

Duy Nguyen· Mar 22, 2019, 07:30 UTC · re: Junio C Hamano · lore

Re: [PATCH v5 00/26] Add new command 'switch'

On Fri, Mar 22, 2019 at 11:26 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 15 quoted lines
>
> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>
> > This adds a new command 'git-switch' as the half-replacement for
> > 'git-checkout'. Jump to 12/26 as the starting point. The other half is
> > git-restore, which is dealt with separately.
> >
> > The diff delta is shrinking nicely. The two main changes are
> >
> > - '--orphan <new-branch> <initial-tree>' is no longer accepted
> > - --force implies --ignore-in-progress
> >
> > The rest is the usual typo and bug fixes.
>
> And droppage of sign-off?

I think the amount of options I have to give to format-patch starts to overwhelm me. Can you forge my sign-off? I'll resend otherwise.

-- 
Duy
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:38 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 00/27] Add new command 'switch'

This adds a new command 'git-switch' as the half-replacement for 'git-checkout'. Jump to 13/27 as the starting point. The other half is git-restore, which is dealt with separately.

The sixth time must be the charm, because the seventh time may be the curse!

The last big change since v5 is from the discussion about remove_branch_state() and its relation with --ignore-in-progress. By popular demand, --ignore-in-progress is gone. The discussion is linked in 23/27, which is also improved to suggest a way out.

That discussion also leads to a new patch, 07/27, which improves 'git checkout' and tells the user when merge/revert/cherry-pick is canceled.

The rest is just minor updates here and there. And I made sure the sign-off is back this time :D

Nguyễn Thái Ngọc Duy (27):
  git-checkout.txt: spell out --no-option
  git-checkout.txt: fix one syntax line
  doc: document --overwrite-ignore
  git-checkout.txt: fix monospace typeset
  t: rename t2014-switch.sh to t2014-checkout-switch.sh
  checkout: advice how to get out of detached HEAD mode
  checkout: inform the user when removing branch state
  checkout: keep most #include sorted
  checkout: factor out some code in parse_branchname_arg()
  checkout: make "opts" in cmd_checkout() a pointer
  checkout: move 'confict_style' and 'dwim_..' to checkout_opts
  checkout: split options[] array in three pieces
  checkout: split part of it to new command 'switch'
  switch: better names for -b and -B
  switch: add --discard-changes
  switch: remove -l
  switch: stop accepting pathspec
  switch: reject "do nothing" case
  switch: only allow explicit detached HEAD
  switch: add short option for --detach
  switch: implicit dwim, use --no-guess to disable it
  switch: no worktree status unless real branch switch happens
  switch: reject if some operation is in progress
  switch: make --orphan switch to an empty tree
  t: add tests for switch
  completion: support switch
  doc: promote "git switch"
 .gitignore                                    |   1 +
 Documentation/config/advice.txt               |  13 +-
 Documentation/config/branch.txt               |   4 +-
 Documentation/config/checkout.txt             |  17 +-
 Documentation/config/diff.txt                 |   3 +-
 Documentation/git-branch.txt                  |  12 +-
 Documentation/git-check-ref-format.txt        |   3 +-
 Documentation/git-checkout.txt                | 221 ++++---
 Documentation/git-format-patch.txt            |   2 +-
 Documentation/git-merge-base.txt              |   2 +-
 Documentation/git-merge.txt                   |   5 +
 Documentation/git-rebase.txt                  |   2 +-
 Documentation/git-remote.txt                  |   2 +-
 Documentation/git-rerere.txt                  |  10 +-
 Documentation/git-reset.txt                   |  20 +-
 Documentation/git-stash.txt                   |   9 +-
 Documentation/git-switch.txt (new)            | 271 ++++++++
 Documentation/gitattributes.txt               |   3 +-
 Documentation/gitcore-tutorial.txt            |  19 +-
 Documentation/giteveryday.txt                 |  24 +-
 Documentation/githooks.txt                    |   8 +-
 Documentation/gittutorial.txt                 |   4 +-
 Documentation/gitworkflows.txt                |   3 +-
 Documentation/revisions.txt                   |   2 +-
 Documentation/user-manual.txt                 |  56 +-
 Makefile                                      |   1 +
 advice.c                                      |  17 +-
 branch.c                                      |  11 +-
 branch.h                                      |   2 +-
 builtin.h                                     |   1 +
 builtin/am.c                                  |   2 +-
 builtin/checkout.c                            | 619 ++++++++++--------
 builtin/rebase.c                              |   4 +-
 builtin/reset.c                               |   2 +-
 builtin/revert.c                              |   2 +-
 command-list.txt                              |   1 +
 contrib/completion/git-completion.bash        |  37 +-
 git.c                                         |   1 +
 parse-options-cb.c                            |  17 +
 parse-options.h                               |   1 +
 sha1-name.c                                   |   2 +-
 t/t1090-sparse-checkout-scope.sh              |  14 -
 ...014-switch.sh => t2014-checkout-switch.sh} |   0
 t/t2020-checkout-detach.sh                    |  28 +-
 t/t2060-switch.sh (new +x)                    |  96 +++
 unpack-trees.c                                |   2 +-
 46 files changed, 1071 insertions(+), 505 deletions(-)
 create mode 100644 Documentation/git-switch.txt
 rename t/{t2014-switch.sh => t2014-checkout-switch.sh} (100%)
 create mode 100755 t/t2060-switch.sh
Range-diff dựa trên v5:
 -:  ---------- >  1:  ddcc6f7684 checkout: inform the user when removing branch state
 1:  0bb2aefb85 =  2:  1d91c3ed35 checkout: keep most #include sorted
 2:  ace82aa26f =  3:  1705d50f4e checkout: factor out some code in parse_branchname_arg()
 3:  6e13efcba8 =  4:  dc07f2fd92 checkout: make "opts" in cmd_checkout() a pointer
 4:  f61a042eb8 =  5:  a8292a7173 checkout: move 'confict_style' and 'dwim_..' to checkout_opts
 5:  972cebc568 =  6:  89b7d4baea checkout: split options[] array in three pieces
 6:  970c727b24 !  7:  0862b680c3 checkout: split part of it to new command 'switch'
    @@ -243,18 +243,14 @@
     +
     +-f::
     +--force::
    -+	An alias for `--discard-changes` and `--ignore-in-progress`.
    ++	An alias for `--discard-changes`.
     +
     +--discard-changes::
     +	Proceed even if the index or the working tree differs from
     +	`HEAD`. Both the index and working tree are restored to match
    -+	the switching target. This is used to throw away local
    -+	changes.
    -+
    -+--ignore-in-progress::
    -+	`git switch` by default refuses when some operation is in
    -+	progress (e.g. "git rebase", "git am" ...). This option
    -+	overrides this safety check and allows switching.
    ++	the switching target. If `--recurse-submodules` is specified,
    ++	submodule content is also restored to match the switching
    ++	target. This is used to throw away local changes.
     +
     +-m::
     +--merge::
    @@ -321,13 +317,12 @@
     +
     +--recurse-submodules::
     +--no-recurse-submodules::
    -+	Using `--recurse-submodules` will update the content of all initialized
    -+	submodules according to the commit recorded in the superproject. If
    -+	local modifications in a submodule would be overwritten the checkout
    -+	will fail unless `-f` is used. If nothing (or `--no-recurse-submodules`)
    -+	is used, the work trees of submodules will not be updated.
    -+	Just like linkgit:git-submodule[1], this will detach `HEAD` of the
    -+	submodule.
    ++	Using `--recurse-submodules` will update the content of all
    ++	initialized submodules according to the commit recorded in the
    ++	superproject. If nothing (or `--no-recurse-submodules`) is
    ++	used, the work trees of submodules will not be updated. Just
    ++	like linkgit:git-submodule[1], this will detach `HEAD` of the
    ++	submodules.
     +
     +EXAMPLES
     +--------
    @@ -385,7 +380,7 @@
     +name:
     +
     +------------
    -+$ git switch -g new-topic
    ++$ git switch new-topic
     +Branch 'new-topic' set up to track remote branch 'new-topic' from 'origin'
     +Switched to a new branch 'new-topic'
     +------------
 7:  676f5df0fd =  8:  52da28d384 switch: better names for -b and -B
 8:  4e37c0659e !  9:  471b0c47ac switch: add --discard-changes
    @@ -7,6 +7,10 @@
         --discard-changes. But it's meant to be an alias for potentially more
         force options in the future.
     
    +    Side note. It's not obvious from the patch but --discard-changes also
    +    affects submodules if --recurse-submodules is used. The knob to force
    +    updating submodules is hidden behind unpack-trees.c
    +
      diff --git a/builtin/checkout.c b/builtin/checkout.c
      --- a/builtin/checkout.c
      +++ b/builtin/checkout.c
 9:  a57208d137 = 10:  bf6d12272c switch: remove -l
10:  3de6f95bf2 = 11:  bec2fb8884 switch: stop accepting pathspec
11:  ad225517cd = 12:  ed0dfccf89 switch: reject "do nothing" case
12:  583cfd5cc4 = 13:  d17eabfb01 switch: only allow explicit detached HEAD
13:  1c5aee658d = 14:  59623757ee switch: add short option for --detach
14:  d942ac52e2 = 15:  04dca884f8 switch: implicit dwim, use --no-guess to disable it
15:  37eb152c0d = 16:  c8ff76def6 switch: no worktree status unless real branch switch happens
16:  50d6768afd ! 17:  34727e7d46 switch: reject if some operation is in progress
    @@ -7,13 +7,20 @@
         forget that you're in the middle of something. By the time you realize,
         you may have done a ton of work and it gets harder to go back.
     
    -    The new option --ignore-in-progress is added to override this check.
    -    However it's probably just safer to create a separate worktree for
    -    that separate thing you want to work on and leave this worktree
    -    alone (unless of course creating or preparing worktrees are not cheap).
    +    A new option --ignore-in-progress was considered but dropped because it
    +    was not exactly clear what should happen. Sometimes you can switch away
    +    and get back safely and resume the operation. Sometimes not. And the
    +    git-checkout behavior is automatically clear merge/revert/cherry-pick,
    +    which makes it a bit even more confusing [1].
     
    -    --force is updated to also imply --ignore-in-progress because it is
    -    supposed to be the "just do your things and don't bother me" option.
    +    We may revisit and add this option in the future. But for now play it
    +    safe and not allow it (you can't even skip this check with --force). The
    +    user is suggested to cancel the operation by themselves (and hopefully
    +    they do consider the consequences, not blindly type the command), or to
    +    create a separate worktree instead of switching. The third option is
    +    the good old "git checkout", but it's not mentioned.
    +
    +    [1] CACsJy8Axa5WsLSjiscjnxVK6jQHkfs-gH959=YtUvQkWriAk5w@mail.gmail.com
     
      diff --git a/builtin/checkout.c b/builtin/checkout.c
      --- a/builtin/checkout.c
    @@ -35,44 +42,55 @@
      	const char *new_branch;
      	const char *new_branch_force;
     @@
    - 	    !new_branch_info->path)
    - 		die_expecting_a_branch(new_branch_info);
    + 	die(_("a branch is expected, got '%s'"), branch_info->name);
    + }
      
    -+	if (!opts->can_switch_when_in_progress) {
    -+		struct wt_status_state state;
    ++static void die_if_some_operation_in_progress(void)
    ++{
    ++	struct wt_status_state state;
     +
    -+		memset(&state, 0, sizeof(state));
    -+		wt_status_get_state(the_repository, &state, 0);
    ++	memset(&state, 0, sizeof(state));
    ++	wt_status_get_state(the_repository, &state, 0);
     +
    -+		if (state.merge_in_progress)
    -+			die(_("cannot switch branch while merging"));
    -+		if (state.am_in_progress)
    -+			die(_("cannot switch branch in the middle of an am session"));
    -+		if (state.rebase_interactive_in_progress || state.rebase_in_progress)
    -+			die(_("cannot switch branch while rebasing"));
    -+		if (state.cherry_pick_in_progress)
    -+			die(_("cannot switch branch while cherry-picking"));
    -+		if (state.revert_in_progress)
    -+			die(_("cannot switch branch while reverting"));
    -+		if (state.bisect_in_progress)
    -+			die(_("cannot switch branch while bisecting"));
    -+	}
    ++	if (state.merge_in_progress)
    ++		die(_("cannot switch branch while merging\n"
    ++		      "Consider \"git merge --quit\" "
    ++		      "or \"git worktree add\"."));
    ++	if (state.am_in_progress)
    ++		die(_("cannot switch branch in the middle of an am session\n"
    ++		      "Consider \"git am --quit\" "
    ++		      "or \"git worktree add\"."));
    ++	if (state.rebase_interactive_in_progress || state.rebase_in_progress)
    ++		die(_("cannot switch branch while rebasing\n"
    ++		      "Consider \"git rebase --quit\" "
    ++		      "or \"git worktree add\"."));
    ++	if (state.cherry_pick_in_progress)
    ++		die(_("cannot switch branch while cherry-picking\n"
    ++		      "Consider \"git cherry-pick --quit\" "
    ++		      "or \"git worktree add\"."));
    ++	if (state.revert_in_progress)
    ++		die(_("cannot switch branch while reverting\n"
    ++		      "Consider \"git revert --quit\" "
    ++		      "or \"git worktree add\"."));
    ++	if (state.bisect_in_progress)
    ++		die(_("cannot switch branch while bisecting\n"
    ++		      "Consider \"git bisect reset HEAD\" "
    ++		      "or \"git worktree add\"."));
    ++}
    ++
    + static int checkout_branch(struct checkout_opts *opts,
    + 			   struct branch_info *new_branch_info)
    + {
    +@@
    + 	    !new_branch_info->path)
    + 		die_expecting_a_branch(new_branch_info);
    + 
    ++	if (!opts->can_switch_when_in_progress)
    ++		die_if_some_operation_in_progress();
     +
      	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
      	    !opts->ignore_other_worktrees) {
      		int flag;
    -@@
    - 		opts->merge = 1; /* implied */
    - 		git_xmerge_config("merge.conflictstyle", opts->conflict_style, NULL);
    - 	}
    --	if (opts->force)
    -+	if (opts->force) {
    - 		opts->discard_changes = 1;
    -+		opts->can_switch_when_in_progress = 1;
    -+	}
    - 
    - 	if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
    - 		die(_("-b, -B and --orphan are mutually exclusive"));
     @@
      	opts.only_merge_on_switching_branches = 0;
      	opts.accept_pathspec = 1;
    @@ -81,15 +99,6 @@
      
      	options = parse_options_dup(checkout_options);
      	options = add_common_options(&opts, options);
    -@@
    - 			 N_("second guess 'git switch <no-such-branch>'")),
    - 		OPT_BOOL(0, "discard-changes", &opts.discard_changes,
    - 			 N_("throw away local modifications")),
    -+		OPT_BOOL(0, "ignore-in-progress", &opts.can_switch_when_in_progress,
    -+			 N_("allow to switch branch when some operation is still in progress")),
    - 		OPT_END()
    - 	};
    - 	int ret;
     @@
      	opts.switch_branch_doing_nothing_is_ok = 0;
      	opts.only_merge_on_switching_branches = 1;
17:  af8bb710c8 = 18:  6e1986f0d4 switch: make --orphan switch to an empty tree
18:  4a293a3d53 ! 19:  414a1783fb t: add tests for switch
    @@ -95,13 +95,11 @@
     +	test_cmp expected actual
     +'
     +
    -+test_expect_success 'switching when something is in progress' '
    ++test_expect_success 'not switching when something is in progress' '
     +	test_when_finished rm -f .git/MERGE_HEAD &&
     +	# fake a merge-in-progress
     +	cp .git/HEAD .git/MERGE_HEAD &&
    -+	test_must_fail git switch -d @^ &&
    -+	git switch --ignore-in-progress -d @^ &&
    -+	git switch -
    ++	test_must_fail git switch -d @^
     +'
     +
     +test_done
19:  b10290b378 = 20:  ac6f5fa9f9 completion: support switch
20:  67c5175e5f ! 21:  5a921aa0a1 doc: promote "git switch"
    @@ -9,6 +9,9 @@
         For these reasons, promote it everywhere possible. This includes
         documentation, suggestions/advice from other commands...
     
    +    The "Checking out files" progress line in unpack-trees.c is also updated
    +    to "Updating files" to be neutral to both git-checkout and git-switch.
    +
      diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
      --- a/Documentation/git-branch.txt
      +++ b/Documentation/git-branch.txt
    @@ -662,7 +665,8 @@
     -	"  git checkout -b <new-branch-name>\n"
     +	"  git switch -c <new-branch-name>\n"
      	"\n"
    - 	"Or undo this checkout with:\n"
    +-	"Or undo this checkout with:\n"
    ++	"Or undo this operation with:\n"
      	"\n"
     -	"  git checkout -\n"
     +	"  git switch -\n"
    @@ -705,7 +709,8 @@
     -	  git checkout -b <new-branch-name>
     +	  git switch -c <new-branch-name>
      
    - 	Or undo this checkout with:
    +-	Or undo this checkout with:
    ++	Or undo this operation with:
      
     -	  git checkout -
     +	  git switch -
    @@ -731,10 +736,24 @@
     -	  git checkout -b <new-branch-name>
     +	  git switch -c <new-branch-name>
      
    - 	Or undo this checkout with:
    +-	Or undo this checkout with:
    ++	Or undo this operation with:
      
     -	  git checkout -
     +	  git switch -
      
      	Turn off this advice by setting config variable advice.detachedHead to false
      
    +
    + diff --git a/unpack-trees.c b/unpack-trees.c
    + --- a/unpack-trees.c
    + +++ b/unpack-trees.c
    +@@
    + 			total++;
    + 	}
    + 
    +-	return start_delayed_progress(_("Checking out files"), total);
    ++	return start_delayed_progress(_("Updating files"), total);
    + }
    + 
    + static void setup_collided_checkout_detection(struct checkout *state,
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:38 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 01/27] git-checkout.txt: spell out --no-option

It's easier to search for and also less cryptic.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)
Show changes to Documentation/git-checkout.txt +7 −1
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index f179b43732..99c8c0dc0f 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -118,7 +118,8 @@ OPTIONS
 --quiet::
 	Quiet, suppress feedback messages.
 
---[no-]progress::
+--progress::
+--no-progress::
 	Progress status is reported on the standard error stream
 	by default when it is attached to a terminal, unless `--quiet`
 	is specified. This flag enables progress reporting even if not
@@ -262,7 +263,7 @@ edits from your current working tree. See the ``Interactive Mode''
 section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
 +
 Note that this option uses the no overlay mode by default (see also
-`--[no-]overlay`), and currently doesn't support overlay mode.
+`--overlay`), and currently doesn't support overlay mode.
 
 --ignore-other-worktrees::
 	`git checkout` refuses when the wanted ref is already checked
@@ -270,7 +271,8 @@ Note that this option uses the no overlay mode by default (see also
 	out anyway. In other words, the ref can be held by more than one
 	worktree.
 
---[no-]recurse-submodules::
+--recurse-submodules::
+--no-recurse-submodules::
 	Using --recurse-submodules will update the content of all initialized
 	submodules according to the commit recorded in the superproject. If
 	local modifications in a submodule would be overwritten the checkout
@@ -283,7 +285,8 @@ Note that this option uses the no overlay mode by default (see also
 	Do not attempt to create a branch if a remote tracking branch
 	of the same name exists.
 
---[no-]overlay::
+--overlay::
+--no-overlay::
 	In the default overlay mode, `git checkout` never
 	removes files from the index or the working tree.  When
 	specifying `--no-overlay`, files that appear in the index and
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:38 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 02/27] git-checkout.txt: fix one syntax line

<branch> can be omitted in this syntax, and it's actually documented a few paragraphs down:

  You could omit <branch>, in which case the command degenerates to
  "check out the current branch", which is a glorified no-op with
  rather expensive side-effects to show only the tracking information,
  if exists, for the current branch.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to Documentation/git-checkout.txt +1 −1
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 99c8c0dc0f..28817cfa41 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -23,7 +23,7 @@ or the specified tree.  If no paths are given, 'git checkout' will
 also update `HEAD` to set the specified branch as the current
 branch.
 
-'git checkout' <branch>::
+'git checkout' [<branch>]::
 	To prepare for working on <branch>, switch to it by updating
 	the index and the files in the working tree, and by pointing
 	HEAD at the branch. Local modifications to the files in the
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:38 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 03/27] doc: document --overwrite-ignore

I added this option in git-checkout and git-merge in c1d7036b6b (checkout,merge: disallow overwriting ignored files with --no-overwrite-ignore - 2011-11-27) but did not remember to update documentation. This completes that commit.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt | 6 ++++++
 Documentation/git-merge.txt    | 5 +++++
 2 files changed, 11 insertions(+)
Show changes to 2 files +11 −0

Documentation/git-checkout.txt, Documentation/git-merge.txt

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 28817cfa41..5280d1f9ed 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -271,6 +271,12 @@ Note that this option uses the no overlay mode by default (see also
 	out anyway. In other words, the ref can be held by more than one
 	worktree.
 
+--overwrite-ignore::
+--no-overwrite-ignore::
+	Silently overwrite ignored files when switching branches. This
+	is the default behavior. Use `--no-overwrite-ignore` to abort
+	the operation when the new branch contains ignored files.
+
 --recurse-submodules::
 --no-recurse-submodules::
 	Using --recurse-submodules will update the content of all initialized
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index 4cc86469f3..6a9163d8fe 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -87,6 +87,11 @@ will be appended to the specified message.
 	Allow the rerere mechanism to update the index with the
 	result of auto-conflict resolution if possible.
 
+--overwrite-ignore::
+--no-overwrite-ignore::
+	Silently overwrite ignored files from the merge result. This
+	is the default behavior. Use `--no-overwrite-ignore` to abort.
+
 --abort::
 	Abort the current conflict resolution process, and
 	try to reconstruct the pre-merge state.
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:38 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 04/27] git-checkout.txt: fix monospace typeset

Add backticks where we have none, replace single quotes with backticks and replace double-quotes. Drop double-quotes from nested constructions such as `"@{-1}"`.

Helped-by: Martin Ågren <martin.agren@gmail.com>
Signed-off-by: Martin Ågren <martin.agren@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt | 162 ++++++++++++++++-----------------
 1 file changed, 81 insertions(+), 81 deletions(-)
Show changes to Documentation/git-checkout.txt +81 −81
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 5280d1f9ed..bf90966c95 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -24,14 +24,14 @@ also update `HEAD` to set the specified branch as the current
 branch.
 
 'git checkout' [<branch>]::
-	To prepare for working on <branch>, switch to it by updating
+	To prepare for working on `<branch>`, switch to it by updating
 	the index and the files in the working tree, and by pointing
-	HEAD at the branch. Local modifications to the files in the
+	`HEAD` at the branch. Local modifications to the files in the
 	working tree are kept, so that they can be committed to the
-	<branch>.
+	`<branch>`.
 +
-If <branch> is not found but there does exist a tracking branch in
-exactly one remote (call it <remote>) with a matching name, treat as
+If `<branch>` is not found but there does exist a tracking branch in
+exactly one remote (call it `<remote>`) with a matching name, treat as
 equivalent to
 +
 ------------
@@ -47,7 +47,7 @@ branches from there if `<branch>` is ambiguous but exists on the
 'origin' remote. See also `checkout.defaultRemote` in
 linkgit:git-config[1].
 +
-You could omit <branch>, in which case the command degenerates to
+You could omit `<branch>`, in which case the command degenerates to
 "check out the current branch", which is a glorified no-op with
 rather expensive side-effects to show only the tracking information,
 if exists, for the current branch.
@@ -61,7 +61,7 @@ if exists, for the current branch.
 	`--track` without `-b` implies branch creation; see the
 	description of `--track` below.
 +
-If `-B` is given, <new_branch> is created if it doesn't exist; otherwise, it
+If `-B` is given, `<new_branch>` is created if it doesn't exist; otherwise, it
 is reset. This is the transactional equivalent of
 +
 ------------
@@ -75,25 +75,25 @@ successful.
 'git checkout' --detach [<branch>]::
 'git checkout' [--detach] <commit>::
 
-	Prepare to work on top of <commit>, by detaching HEAD at it
+	Prepare to work on top of `<commit>`, by detaching `HEAD` at it
 	(see "DETACHED HEAD" section), and updating the index and the
 	files in the working tree.  Local modifications to the files
 	in the working tree are kept, so that the resulting working
 	tree will be the state recorded in the commit plus the local
 	modifications.
 +
-When the <commit> argument is a branch name, the `--detach` option can
-be used to detach HEAD at the tip of the branch (`git checkout
-<branch>` would check out that branch without detaching HEAD).
+When the `<commit>` argument is a branch name, the `--detach` option can
+be used to detach `HEAD` at the tip of the branch (`git checkout
+<branch>` would check out that branch without detaching `HEAD`).
 +
-Omitting <branch> detaches HEAD at the tip of the current branch.
+Omitting `<branch>` detaches `HEAD` at the tip of the current branch.
 
 'git checkout' [<tree-ish>] [--] <pathspec>...::
 
 	Overwrite paths in the working tree by replacing with the
-	contents in the index or in the <tree-ish> (most often a
-	commit).  When a <tree-ish> is given, the paths that
-	match the <pathspec> are updated both in the index and in
+	contents in the index or in the `<tree-ish>` (most often a
+	commit).  When a `<tree-ish>` is given, the paths that
+	match the `<pathspec>` are updated both in the index and in
 	the working tree.
 +
 The index may contain unmerged entries because of a previous failed merge.
@@ -128,7 +128,7 @@ OPTIONS
 -f::
 --force::
 	When switching branches, proceed even if the index or the
-	working tree differs from HEAD.  This is used to throw away
+	working tree differs from `HEAD`.  This is used to throw away
 	local changes.
 +
 When checking out paths from the index, do not fail upon unmerged
@@ -155,12 +155,12 @@ on your side branch as `theirs` (i.e. "one contributor's work on top
 of it").
 
 -b <new_branch>::
-	Create a new branch named <new_branch> and start it at
-	<start_point>; see linkgit:git-branch[1] for details.
+	Create a new branch named `<new_branch>` and start it at
+	`<start_point>`; see linkgit:git-branch[1] for details.
 
 -B <new_branch>::
-	Creates the branch <new_branch> and start it at <start_point>;
-	if it already exists, then reset it to <start_point>. This is
+	Creates the branch `<new_branch>` and start it at `<start_point>`;
+	if it already exists, then reset it to `<start_point>`. This is
 	equivalent to running "git branch" with "-f"; see
 	linkgit:git-branch[1] for details.
 
@@ -173,15 +173,15 @@ If no `-b` option is given, the name of the new branch will be
 derived from the remote-tracking branch, by looking at the local part of
 the refspec configured for the corresponding remote, and then stripping
 the initial part up to the "*".
-This would tell us to use "hack" as the local branch when branching
-off of "origin/hack" (or "remotes/origin/hack", or even
-"refs/remotes/origin/hack").  If the given name has no slash, or the above
+This would tell us to use `hack` as the local branch when branching
+off of `origin/hack` (or `remotes/origin/hack`, or even
+`refs/remotes/origin/hack`).  If the given name has no slash, or the above
 guessing results in an empty name, the guessing is aborted.  You can
 explicitly give a name with `-b` in such a case.
 
 --no-track::
 	Do not set up "upstream" configuration, even if the
-	branch.autoSetupMerge configuration variable is true.
+	`branch.autoSetupMerge` configuration variable is true.
 
 -l::
 	Create the new branch's reflog; see linkgit:git-branch[1] for
@@ -190,21 +190,21 @@ explicitly give a name with `-b` in such a case.
 --detach::
 	Rather than checking out a branch to work on it, check out a
 	commit for inspection and discardable experiments.
-	This is the default behavior of "git checkout <commit>" when
-	<commit> is not a branch name.  See the "DETACHED HEAD" section
+	This is the default behavior of `git checkout <commit>` when
+	`<commit>` is not a branch name.  See the "DETACHED HEAD" section
 	below for details.
 
 --orphan <new_branch>::
-	Create a new 'orphan' branch, named <new_branch>, started from
-	<start_point> and switch to it.  The first commit made on this
+	Create a new 'orphan' branch, named `<new_branch>`, started from
+	`<start_point>` and switch to it.  The first commit made on this
 	new branch will have no parents and it will be the root of a new
 	history totally disconnected from all the other branches and
 	commits.
 +
 The index and the working tree are adjusted as if you had previously run
-"git checkout <start_point>".  This allows you to start a new history
-that records a set of paths similar to <start_point> by easily running
-"git commit -a" to make the root commit.
+`git checkout <start_point>`.  This allows you to start a new history
+that records a set of paths similar to `<start_point>` by easily running
+`git commit -a` to make the root commit.
 +
 This can be useful when you want to publish the tree from a commit
 without exposing its full history. You might want to do this to publish
@@ -213,17 +213,17 @@ whose full history contains proprietary or otherwise encumbered bits of
 code.
 +
 If you want to start a disconnected history that records a set of paths
-that is totally different from the one of <start_point>, then you should
+that is totally different from the one of `<start_point>`, then you should
 clear the index and the working tree right after creating the orphan
-branch by running "git rm -rf ." from the top level of the working tree.
+branch by running `git rm -rf .` from the top level of the working tree.
 Afterwards you will be ready to prepare your new files, repopulating the
 working tree, by copying them from elsewhere, extracting a tarball, etc.
 
 --ignore-skip-worktree-bits::
 	In sparse checkout mode, `git checkout -- <paths>` would
-	update only entries matched by <paths> and sparse patterns
-	in $GIT_DIR/info/sparse-checkout. This option ignores
-	the sparse patterns and adds back any files in <paths>.
+	update only entries matched by `<paths>` and sparse patterns
+	in `$GIT_DIR/info/sparse-checkout`. This option ignores
+	the sparse patterns and adds back any files in `<paths>`.
 
 -m::
 --merge::
@@ -245,18 +245,18 @@ When checking out paths from the index, this option lets you recreate
 the conflicted merge in the specified paths.
 
 --conflict=<style>::
-	The same as --merge option above, but changes the way the
+	The same as `--merge` option above, but changes the way the
 	conflicting hunks are presented, overriding the
-	merge.conflictStyle configuration variable.  Possible values are
+	`merge.conflictStyle` configuration variable.  Possible values are
 	"merge" (default) and "diff3" (in addition to what is shown by
 	"merge" style, shows the original contents).
 
 -p::
 --patch::
 	Interactively select hunks in the difference between the
-	<tree-ish> (or the index, if unspecified) and the working
+	`<tree-ish>` (or the index, if unspecified) and the working
 	tree.  The chosen hunks are then applied in reverse to the
-	working tree (and if a <tree-ish> was specified, the index).
+	working tree (and if a `<tree-ish>` was specified, the index).
 +
 This means that you can use `git checkout -p` to selectively discard
 edits from your current working tree. See the ``Interactive Mode''
@@ -279,13 +279,13 @@ Note that this option uses the no overlay mode by default (see also
 
 --recurse-submodules::
 --no-recurse-submodules::
-	Using --recurse-submodules will update the content of all initialized
+	Using `--recurse-submodules` will update the content of all initialized
 	submodules according to the commit recorded in the superproject. If
 	local modifications in a submodule would be overwritten the checkout
-	will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
+	will fail unless `-f` is used. If nothing (or `--no-recurse-submodules`)
 	is used, the work trees of submodules will not be updated.
-	Just like linkgit:git-submodule[1], this will detach the
-	submodules HEAD.
+	Just like linkgit:git-submodule[1], this will detach `HEAD` of the
+	submodule.
 
 --no-guess::
 	Do not attempt to create a branch if a remote tracking branch
@@ -296,21 +296,21 @@ Note that this option uses the no overlay mode by default (see also
 	In the default overlay mode, `git checkout` never
 	removes files from the index or the working tree.  When
 	specifying `--no-overlay`, files that appear in the index and
-	working tree, but not in <tree-ish> are removed, to make them
-	match <tree-ish> exactly.
+	working tree, but not in `<tree-ish>` are removed, to make them
+	match `<tree-ish>` exactly.
 
 <branch>::
 	Branch to checkout; if it refers to a branch (i.e., a name that,
 	when prepended with "refs/heads/", is a valid ref), then that
 	branch is checked out. Otherwise, if it refers to a valid
-	commit, your HEAD becomes "detached" and you are no longer on
+	commit, your `HEAD` becomes "detached" and you are no longer on
 	any branch (see below for details).
 +
-You can use the `"@{-N}"` syntax to refer to the N-th last
+You can use the `@{-N}` syntax to refer to the N-th last
 branch/commit checked out using "git checkout" operation. You may
-also specify `-` which is synonymous to `"@{-1}"`.
+also specify `-` which is synonymous to `@{-1}`.
 +
-As a special case, you may use `"A...B"` as a shortcut for the
+As a special case, you may use `A...B` as a shortcut for the
 merge base of `A` and `B` if there is exactly one merge base. You can
 leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 
@@ -319,7 +319,7 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 
 <start_point>::
 	The name of a commit at which to start the new branch; see
-	linkgit:git-branch[1] for details. Defaults to HEAD.
+	linkgit:git-branch[1] for details. Defaults to `HEAD`.
 
 <tree-ish>::
 	Tree to checkout from (when paths are given). If not specified,
@@ -329,9 +329,9 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 
 DETACHED HEAD
 -------------
-HEAD normally refers to a named branch (e.g. 'master'). Meanwhile, each
+`HEAD` normally refers to a named branch (e.g. `master`). Meanwhile, each
 branch refers to a specific commit. Let's look at a repo with three
-commits, one of them tagged, and with branch 'master' checked out:
+commits, one of them tagged, and with branch `master` checked out:
 
 ------------
            HEAD (refers to branch 'master')
@@ -344,10 +344,10 @@ a---b---c  branch 'master' (refers to commit 'c')
 ------------
 
 When a commit is created in this state, the branch is updated to refer to
-the new commit. Specifically, 'git commit' creates a new commit 'd', whose
-parent is commit 'c', and then updates branch 'master' to refer to new
-commit 'd'. HEAD still refers to branch 'master' and so indirectly now refers
-to commit 'd':
+the new commit. Specifically, 'git commit' creates a new commit `d`, whose
+parent is commit `c`, and then updates branch `master` to refer to new
+commit `d`. `HEAD` still refers to branch `master` and so indirectly now refers
+to commit `d`:
 
 ------------
 $ edit; git add; git commit
@@ -364,7 +364,7 @@ a---b---c---d  branch 'master' (refers to commit 'd')
 It is sometimes useful to be able to checkout a commit that is not at
 the tip of any named branch, or even to create a new commit that is not
 referenced by a named branch. Let's look at what happens when we
-checkout commit 'b' (here we show two ways this may be done):
+checkout commit `b` (here we show two ways this may be done):
 
 ------------
 $ git checkout v2.0  # or
@@ -379,9 +379,9 @@ a---b---c---d  branch 'master' (refers to commit 'd')
   tag 'v2.0' (refers to commit 'b')
 ------------
 
-Notice that regardless of which checkout command we use, HEAD now refers
-directly to commit 'b'. This is known as being in detached HEAD state.
-It means simply that HEAD refers to a specific commit, as opposed to
+Notice that regardless of which checkout command we use, `HEAD` now refers
+directly to commit `b`. This is known as being in detached `HEAD` state.
+It means simply that `HEAD` refers to a specific commit, as opposed to
 referring to a named branch. Let's see what happens when we create a commit:
 
 ------------
@@ -398,7 +398,7 @@ a---b---c---d  branch 'master' (refers to commit 'd')
   tag 'v2.0' (refers to commit 'b')
 ------------
 
-There is now a new commit 'e', but it is referenced only by HEAD. We can
+There is now a new commit `e`, but it is referenced only by `HEAD`. We can
 of course add yet another commit in this state:
 
 ------------
@@ -416,7 +416,7 @@ a---b---c---d  branch 'master' (refers to commit 'd')
 ------------
 
 In fact, we can perform all the normal Git operations. But, let's look
-at what happens when we then checkout master:
+at what happens when we then checkout `master`:
 
 ------------
 $ git checkout master
@@ -431,9 +431,9 @@ a---b---c---d  branch 'master' (refers to commit 'd')
 ------------
 
 It is important to realize that at this point nothing refers to commit
-'f'. Eventually commit 'f' (and by extension commit 'e') will be deleted
+`f`. Eventually commit `f` (and by extension commit `e`) will be deleted
 by the routine Git garbage collection process, unless we create a reference
-before that happens. If we have not yet moved away from commit 'f',
+before that happens. If we have not yet moved away from commit `f`,
 any of these will create a reference to it:
 
 ------------
@@ -442,19 +442,19 @@ $ git branch foo        <2>
 $ git tag foo           <3>
 ------------
 
-<1> creates a new branch 'foo', which refers to commit 'f', and then
-    updates HEAD to refer to branch 'foo'. In other words, we'll no longer
-    be in detached HEAD state after this command.
+<1> creates a new branch `foo`, which refers to commit `f`, and then
+    updates `HEAD` to refer to branch `foo`. In other words, we'll no longer
+    be in detached `HEAD` state after this command.
 
-<2> similarly creates a new branch 'foo', which refers to commit 'f',
-    but leaves HEAD detached.
+<2> similarly creates a new branch `foo`, which refers to commit `f`,
+    but leaves `HEAD` detached.
 
-<3> creates a new tag 'foo', which refers to commit 'f',
-    leaving HEAD detached.
+<3> creates a new tag `foo`, which refers to commit `f`,
+    leaving `HEAD` detached.
 
-If we have moved away from commit 'f', then we must first recover its object
+If we have moved away from commit `f`, then we must first recover its object
 name (typically by using git reflog), and then we can create a reference to
-it. For example, to see the last two commits to which HEAD referred, we
+it. For example, to see the last two commits to which `HEAD` referred, we
 can use either of these commands:
 
 ------------
@@ -465,12 +465,12 @@ $ git log -g -2 HEAD
 ARGUMENT DISAMBIGUATION
 -----------------------
 
-When there is only one argument given and it is not `--` (e.g. "git
-checkout abc"), and when the argument is both a valid `<tree-ish>`
-(e.g. a branch "abc" exists) and a valid `<pathspec>` (e.g. a file
+When there is only one argument given and it is not `--` (e.g. `git
+checkout abc`), and when the argument is both a valid `<tree-ish>`
+(e.g. a branch `abc` exists) and a valid `<pathspec>` (e.g. a file
 or a directory whose name is "abc" exists), Git would usually ask
 you to disambiguate.  Because checking out a branch is so common an
-operation, however, "git checkout abc" takes "abc" as a `<tree-ish>`
+operation, however, `git checkout abc` takes "abc" as a `<tree-ish>`
 in such a situation.  Use `git checkout -- <pathspec>` if you want
 to checkout these paths out of the index.
 
@@ -478,7 +478,7 @@ EXAMPLES
 --------
 
 . The following sequence checks out the `master` branch, reverts
-  the `Makefile` to two revisions back, deletes hello.c by
+  the `Makefile` to two revisions back, deletes `hello.c` by
   mistake, and gets it back from the index.
 +
 ------------
@@ -490,7 +490,7 @@ $ git checkout hello.c            <3>
 +
 <1> switch branch
 <2> take a file out of another commit
-<3> restore hello.c from the index
+<3> restore `hello.c` from the index
 +
 If you want to check out _all_ C source files out of the index,
 you can say
@@ -519,7 +519,7 @@ $ git checkout -- hello.c
 $ git checkout mytopic
 ------------
 +
-However, your "wrong" branch and correct "mytopic" branch may
+However, your "wrong" branch and correct `mytopic` branch may
 differ in files that you have modified locally, in which case
 the above checkout would fail like this:
 +
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:38 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 05/27] t: rename t2014-switch.sh to t2014-checkout-switch.sh

The old name does not really say that this is about 'checkout -b'. See 49d833dc07 (Revert "checkout branch: prime cache-tree fully" - 2009-05-12) for more information

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 t/{t2014-switch.sh => t2014-checkout-switch.sh} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
Show changes to t/t2014-switch.sh +0 −0
diff --git a/t/t2014-switch.sh b/t/t2014-checkout-switch.sh
similarity index 100%
rename from t/t2014-switch.sh
rename to t/t2014-checkout-switch.sh
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:38 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 06/27] checkout: advice how to get out of detached HEAD mode

Detached HEAD mode is considered dangerous and confusing for newcomers and we print a big block of warning how to move forward. But we should also suggest the user the way to get out of it if they get into detached HEAD by mistake.

While at there, I also suggest how to turn the advice off. This is another thing I find annoying with advices and should be dealt with in a more generic way. But that may require some refactoring in advice.c first.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 advice.c                   | 17 +++++++++++++----
 t/t2020-checkout-detach.sh | 12 ++++++++++++
 2 files changed, 25 insertions(+), 4 deletions(-)
Show changes to 2 files +25 −4

advice.c, t/t2020-checkout-detach.sh

diff --git a/advice.c b/advice.c
index 567209aa79..b224825637 100644
--- a/advice.c
+++ b/advice.c
@@ -191,13 +191,22 @@ void NORETURN die_conclude_merge(void)
 void detach_advice(const char *new_name)
 {
 	const char *fmt =
-	_("Note: checking out '%s'.\n\n"
+	_("Note: checking out '%s'.\n"
+	"\n"
 	"You are in 'detached HEAD' state. You can look around, make experimental\n"
 	"changes and commit them, and you can discard any commits you make in this\n"
-	"state without impacting any branches by performing another checkout.\n\n"
+	"state without impacting any branches by performing another checkout.\n"
+	"\n"
 	"If you want to create a new branch to retain commits you create, you may\n"
-	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
-	"  git checkout -b <new-branch-name>\n\n");
+	"do so (now or later) by using -b with the checkout command again. Example:\n"
+	"\n"
+	"  git checkout -b <new-branch-name>\n"
+	"\n"
+	"Or undo this checkout with:\n"
+	"\n"
+	"  git checkout -\n"
+	"\n"
+	"Turn off this advice by setting config variable advice.detachedHead to false\n\n");
 
 	fprintf(stderr, fmt, new_name);
 }
diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
index 1fa670625c..743c7248a2 100755
--- a/t/t2020-checkout-detach.sh
+++ b/t/t2020-checkout-detach.sh
@@ -206,6 +206,12 @@ test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not as
 
 	  git checkout -b <new-branch-name>
 
+	Or undo this checkout with:
+
+	  git checkout -
+
+	Turn off this advice by setting config variable advice.detachedHead to false
+
 	HEAD is now at \$commit three
 	EOF
 
@@ -282,6 +288,12 @@ test_expect_success 'describe_detached_head does print SHA-1 ellipsis when asked
 
 	  git checkout -b <new-branch-name>
 
+	Or undo this checkout with:
+
+	  git checkout -
+
+	Turn off this advice by setting config variable advice.detachedHead to false
+
 	HEAD is now at \$commit... three
 	EOF
 
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:38 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 07/27] checkout: inform the user when removing branch state

After a successful switch, if a merge, cherry-pick or revert is ongoing, it is canceled. This behavior has been with us from the very early beginning, soon after git-merge was created but never actually documented [1]. It may be a good idea to be transparent and tell the user if some operation is canceled.

I consider this a better way of telling the user than just adding a sentence or two in git-checkout.txt, which will be mostly ignored anyway.

PS. Originally I wanted to print more details like
    warning: cancelling an in-progress merge from <SHA-1>

which may allow some level of undo if the user wants to. But that seems a lot more work. Perhaps it can be improved later if people still want that.

[1] ... and I will try not to argue whether it is a sensible behavior. There is some more discussion here if people are interested: CACsJy8Axa5WsLSjiscjnxVK6jQHkfs-gH959=YtUvQkWriAk5w@mail.gmail.com

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 branch.c           | 11 +++++++----
 branch.h           |  2 +-
 builtin/am.c       |  2 +-
 builtin/checkout.c |  2 +-
 builtin/rebase.c   |  4 ++--
 builtin/reset.c    |  2 +-
 builtin/revert.c   |  2 +-
 7 files changed, 14 insertions(+), 11 deletions(-)
Show changes to 7 files +14 −11

branch.c, branch.h, builtin/am.c, builtin/checkout.c, builtin/rebase.c, builtin/reset.c, builtin/revert.c

diff --git a/branch.c b/branch.c
index 28b81a7e02..8dd5bb9f1c 100644
--- a/branch.c
+++ b/branch.c
@@ -337,11 +337,14 @@ void create_branch(struct repository *r,
 	free(real_ref);
 }
 
-void remove_branch_state(struct repository *r)
+void remove_branch_state(struct repository *r, int verbose)
 {
-	unlink(git_path_cherry_pick_head(r));
-	unlink(git_path_revert_head(r));
-	unlink(git_path_merge_head(r));
+	if (!unlink(git_path_cherry_pick_head(r)) && verbose)
+		warning(_("cancelling a cherry picking in progress"));
+	if (!unlink(git_path_revert_head(r)) && verbose)
+		warning(_("cancelling a revert in progress"));
+	if (!unlink(git_path_merge_head(r)) && verbose)
+		warning(_("cancelling a merge in progress"));
 	unlink(git_path_merge_rr(r));
 	unlink(git_path_merge_msg(r));
 	unlink(git_path_merge_mode(r));
diff --git a/branch.h b/branch.h
index 29c1afa4d0..aed045901e 100644
--- a/branch.h
+++ b/branch.h
@@ -64,7 +64,7 @@ extern int validate_new_branchname(const char *name, struct strbuf *ref, int for
  * Remove information about the state of working on the current
  * branch. (E.g., MERGE_HEAD)
  */
-void remove_branch_state(struct repository *r);
+void remove_branch_state(struct repository *r, int verbose);
 
 /*
  * Configure local branch "local" as downstream to branch "remote"
diff --git a/builtin/am.c b/builtin/am.c
index 4fb107a9d1..99b66508fd 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1957,7 +1957,7 @@ static int clean_index(const struct object_id *head, const struct object_id *rem
 	if (merge_tree(remote_tree))
 		return -1;
 
-	remove_branch_state(the_repository);
+	remove_branch_state(the_repository, 0);
 
 	return 0;
 }
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 0e6037b296..f66bd2f56d 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -899,7 +899,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 				delete_reflog(old_branch_info->path);
 		}
 	}
-	remove_branch_state(the_repository);
+	remove_branch_state(the_repository, !opts->quiet);
 	strbuf_release(&msg);
 	if (!opts->quiet &&
 	    (new_branch_info->path || (!opts->force_detach && !strcmp(new_branch_info->name, "HEAD"))))
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 52114cbf0d..646d0f9fb1 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1272,7 +1272,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		if (reset_head(NULL, "reset", NULL, RESET_HEAD_HARD,
 			       NULL, NULL) < 0)
 			die(_("could not discard worktree changes"));
-		remove_branch_state(the_repository);
+		remove_branch_state(the_repository, 0);
 		if (read_basic_state(&options))
 			exit(1);
 		goto run_rebase;
@@ -1292,7 +1292,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			       NULL, NULL) < 0)
 			die(_("could not move back to %s"),
 			    oid_to_hex(&options.orig_head));
-		remove_branch_state(the_repository);
+		remove_branch_state(the_repository, 0);
 		ret = finish_rebase(&options);
 		goto cleanup;
 	}
diff --git a/builtin/reset.c b/builtin/reset.c
index 7882829a95..6d9397c844 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -420,7 +420,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 			print_new_head_line(lookup_commit_reference(the_repository, &oid));
 	}
 	if (!pathspec.nr)
-		remove_branch_state(the_repository);
+		remove_branch_state(the_repository, 0);
 
 	return update_ref_status;
 }
diff --git a/builtin/revert.c b/builtin/revert.c
index a47b53ceaf..ebf2789225 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -196,7 +196,7 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
 	if (cmd == 'q') {
 		int ret = sequencer_remove_state(opts);
 		if (!ret)
-			remove_branch_state(the_repository);
+			remove_branch_state(the_repository, 0);
 		return ret;
 	}
 	if (cmd == 'c')
-- 
2.21.0.479.g47ac719cd3
SZEDER Gábor· Jul 2, 2019, 08:06 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v6 07/27] checkout: inform the user when removing branch state

On Fri, Mar 29, 2019 at 05:38:59PM +0700, Nguyễn Thái Ngọc Duy wrote:
Show 5 quoted lines
> After a successful switch, if a merge, cherry-pick or revert is ongoing,
> it is canceled. This behavior has been with us from the very early
> beginning, soon after git-merge was created but never actually
> documented [1]. It may be a good idea to be transparent and tell the
> user if some operation is canceled.

After this entered 'next' last week, today it greeted me with 167(!) of these warnings... before I even had my breakfast.

Now, my script does a lot of repeated cherry-picks and expects that rerere is able to deal with most of the conflicts, i.e. it does approximately this:

  if ! git cherry-pick $oid >/dev/null 2>&1
  then
      if was_the_conflict_resolved
      then
          echo "using previous conflict resolution"
          git commit --no-edit --cleanup=strip --quiet
      else
          die "uh-oh"
      fi
  fi
That 'git commit' in there always prints:
  warning: cancelling a cherry picking in progress

I don't understand why committing after a cherry-pick is considered "cancelling"... in my view it's finishing it and there should be no warning whatsoever.

Show 140 quoted lines
> I consider this a better way of telling the user than just adding a
> sentence or two in git-checkout.txt, which will be mostly ignored
> anyway.
> 
> PS. Originally I wanted to print more details like
> 
>     warning: cancelling an in-progress merge from <SHA-1>
> 
> which may allow some level of undo if the user wants to. But that seems
> a lot more work. Perhaps it can be improved later if people still want
> that.
> 
> [1] ... and I will try not to argue whether it is a sensible behavior.
> There is some more discussion here if people are interested:
> CACsJy8Axa5WsLSjiscjnxVK6jQHkfs-gH959=YtUvQkWriAk5w@mail.gmail.com
> 
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  branch.c           | 11 +++++++----
>  branch.h           |  2 +-
>  builtin/am.c       |  2 +-
>  builtin/checkout.c |  2 +-
>  builtin/rebase.c   |  4 ++--
>  builtin/reset.c    |  2 +-
>  builtin/revert.c   |  2 +-
>  7 files changed, 14 insertions(+), 11 deletions(-)
> 
> diff --git a/branch.c b/branch.c
> index 28b81a7e02..8dd5bb9f1c 100644
> --- a/branch.c
> +++ b/branch.c
> @@ -337,11 +337,14 @@ void create_branch(struct repository *r,
>  	free(real_ref);
>  }
>  
> -void remove_branch_state(struct repository *r)
> +void remove_branch_state(struct repository *r, int verbose)
>  {
> -	unlink(git_path_cherry_pick_head(r));
> -	unlink(git_path_revert_head(r));
> -	unlink(git_path_merge_head(r));
> +	if (!unlink(git_path_cherry_pick_head(r)) && verbose)
> +		warning(_("cancelling a cherry picking in progress"));
> +	if (!unlink(git_path_revert_head(r)) && verbose)
> +		warning(_("cancelling a revert in progress"));
> +	if (!unlink(git_path_merge_head(r)) && verbose)
> +		warning(_("cancelling a merge in progress"));
>  	unlink(git_path_merge_rr(r));
>  	unlink(git_path_merge_msg(r));
>  	unlink(git_path_merge_mode(r));
> diff --git a/branch.h b/branch.h
> index 29c1afa4d0..aed045901e 100644
> --- a/branch.h
> +++ b/branch.h
> @@ -64,7 +64,7 @@ extern int validate_new_branchname(const char *name, struct strbuf *ref, int for
>   * Remove information about the state of working on the current
>   * branch. (E.g., MERGE_HEAD)
>   */
> -void remove_branch_state(struct repository *r);
> +void remove_branch_state(struct repository *r, int verbose);
>  
>  /*
>   * Configure local branch "local" as downstream to branch "remote"
> diff --git a/builtin/am.c b/builtin/am.c
> index 4fb107a9d1..99b66508fd 100644
> --- a/builtin/am.c
> +++ b/builtin/am.c
> @@ -1957,7 +1957,7 @@ static int clean_index(const struct object_id *head, const struct object_id *rem
>  	if (merge_tree(remote_tree))
>  		return -1;
>  
> -	remove_branch_state(the_repository);
> +	remove_branch_state(the_repository, 0);
>  
>  	return 0;
>  }
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index 0e6037b296..f66bd2f56d 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -899,7 +899,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
>  				delete_reflog(old_branch_info->path);
>  		}
>  	}
> -	remove_branch_state(the_repository);
> +	remove_branch_state(the_repository, !opts->quiet);
>  	strbuf_release(&msg);
>  	if (!opts->quiet &&
>  	    (new_branch_info->path || (!opts->force_detach && !strcmp(new_branch_info->name, "HEAD"))))
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index 52114cbf0d..646d0f9fb1 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -1272,7 +1272,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>  		if (reset_head(NULL, "reset", NULL, RESET_HEAD_HARD,
>  			       NULL, NULL) < 0)
>  			die(_("could not discard worktree changes"));
> -		remove_branch_state(the_repository);
> +		remove_branch_state(the_repository, 0);
>  		if (read_basic_state(&options))
>  			exit(1);
>  		goto run_rebase;
> @@ -1292,7 +1292,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>  			       NULL, NULL) < 0)
>  			die(_("could not move back to %s"),
>  			    oid_to_hex(&options.orig_head));
> -		remove_branch_state(the_repository);
> +		remove_branch_state(the_repository, 0);
>  		ret = finish_rebase(&options);
>  		goto cleanup;
>  	}
> diff --git a/builtin/reset.c b/builtin/reset.c
> index 7882829a95..6d9397c844 100644
> --- a/builtin/reset.c
> +++ b/builtin/reset.c
> @@ -420,7 +420,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
>  			print_new_head_line(lookup_commit_reference(the_repository, &oid));
>  	}
>  	if (!pathspec.nr)
> -		remove_branch_state(the_repository);
> +		remove_branch_state(the_repository, 0);
>  
>  	return update_ref_status;
>  }
> diff --git a/builtin/revert.c b/builtin/revert.c
> index a47b53ceaf..ebf2789225 100644
> --- a/builtin/revert.c
> +++ b/builtin/revert.c
> @@ -196,7 +196,7 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
>  	if (cmd == 'q') {
>  		int ret = sequencer_remove_state(opts);
>  		if (!ret)
> -			remove_branch_state(the_repository);
> +			remove_branch_state(the_repository, 0);
>  		return ret;
>  	}
>  	if (cmd == 'c')
> -- 
> 2.21.0.479.g47ac719cd3
> 
Duy Nguyen· Jul 2, 2019, 09:03 UTC · re: SZEDER Gábor · lore

Re: [PATCH v6 07/27] checkout: inform the user when removing branch state

On Tue, Jul 02, 2019 at 10:06:11AM +0200, SZEDER Gábor wrote:
Show 33 quoted lines
> On Fri, Mar 29, 2019 at 05:38:59PM +0700, Nguyễn Thái Ngọc Duy wrote:
> > After a successful switch, if a merge, cherry-pick or revert is ongoing,
> > it is canceled. This behavior has been with us from the very early
> > beginning, soon after git-merge was created but never actually
> > documented [1]. It may be a good idea to be transparent and tell the
> > user if some operation is canceled.
> 
> After this entered 'next' last week, today it greeted me with 167(!)
> of these warnings...  before I even had my breakfast.
> 
> Now, my script does a lot of repeated cherry-picks and expects that
> rerere is able to deal with most of the conflicts, i.e. it does
> approximately this:
> 
>   if ! git cherry-pick $oid >/dev/null 2>&1
>   then
>       if was_the_conflict_resolved
>       then
>           echo "using previous conflict resolution"
>           git commit --no-edit --cleanup=strip --quiet
>       else
>           die "uh-oh"
>       fi
>   fi
> 
> That 'git commit' in there always prints:
> 
>   warning: cancelling a cherry picking in progress
> 
> I don't understand why committing after a cherry-pick is considered
> "cancelling"...  in my view it's finishing it and there should be no
> warning whatsoever.
> 

I agree, this is not "canceling". I think this series causes conflicts with pw/clean-sequencer-state-upon-final-commit and the warning is accidentally enabled (partly my fault since I named the argument "verbose").

Junio, in this conflict resolution (merging nd/switch-and-restore to next), we should pass '0' instead of 'verbose' to sequencer_post_commit_cleanup().

diff --cc builtin/commit.c index 1921401117,fa5982cc86..145d50caf0

Show changes to builtin/commit.c +1 −2
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@@ -1658,7 -1666,8 +1658,7 @@@ int cmd_commit(int argc, const char **a
  		die("%s", err.buf);
  	}
  
- 	sequencer_post_commit_cleanup(the_repository);
 -	unlink(git_path_cherry_pick_head(the_repository));
 -	unlink(git_path_revert_head(the_repository));
++	sequencer_post_commit_cleanup(the_repository, verbose);
  	unlink(git_path_merge_head(the_repository));
  	unlink(git_path_merge_msg(the_repository));
  	unlink(git_path_merge_mode(the_repository));
--
Duy
SZEDER Gábor· Jul 2, 2019, 10:43 UTC · re: Duy Nguyen · lore

Re: [PATCH v6 07/27] checkout: inform the user when removing branch state

On Tue, Jul 02, 2019 at 04:03:09PM +0700, Duy Nguyen wrote:
Show 59 quoted lines
> On Tue, Jul 02, 2019 at 10:06:11AM +0200, SZEDER Gábor wrote:
> > On Fri, Mar 29, 2019 at 05:38:59PM +0700, Nguyễn Thái Ngọc Duy wrote:
> > > After a successful switch, if a merge, cherry-pick or revert is ongoing,
> > > it is canceled. This behavior has been with us from the very early
> > > beginning, soon after git-merge was created but never actually
> > > documented [1]. It may be a good idea to be transparent and tell the
> > > user if some operation is canceled.
> > 
> > After this entered 'next' last week, today it greeted me with 167(!)
> > of these warnings...  before I even had my breakfast.
> > 
> > Now, my script does a lot of repeated cherry-picks and expects that
> > rerere is able to deal with most of the conflicts, i.e. it does
> > approximately this:
> > 
> >   if ! git cherry-pick $oid >/dev/null 2>&1
> >   then
> >       if was_the_conflict_resolved
> >       then
> >           echo "using previous conflict resolution"
> >           git commit --no-edit --cleanup=strip --quiet
> >       else
> >           die "uh-oh"
> >       fi
> >   fi
> > 
> > That 'git commit' in there always prints:
> > 
> >   warning: cancelling a cherry picking in progress
> > 
> > I don't understand why committing after a cherry-pick is considered
> > "cancelling"...  in my view it's finishing it and there should be no
> > warning whatsoever.
> > 
> 
> I agree, this is not "canceling". I think this series causes conflicts
> with pw/clean-sequencer-state-upon-final-commit and the warning is
> accidentally enabled (partly my fault since I named the argument
> "verbose").
> 
> Junio, in this conflict resolution (merging nd/switch-and-restore to
> next), we should pass '0' instead of 'verbose' to
> sequencer_post_commit_cleanup().
> 
> diff --cc builtin/commit.c
> index 1921401117,fa5982cc86..145d50caf0
> --- a/builtin/commit.c
> +++ b/builtin/commit.c
> @@@ -1658,7 -1666,8 +1658,7 @@@ int cmd_commit(int argc, const char **a
>   		die("%s", err.buf);
>   	}
>   
> - 	sequencer_post_commit_cleanup(the_repository);
>  -	unlink(git_path_cherry_pick_head(the_repository));
>  -	unlink(git_path_revert_head(the_repository));
> ++	sequencer_post_commit_cleanup(the_repository, verbose);
>   	unlink(git_path_merge_head(the_repository));
>   	unlink(git_path_merge_msg(the_repository));
>   	unlink(git_path_merge_mode(the_repository));
Thanks, this indeed takes care of it.
Junio C Hamano· Jul 2, 2019, 17:51 UTC · re: Duy Nguyen · lore

Re: [PATCH v6 07/27] checkout: inform the user when removing branch state

Duy Nguyen <pclouds@gmail.com> writes:
Show 8 quoted lines
> I agree, this is not "canceling". I think this series causes conflicts
> with pw/clean-sequencer-state-upon-final-commit and the warning is
> accidentally enabled (partly my fault since I named the argument
> "verbose").
>
> Junio, in this conflict resolution (merging nd/switch-and-restore to
> next), we should pass '0' instead of 'verbose' to
> sequencer_post_commit_cleanup().

Thanks for an update to the merge-fix. What should the name of the parameter to the sequencer_post_commit_cleanup() funcion be then, though? Perhaps we'd want an update after the topic graduates to the 'master' branch.

Show 18 quoted lines
>
> diff --cc builtin/commit.c
> index 1921401117,fa5982cc86..145d50caf0
> --- a/builtin/commit.c
> +++ b/builtin/commit.c
> @@@ -1658,7 -1666,8 +1658,7 @@@ int cmd_commit(int argc, const char **a
>   		die("%s", err.buf);
>   	}
>   
> - 	sequencer_post_commit_cleanup(the_repository);
>  -	unlink(git_path_cherry_pick_head(the_repository));
>  -	unlink(git_path_revert_head(the_repository));
> ++	sequencer_post_commit_cleanup(the_repository, verbose);
>   	unlink(git_path_merge_head(the_repository));
>   	unlink(git_path_merge_msg(the_repository));
>   	unlink(git_path_merge_mode(the_repository));
> --
> Duy
Duy Nguyen· Jul 4, 2019, 23:57 UTC · re: Junio C Hamano · lore

Re: [PATCH v6 07/27] checkout: inform the user when removing branch state

On Wed, Jul 3, 2019 at 12:51 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 16 quoted lines
>
> Duy Nguyen <pclouds@gmail.com> writes:
>
> > I agree, this is not "canceling". I think this series causes conflicts
> > with pw/clean-sequencer-state-upon-final-commit and the warning is
> > accidentally enabled (partly my fault since I named the argument
> > "verbose").
> >
> > Junio, in this conflict resolution (merging nd/switch-and-restore to
> > next), we should pass '0' instead of 'verbose' to
> > sequencer_post_commit_cleanup().
>
> Thanks for an update to the merge-fix.  What should the name of the
> parameter to the sequencer_post_commit_cleanup() funcion be then,
> though?  Perhaps we'd want an update after the topic graduates to
> the 'master' branch.

Definitely. I was thinking something with "warn" and "cancel", which is more the the point. Probably warn_cancel_in_progress, or something.

-- 
Duy
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 08/27] checkout: keep most #include sorted

The include list becomes very long and frankly a bit unorganized. With the exception of builtin.h, cache.h or git-compat-util.h which have to come first, keep the rest sorted.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 34 +++++++++++++++++-----------------
 1 file changed, 17 insertions(+), 17 deletions(-)
Show changes to builtin/checkout.c +17 −17
diff --git a/builtin/checkout.c b/builtin/checkout.c
index f66bd2f56d..15ee5181eb 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1,30 +1,30 @@
 #define USE_THE_INDEX_COMPATIBILITY_MACROS
 #include "builtin.h"
-#include "config.h"
+#include "advice.h"
+#include "blob.h"
+#include "branch.h"
+#include "cache-tree.h"
 #include "checkout.h"
+#include "commit.h"
+#include "config.h"
+#include "diff.h"
+#include "dir.h"
+#include "ll-merge.h"
 #include "lockfile.h"
+#include "merge-recursive.h"
+#include "object-store.h"
 #include "parse-options.h"
 #include "refs.h"
-#include "object-store.h"
-#include "commit.h"
+#include "remote.h"
+#include "resolve-undo.h"
+#include "revision.h"
+#include "run-command.h"
+#include "submodule.h"
+#include "submodule-config.h"
 #include "tree.h"
 #include "tree-walk.h"
-#include "cache-tree.h"
 #include "unpack-trees.h"
-#include "dir.h"
-#include "run-command.h"
-#include "merge-recursive.h"
-#include "branch.h"
-#include "diff.h"
-#include "revision.h"
-#include "remote.h"
-#include "blob.h"
 #include "xdiff-interface.h"
-#include "ll-merge.h"
-#include "resolve-undo.h"
-#include "submodule-config.h"
-#include "submodule.h"
-#include "advice.h"
 
 static int checkout_optimize_new_branch;
 
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 09/27] checkout: factor out some code in parse_branchname_arg()

This is in preparation for the new command restore, which also needs to parse opts->source_tree but does not need all the disambiguation logic.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 51 ++++++++++++++++++++++++++++------------------
 1 file changed, 31 insertions(+), 20 deletions(-)
Show changes to builtin/checkout.c +31 −20
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 15ee5181eb..897eefe16d 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1081,6 +1081,34 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
 	return git_xmerge_config(var, value, NULL);
 }
 
+static void setup_new_branch_info_and_source_tree(
+	struct branch_info *new_branch_info,
+	struct checkout_opts *opts,
+	struct object_id *rev,
+	const char *arg)
+{
+	struct tree **source_tree = &opts->source_tree;
+	struct object_id branch_rev;
+
+	new_branch_info->name = arg;
+	setup_branch_path(new_branch_info);
+
+	if (!check_refname_format(new_branch_info->path, 0) &&
+	    !read_ref(new_branch_info->path, &branch_rev))
+		oidcpy(rev, &branch_rev);
+	else
+		new_branch_info->path = NULL; /* not an existing branch */
+
+	new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
+	if (!new_branch_info->commit) {
+		/* not a commit */
+		*source_tree = parse_tree_indirect(rev);
+	} else {
+		parse_commit_or_die(new_branch_info->commit);
+		*source_tree = get_commit_tree(new_branch_info->commit);
+	}
+}
+
 static int parse_branchname_arg(int argc, const char **argv,
 				int dwim_new_local_branch_ok,
 				struct branch_info *new_branch_info,
@@ -1088,10 +1116,8 @@ static int parse_branchname_arg(int argc, const char **argv,
 				struct object_id *rev,
 				int *dwim_remotes_matched)
 {
-	struct tree **source_tree = &opts->source_tree;
 	const char **new_branch = &opts->new_branch;
 	int argcount = 0;
-	struct object_id branch_rev;
 	const char *arg;
 	int dash_dash_pos;
 	int has_dash_dash = 0;
@@ -1213,26 +1239,11 @@ static int parse_branchname_arg(int argc, const char **argv,
 	argv++;
 	argc--;
 
-	new_branch_info->name = arg;
-	setup_branch_path(new_branch_info);
-
-	if (!check_refname_format(new_branch_info->path, 0) &&
-	    !read_ref(new_branch_info->path, &branch_rev))
-		oidcpy(rev, &branch_rev);
-	else
-		new_branch_info->path = NULL; /* not an existing branch */
+	setup_new_branch_info_and_source_tree(new_branch_info, opts, rev, arg);
 
-	new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
-	if (!new_branch_info->commit) {
-		/* not a commit */
-		*source_tree = parse_tree_indirect(rev);
-	} else {
-		parse_commit_or_die(new_branch_info->commit);
-		*source_tree = get_commit_tree(new_branch_info->commit);
-	}
-
-	if (!*source_tree)                   /* case (1): want a tree */
+	if (!opts->source_tree)                   /* case (1): want a tree */
 		die(_("reference is not a tree: %s"), arg);
+
 	if (!has_dash_dash) {	/* case (3).(d) -> (1) */
 		/*
 		 * Do not complain the most common case
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 10/27] checkout: make "opts" in cmd_checkout() a pointer

"opts" will soon be moved out of cmd_checkout(). To keep changes in that patch smaller, convert "opts" to a pointer and keep the real thing behind "real_opts".

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 115 +++++++++++++++++++++++----------------------
 1 file changed, 58 insertions(+), 57 deletions(-)
Show changes to builtin/checkout.c +58 −57
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 897eefe16d..b6de8401fe 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1341,82 +1341,83 @@ static int checkout_branch(struct checkout_opts *opts,
 
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
-	struct checkout_opts opts;
+	struct checkout_opts real_opts;
+	struct checkout_opts *opts = &real_opts;
 	struct branch_info new_branch_info;
 	char *conflict_style = NULL;
 	int dwim_new_local_branch, no_dwim_new_local_branch = 0;
 	int dwim_remotes_matched = 0;
 	struct option options[] = {
-		OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
-		OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
+		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
+		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"),
+		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"),
+		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),
-		OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
-		OPT_SET_INT_F('2', "ours", &opts.writeout_stage,
+		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
+		OPT_SET_INT_F('2', "ours", &opts->writeout_stage,
 			      N_("checkout our version for unmerged files"),
 			      2, PARSE_OPT_NONEG),
-		OPT_SET_INT_F('3', "theirs", &opts.writeout_stage,
+		OPT_SET_INT_F('3', "theirs", &opts->writeout_stage,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
-		OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)"),
+		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")),
-		OPT_BOOL_F(0, "overwrite-ignore", &opts.overwrite_ignore,
+		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
+		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
 		OPT_STRING(0, "conflict", &conflict_style, N_("style"),
 			   N_("conflict style (merge or diff3)")),
-		OPT_BOOL('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
-		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree,
+		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_BOOL(0, "no-guess", &no_dwim_new_local_branch,
 			 N_("do not second guess 'git checkout <no-such-branch>'")),
-		OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
+		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
 		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
 			    "checkout", "control recursive updating of submodules",
 			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
-		OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
-		OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")),
+		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
+		OPT_BOOL(0, "overlay", &opts->overlay_mode, N_("use overlay mode (default)")),
 		OPT_END(),
 	};
 
-	memset(&opts, 0, sizeof(opts));
+	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->overwrite_ignore = 1;
+	opts->prefix = prefix;
+	opts->show_progress = -1;
+	opts->overlay_mode = -1;
 
-	git_config(git_checkout_config, &opts);
+	git_config(git_checkout_config, opts);
 
-	opts.track = BRANCH_TRACK_UNSPECIFIED;
+	opts->track = BRANCH_TRACK_UNSPECIFIED;
 
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
 	dwim_new_local_branch = !no_dwim_new_local_branch;
-	if (opts.show_progress < 0) {
-		if (opts.quiet)
-			opts.show_progress = 0;
+	if (opts->show_progress < 0) {
+		if (opts->quiet)
+			opts->show_progress = 0;
 		else
-			opts.show_progress = isatty(2);
+			opts->show_progress = isatty(2);
 	}
 
 	if (conflict_style) {
-		opts.merge = 1; /* implied */
+		opts->merge = 1; /* implied */
 		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
 	}
 
-	if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)
+	if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
 		die(_("-b, -B and --orphan are mutually exclusive"));
 
-	if (opts.overlay_mode == 1 && opts.patch_mode)
+	if (opts->overlay_mode == 1 && opts->patch_mode)
 		die(_("-p and --overlay are mutually exclusive"));
 
 	/*
@@ -1424,14 +1425,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	 * and new_branch_force and new_orphan_branch will tell us which one of
 	 * -b/-B/--orphan is being used.
 	 */
-	if (opts.new_branch_force)
-		opts.new_branch = opts.new_branch_force;
+	if (opts->new_branch_force)
+		opts->new_branch = opts->new_branch_force;
 
-	if (opts.new_orphan_branch)
-		opts.new_branch = opts.new_orphan_branch;
+	if (opts->new_orphan_branch)
+		opts->new_branch = opts->new_orphan_branch;
 
 	/* --track without -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];
 		if (!argc || !strcmp(argv0, "--"))
 			die(_("--track needs a branch name"));
@@ -1440,7 +1441,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		argv0 = strchr(argv0, '/');
 		if (!argv0 || !argv0[1])
 			die(_("missing branch name; try -b"));
-		opts.new_branch = argv0 + 1;
+		opts->new_branch = argv0 + 1;
 	}
 
 	/*
@@ -1459,56 +1460,56 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	if (argc) {
 		struct object_id rev;
 		int dwim_ok =
-			!opts.patch_mode &&
+			!opts->patch_mode &&
 			dwim_new_local_branch &&
-			opts.track == BRANCH_TRACK_UNSPECIFIED &&
-			!opts.new_branch;
+			opts->track == BRANCH_TRACK_UNSPECIFIED &&
+			!opts->new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
-					     &new_branch_info, &opts, &rev,
+					     &new_branch_info, opts, &rev,
 					     &dwim_remotes_matched);
 		argv += n;
 		argc -= n;
 	}
 
 	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)
+		if (opts->new_branch && argc == 1)
 			die(_("'%s' is not a commit and a branch '%s' cannot be created from it"),
-				argv[0], opts.new_branch);
+				argv[0], opts->new_branch);
 
-		if (opts.force_detach)
+		if (opts->force_detach)
 			die(_("git checkout: --detach does not take a path argument '%s'"),
 			    argv[0]);
 
-		if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
+		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.new_branch) {
+	if (opts->new_branch) {
 		struct strbuf buf = STRBUF_INIT;
 
-		if (opts.new_branch_force)
-			opts.branch_exists = validate_branchname(opts.new_branch, &buf);
+		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);
+			opts->branch_exists =
+				validate_new_branchname(opts->new_branch, &buf, 0);
 		strbuf_release(&buf);
 	}
 
 	UNLEAK(opts);
-	if (opts.patch_mode || opts.pathspec.nr) {
-		int ret = checkout_paths(&opts, new_branch_info.name);
+	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"
@@ -1527,6 +1528,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			       dwim_remotes_matched);
 		return ret;
 	} else {
-		return checkout_branch(&opts, &new_branch_info);
+		return checkout_branch(opts, &new_branch_info);
 	}
 }
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 11/27] checkout: move 'confict_style' and 'dwim_..' to checkout_opts

These local variables are referenced by struct option[]. This struct will soon be broken down, moved away and we can't rely on local variables anymore. Move these two to struct checkout_opts in preparation for that.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)
Show changes to builtin/checkout.c +10 −7
diff --git a/builtin/checkout.c b/builtin/checkout.c
index b6de8401fe..93fc2a5815 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -47,6 +47,8 @@ struct checkout_opts {
 	int show_progress;
 	int count_checkout_paths;
 	int overlay_mode;
+	int no_dwim_new_local_branch;
+
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
 	 * should be updated accordingly.
@@ -58,6 +60,7 @@ struct checkout_opts {
 	int new_branch_log;
 	enum branch_track track;
 	struct diff_options diff_options;
+	char *conflict_style;
 
 	int branch_exists;
 	const char *prefix;
@@ -1344,8 +1347,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	struct checkout_opts real_opts;
 	struct checkout_opts *opts = &real_opts;
 	struct branch_info new_branch_info;
-	char *conflict_style = NULL;
-	int dwim_new_local_branch, no_dwim_new_local_branch = 0;
+	int dwim_new_local_branch;
 	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
@@ -1370,12 +1372,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_STRING(0, "conflict", &conflict_style, N_("style"),
+		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
 			   N_("conflict style (merge or diff3)")),
 		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_BOOL(0, "no-guess", &no_dwim_new_local_branch,
+		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
 			 N_("do not second guess 'git checkout <no-such-branch>'")),
 		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
@@ -1393,6 +1395,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts->prefix = prefix;
 	opts->show_progress = -1;
 	opts->overlay_mode = -1;
+	opts->no_dwim_new_local_branch = 0;
 
 	git_config(git_checkout_config, opts);
 
@@ -1401,7 +1404,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
-	dwim_new_local_branch = !no_dwim_new_local_branch;
+	dwim_new_local_branch = !opts->no_dwim_new_local_branch;
 	if (opts->show_progress < 0) {
 		if (opts->quiet)
 			opts->show_progress = 0;
@@ -1409,9 +1412,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			opts->show_progress = isatty(2);
 	}
 
-	if (conflict_style) {
+	if (opts->conflict_style) {
 		opts->merge = 1; /* implied */
-		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
+		git_xmerge_config("merge.conflictstyle", opts->conflict_style, NULL);
 	}
 
 	if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 12/27] checkout: split options[] array in three pieces

This is a preparation step for introducing new commands that do parts of what checkout does. There will be two new commands, one is about switching branches, detaching HEAD... one about checking out paths. These share the a subset of command line options. The rest of command line options are separate.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 82 +++++++++++++++++++++++++++++++++-------------
 parse-options-cb.c | 17 ++++++++++
 parse-options.h    |  1 +
 3 files changed, 77 insertions(+), 23 deletions(-)
Show changes to 3 files +77 −23

builtin/checkout.c, parse-options-cb.c, parse-options.h

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 93fc2a5815..c15dc91e16 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1342,15 +1342,31 @@ static int checkout_branch(struct checkout_opts *opts,
 	return switch_branches(opts, new_branch_info);
 }
 
-int cmd_checkout(int argc, const char **argv, const char *prefix)
+static struct option *add_common_options(struct checkout_opts *opts,
+					 struct option *prevopts)
 {
-	struct checkout_opts real_opts;
-	struct checkout_opts *opts = &real_opts;
-	struct branch_info new_branch_info;
-	int dwim_new_local_branch;
-	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
+		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
+			    "checkout", "control recursive updating of submodules",
+			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
+		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
+		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
+			   PARSE_OPT_NOCOMPLETE),
+		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
+		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
+			   N_("conflict style (merge or diff3)")),
+		OPT_END()
+	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+static struct option *add_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"),
@@ -1360,34 +1376,49 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
 			BRANCH_TRACK_EXPLICIT),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
+		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
+			   N_("update ignored files (default)"),
+			   PARSE_OPT_NOCOMPLETE),
+		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
+			 N_("second guess 'git checkout <no-such-branch>'")),
+		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
+			 N_("do not check if another worktree is holding the given ref")),
+		OPT_END()
+	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+static struct option *add_checkout_path_options(struct checkout_opts *opts,
+						struct option *prevopts)
+{
+	struct option options[] = {
 		OPT_SET_INT_F('2', "ours", &opts->writeout_stage,
 			      N_("checkout our version for unmerged files"),
 			      2, PARSE_OPT_NONEG),
 		OPT_SET_INT_F('3', "theirs", &opts->writeout_stage,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
-		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
-			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
-		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
-			   N_("update ignored files (default)"),
-			   PARSE_OPT_NOCOMPLETE),
-		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
-			   N_("conflict style (merge or diff3)")),
 		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_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
-			 N_("do not second guess 'git checkout <no-such-branch>'")),
-		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
-			 N_("do not check if another worktree is holding the given ref")),
-		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
-			    "checkout", "control recursive updating of submodules",
-			    PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
-		OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
 		OPT_BOOL(0, "overlay", &opts->overlay_mode, N_("use overlay mode (default)")),
-		OPT_END(),
+		OPT_END()
 	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+int cmd_checkout(int argc, const char **argv, const char *prefix)
+{
+	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));
@@ -1401,6 +1432,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	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,
 			     PARSE_OPT_KEEP_DASHDASH);
 
diff --git a/parse-options-cb.c b/parse-options-cb.c
index 2733393546..caaeed896f 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -122,6 +122,23 @@ int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
 	return 0;
 }
 
+struct option *parse_options_dup(const struct option *o)
+{
+	struct option *opts;
+	int nr = 0;
+
+	while (o && o->type != OPTION_END) {
+		nr++;
+		o++;
+	}
+
+	ALLOC_ARRAY(opts, nr + 1);
+	memcpy(opts, o - nr, sizeof(*o) * nr);
+	memset(opts + nr, 0, sizeof(*opts));
+	opts[nr].type = OPTION_END;
+	return opts;
+}
+
 struct option *parse_options_concat(struct option *a, struct option *b)
 {
 	struct option *ret;
diff --git a/parse-options.h b/parse-options.h
index 7d83e2971d..9a90c332a5 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -257,6 +257,7 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
 
 int parse_options_end(struct parse_opt_ctx_t *ctx);
 
+struct option *parse_options_dup(const struct option *a);
 struct option *parse_options_concat(struct option *a, struct option *b);
 
 /*----- some often used options -----*/
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 13/27] checkout: split part of it to new command 'switch'

"git checkout" doing too many things is a source of confusion for many users (and it even bites old timers sometimes). To remedy that, the command will be split into two new ones: switch and restore. The good old "git checkout" command is still here and will be until all (or most of users) are sick of it.

See the new man page for the final design of switch. The actual implementation though is still pretty much the same as "git checkout" and not completely aligned with the man page. Following patches will adjust their behavior to match the man page.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 .gitignore                         |   1 +
 Documentation/config/advice.txt    |  13 +-
 Documentation/config/branch.txt    |   4 +-
 Documentation/config/checkout.txt  |   9 +-
 Documentation/config/diff.txt      |   3 +-
 Documentation/git-checkout.txt     |   4 +
 Documentation/git-switch.txt (new) | 271 +++++++++++++++++++++++++++++
 Documentation/gitattributes.txt    |   3 +-
 Documentation/githooks.txt         |   8 +-
 Makefile                           |   1 +
 builtin.h                          |   1 +
 builtin/checkout.c                 |  60 +++++--
 command-list.txt                   |   1 +
 git.c                              |   1 +
 14 files changed, 353 insertions(+), 27 deletions(-)
Show changes to 14 files +353 −27

.gitignore, Documentation/config/advice.txt, Documentation/config/branch.txt, Documentation/config/checkout.txt, Documentation/config/diff.txt, Documentation/git-checkout.txt, Documentation/git-switch.txt, Documentation/gitattributes.txt, Documentation/githooks.txt, Makefile, builtin.h, builtin/checkout.c, command-list.txt, git.c

diff --git a/.gitignore b/.gitignore
index 7374587f9d..c687b92b1c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -167,6 +167,7 @@
 /git-submodule
 /git-submodule--helper
 /git-svn
+/git-switch
 /git-symbolic-ref
 /git-tag
 /git-unpack-file
diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
index 88620429ea..239d479506 100644
--- a/Documentation/config/advice.txt
+++ b/Documentation/config/advice.txt
@@ -42,7 +42,8 @@ advice.*::
 		state in the output of linkgit:git-status[1], in
 		the template shown when writing commit messages in
 		linkgit:git-commit[1], and in the help message shown
-		by linkgit:git-checkout[1] when switching branch.
+		by linkgit:git-switch[1] or
+		linkgit:git-checkout[1] when switching branch.
 	statusUoption::
 		Advise to consider using the `-u` option to linkgit:git-status[1]
 		when the command takes more than 2 seconds to enumerate untracked
@@ -62,12 +63,14 @@ advice.*::
 		your information is guessed from the system username and
 		domain name.
 	detachedHead::
-		Advice shown when you used linkgit:git-checkout[1] to
-		move to the detach HEAD state, to instruct how to create
-		a local branch after the fact.
+		Advice shown when you used
+		linkgit:git-switch[1] or linkgit:git-checkout[1]
+		to move to the detach HEAD state, to instruct how to
+		create a local branch after the fact.
 	checkoutAmbiguousRemoteBranchName::
 		Advice shown when the argument to
-		linkgit:git-checkout[1] ambiguously resolves to a
+		linkgit:git-checkout[1] and linkgit:git-switch[1]
+		ambiguously resolves to a
 		remote tracking branch on more than one remote in
 		situations where an unambiguous argument would have
 		otherwise caused a remote-tracking branch to be
diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index 019d60ede2..8050466159 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -1,5 +1,5 @@
 branch.autoSetupMerge::
-	Tells 'git branch' and 'git checkout' to set up new branches
+	Tells 'git branch', 'git switch' and 'git checkout' to set up new branches
 	so that linkgit:git-pull[1] will appropriately merge from the
 	starting point branch. Note that even if this option is not set,
 	this behavior can be chosen per-branch using the `--track`
@@ -11,7 +11,7 @@ branch.autoSetupMerge::
 	branch. This option defaults to true.
 
 branch.autoSetupRebase::
-	When a new branch is created with 'git branch' or 'git checkout'
+	When a new branch is created with 'git branch', 'git switch' or 'git checkout'
 	that tracks another branch, this variable tells Git to set
 	up pull to rebase instead of merge (see "branch.<name>.rebase").
 	When `never`, rebase is never automatically set to true.
diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt
index c4118fa196..d6872ffa83 100644
--- a/Documentation/config/checkout.txt
+++ b/Documentation/config/checkout.txt
@@ -1,5 +1,6 @@
 checkout.defaultRemote::
-	When you run 'git checkout <something>' and only have one
+	When you run 'git checkout <something>'
+	or 'git switch <something>' and only have one
 	remote, it may implicitly fall back on checking out and
 	tracking e.g. 'origin/<something>'. This stops working as soon
 	as you have more than one remote with a '<something>'
@@ -8,8 +9,10 @@ checkout.defaultRemote::
 	disambiguation. The typical use-case is to set this to
 	`origin`.
 +
-Currently this is used by linkgit:git-checkout[1] when 'git checkout
-<something>' will checkout the '<something>' branch on another remote,
+Currently this is used by linkgit:git-switch[1] and
+linkgit:git-checkout[1] when 'git checkout <something>'
+or 'git switch <something>'
+will checkout the '<something>' branch on another remote,
 and by linkgit:git-worktree[1] when 'git worktree add' refers to a
 remote branch. This setting might be used for other checkout-like
 commands or functionality in the future.
diff --git a/Documentation/config/diff.txt b/Documentation/config/diff.txt
index e48bb987d7..b3b304ee12 100644
--- a/Documentation/config/diff.txt
+++ b/Documentation/config/diff.txt
@@ -78,7 +78,8 @@ diff.external::
 diff.ignoreSubmodules::
 	Sets the default value of --ignore-submodules. Note that this
 	affects only 'git diff' Porcelain, and not lower level 'diff'
-	commands such as 'git diff-files'. 'git checkout' also honors
+	commands such as 'git diff-files'. 'git checkout'
+	and 'git switch' also honor
 	this setting when reporting uncommitted changes. Setting it to
 	'all' disables the submodule summary normally shown by 'git commit'
 	and 'git status' when `status.submoduleSummary` is set unless it is
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index bf90966c95..c7192bdefe 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -560,6 +560,10 @@ $ edit frotz
 $ git add frotz
 ------------
 
+SEE ALSO
+--------
+linkgit:git-switch[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
new file mode 100644
index 0000000000..f15e5bdcf4
--- /dev/null
+++ b/Documentation/git-switch.txt
@@ -0,0 +1,271 @@
+git-switch(1)
+=============
+
+NAME
+----
+git-switch - Switch branches
+
+SYNOPSIS
+--------
+[verse]
+'git switch' [<options>] [--no-guess] <branch>
+'git switch' [<options>] --detach [<start-point>]
+'git switch' [<options>] (-c|-C) <new-branch> [<start-point>]
+'git switch' [<options>] --orphan <new-branch>
+
+DESCRIPTION
+-----------
+Switch to a specified branch. The working tree and the index are
+updated to match the branch. All new commits will be added to the tip
+of this branch.
+
+Optionally a new branch could be created with either `-c`, `-C`,
+automatically from a remote branch of same name (see `--guess`), or
+detach the working tree from any branch with `--detach`, along with
+switching.
+
+Switching branches does not require a clean index and working tree
+(i.e. no differences compared to `HEAD`). The operation is aborted
+however if the operation leads to loss of local changes, unless told
+otherwise with `--discard-changes` or `--merge`.
+
+OPTIONS
+-------
+<branch>::
+	Branch to switch to.
+
+<new-branch>::
+	Name for the new branch.
+
+<start-point>::
+	The starting point for the new branch. Specifying a
+	`<start-point>` allows you to create a branch based on some
+	other point in history than where HEAD currently points. (Or,
+	in the case of `--detach`, allows you to inspect and detach
+	from some other point.)
++
+You can use the `@{-N}` syntax to refer to the N-th last
+branch/commit switched to using "git switch" or "git checkout"
+operation. You may also specify `-` which is synonymous to `@{-1}`.
+This is often used to switch quickly between two branches, or to undo
+a branch switch by mistake.
++
+As a special case, you may use `A...B` as a shortcut for the merge
+base of `A` and `B` if there is exactly one merge base. You can leave
+out at most one of `A` and `B`, in which case it defaults to `HEAD`.
+
+-c <new-branch>::
+--create <new-branch>::
+	Create a new branch named `<new-branch>` starting at
+	`<start-point>` before switching to the branch. This is a
+	convenient shortcut for:
++
+------------
+$ git branch <new-branch>
+$ git switch <new-branch>
+------------
+
+-C <new-branch>::
+--force-create <new-branch>::
+	Similar to `--create` except that if `<new-branch>` already
+	exists, it will be reset to `<start-point>`. This is a
+	convenient shortcut for:
++
+------------
+$ git branch -f <new-branch>
+$ git switch <new-branch>
+------------
+
+-d::
+--detach::
+	Switch to a commit for inspection and discardable
+	experiments. See the "DETACHED HEAD" section in
+	linkgit:git-checkout[1] for details.
+
+--guess::
+--no-guess::
+	If `<branch>` is not found but there does exist a tracking
+	branch in exactly one remote (call it `<remote>`) with a
+	matching name, treat as equivalent to
++
+------------
+$ git switch -c <branch> --track <remote>/<branch>
+------------
++
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to e.g. `checkout.defaultRemote=origin`
+to always checkout remote branches from there if `<branch>` is
+ambiguous but exists on the 'origin' remote. See also
+`checkout.defaultRemote` in linkgit:git-config[1].
++
+`--guess` is the default behavior. Use `--no-guess` to disable it.
+
+-f::
+--force::
+	An alias for `--discard-changes`.
+
+--discard-changes::
+	Proceed even if the index or the working tree differs from
+	`HEAD`. Both the index and working tree are restored to match
+	the switching target. If `--recurse-submodules` is specified,
+	submodule content is also restored to match the switching
+	target. This is used to throw away local changes.
+
+-m::
+--merge::
+	If you have local modifications to one or more files that are
+	different between the current branch and the branch to which
+	you are switching, the command refuses to switch branches in
+	order to preserve your modifications in context.  However,
+	with this option, a three-way merge between the current
+	branch, your working tree contents, and the new branch is
+	done, and you will be on the new branch.
++
+When a merge conflict happens, the index entries for conflicting
+paths are left unmerged, and you need to resolve the conflicts
+and mark the resolved paths with `git add` (or `git rm` if the merge
+should result in deletion of the path).
+
+--conflict=<style>::
+	The same as `--merge` option above, but changes the way the
+	conflicting hunks are presented, overriding the
+	`merge.conflictStyle` configuration variable.  Possible values are
+	"merge" (default) and "diff3" (in addition to what is shown by
+	"merge" style, shows the original contents).
+
+-q::
+--quiet::
+	Quiet, suppress feedback messages.
+
+--progress::
+--no-progress::
+	Progress status is reported on the standard error stream
+	by default when it is attached to a terminal, unless `--quiet`
+	is specified. This flag enables progress reporting even if not
+	attached to a terminal, regardless of `--quiet`.
+
+-t::
+--track::
+	When creating a new branch, set up "upstream" configuration.
+	`-c` is implied. See `--track` in linkgit:git-branch[1] for
+	details.
++
+If no `-c` option is given, the name of the new branch will be derived
+from the remote-tracking branch, by looking at the local part of the
+refspec configured for the corresponding remote, and then stripping
+the initial part up to the "*".  This would tell us to use `hack` as
+the local branch when branching off of `origin/hack` (or
+`remotes/origin/hack`, or even `refs/remotes/origin/hack`).  If the
+given name has no slash, or the above guessing results in an empty
+name, the guessing is aborted.  You can explicitly give a name with
+`-c` in such a case.
+
+--no-track::
+	Do not set up "upstream" configuration, even if the
+	`branch.autoSetupMerge` configuration variable is true.
+
+--orphan <new-branch>::
+	Create a new 'orphan' branch, named `<new-branch>`. All
+	tracked files are removed.
+
+--ignore-other-worktrees::
+	`git switch` refuses when the wanted ref is already
+	checked out by another worktree. This option makes it check
+	the ref out anyway. In other words, the ref can be held by
+	more than one worktree.
+
+--recurse-submodules::
+--no-recurse-submodules::
+	Using `--recurse-submodules` will update the content of all
+	initialized submodules according to the commit recorded in the
+	superproject. If nothing (or `--no-recurse-submodules`) is
+	used, the work trees of submodules will not be updated. Just
+	like linkgit:git-submodule[1], this will detach `HEAD` of the
+	submodules.
+
+EXAMPLES
+--------
+
+The following command switches to the "master" branch:
+
+------------
+$ git switch master
+------------
+
+After working in the wrong branch, switching to the correct branch
+would be done using:
+
+------------
+$ git switch mytopic
+------------
+
+However, your "wrong" branch and correct "mytopic" branch may differ
+in files that you have modified locally, in which case the above
+switch would fail like this:
+
+------------
+$ git switch mytopic
+error: You have local changes to 'frotz'; not switching branches.
+------------
+
+You can give the `-m` flag to the command, which would try a three-way
+merge:
+
+------------
+$ git switch -m mytopic
+Auto-merging frotz
+------------
+
+After this three-way merge, the local modifications are _not_
+registered in your index file, so `git diff` would show you what
+changes you made since the tip of the new branch.
+
+To switch back to the previous branch before we switched to mytopic
+(i.e. "master" branch):
+
+------------
+$ git switch -
+------------
+
+You can grow a new branch from any commit. For example, switch to
+"HEAD~3" and create branch "fixup":
+
+------------
+$ git switch -c fixup HEAD~3
+Switched to a new branch 'fixup'
+------------
+
+If you want to start a new branch from a remote branch of the same
+name:
+
+------------
+$ git switch new-topic
+Branch 'new-topic' set up to track remote branch 'new-topic' from 'origin'
+Switched to a new branch 'new-topic'
+------------
+
+To check out commit `HEAD~3` for temporary inspection or experiment
+without creating a new branch:
+
+------------
+$ git switch --detach HEAD~3
+HEAD is now at 9fc9555312 Merge branch 'cc/shared-index-permbits'
+------------
+
+If it turns out whatever you have done is worth keeping, you can
+always create a new name for it (without switching away):
+
+------------
+$ git switch -c good-surprises
+------------
+
+SEE ALSO
+--------
+linkgit:git-checkout[1],
+linkgit:git-branch[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 9b41f81c06..cd0f9fa507 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -112,7 +112,8 @@ Checking-out and checking-in
 
 These attributes affect how the contents stored in the
 repository are copied to the working tree files when commands
-such as 'git checkout' and 'git merge' run.  They also affect how
+such as 'git switch', 'git checkout'  and 'git merge' run.
+They also affect how
 Git stores the contents you prepare in the working tree in the
 repository upon 'git add' and 'git commit'.
 
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 5bf653c111..8ff72f0613 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -165,12 +165,13 @@ rebased, and is not set when rebasing the current branch.
 post-checkout
 ~~~~~~~~~~~~~
 
-This hook is invoked when a linkgit:git-checkout[1] is run after having updated the
+This hook is invoked when a linkgit:git-checkout[1] or
+linkgit:git-switch[1] is run after having updated the
 worktree.  The hook is given three parameters: the ref of the previous HEAD,
 the ref of the new HEAD (which may or may not have changed), and a flag
 indicating whether the checkout was a branch checkout (changing branches,
 flag=1) or a file checkout (retrieving a file from the index, flag=0).
-This hook cannot affect the outcome of `git checkout`.
+This hook cannot affect the outcome of `git switch` or `git checkout`.
 
 It is also run after linkgit:git-clone[1], unless the `--no-checkout` (`-n`) option is
 used. The first parameter given to the hook is the null-ref, the second the
@@ -406,7 +407,8 @@ exit with a zero status.
 For example, the hook can simply run `git read-tree -u -m HEAD "$1"`
 in order to emulate `git fetch` that is run in the reverse direction
 with `git push`, as the two-tree form of `git read-tree -u -m` is
-essentially the same as `git checkout` that switches branches while
+essentially the same as `git switch` or `git checkout`
+that switches branches while
 keeping the local changes in the working tree that do not interfere
 with the difference between the branches.
 
diff --git a/Makefile b/Makefile
index 148668368b..8e91db73ad 100644
--- a/Makefile
+++ b/Makefile
@@ -802,6 +802,7 @@ BUILT_INS += git-merge-subtree$X
 BUILT_INS += git-show$X
 BUILT_INS += git-stage$X
 BUILT_INS += git-status$X
+BUILT_INS += git-switch$X
 BUILT_INS += git-whatchanged$X
 
 # what 'all' will build and 'install' will install in gitexecdir,
diff --git a/builtin.h b/builtin.h
index 6538932e99..c64e44450e 100644
--- a/builtin.h
+++ b/builtin.h
@@ -227,6 +227,7 @@ extern int cmd_show_index(int argc, const char **argv, const char *prefix);
 extern int cmd_status(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
+extern int cmd_switch(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index c15dc91e16..4989ca73a3 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -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;
@@ -1411,33 +1416,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;
@@ -1570,3 +1567,42 @@ 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 = NULL;
+	int ret;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.no_dwim_new_local_branch = 0;
+
+	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);
+
+	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;
+	int ret;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.no_dwim_new_local_branch = 0;
+
+	options = parse_options_dup(options);
+	options = add_common_options(&opts, options);
+	options = add_switch_branch_options(&opts, options);
+
+	ret = checkout_main(argc, argv, prefix, &opts,
+			    options, switch_branch_usage);
+	FREE_AND_NULL(options);
+	return ret;
+}
diff --git a/command-list.txt b/command-list.txt
index 3a9af104b5..13317f47d4 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -171,6 +171,7 @@ git-status                              mainporcelain           info
 git-stripspace                          purehelpers
 git-submodule                           mainporcelain
 git-svn                                 foreignscminterface
+git-switch                              mainporcelain           history
 git-symbolic-ref                        plumbingmanipulators
 git-tag                                 mainporcelain           history
 git-unpack-file                         plumbinginterrogators
diff --git a/git.c b/git.c
index 2014aab6b8..39582cf511 100644
--- a/git.c
+++ b/git.c
@@ -573,6 +573,7 @@ static struct cmd_struct commands[] = {
 	{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
 	{ "stripspace", cmd_stripspace },
 	{ "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
+	{ "switch", cmd_switch, RUN_SETUP | NEED_WORK_TREE },
 	{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
 	{ "tag", cmd_tag, RUN_SETUP | DELAY_PAGER_CONFIG },
 	{ "unpack-file", cmd_unpack_file, RUN_SETUP | NO_PARSEOPT },
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 14/27] switch: better names for -b and -B

The shortcut of these options do not make much sense when used with switch. And their descriptions are also tied to checkout. Move -b/-B to cmd_checkout() and new -c/-C with the same functionality in cmd_switch_branch()

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 32 +++++++++++++++++++++-----------
 1 file changed, 21 insertions(+), 11 deletions(-)
Show changes to builtin/checkout.c +21 −11
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 4989ca73a3..319ba372e3 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1368,14 +1368,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"),
@@ -1571,15 +1567,22 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
 	struct checkout_opts opts;
-	struct option *options = NULL;
+	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_END()
+	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
 
-	options = parse_options_dup(options);
+	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
-	options = add_switch_branch_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,
@@ -1592,14 +1595,21 @@ 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_END()
+	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
 
-	options = parse_options_dup(options);
+	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-	options = add_switch_branch_options(&opts, options);
+	options = add_common_switch_branch_options(&opts, options);
 
 	ret = checkout_main(argc, argv, prefix, &opts,
 			    options, switch_branch_usage);
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 15/27] switch: add --discard-changes

--discard-changes is a better name than --force for this option since it's what really happens. --force is turned to an alias for --discard-changes. But it's meant to be an alias for potentially more force options in the future.

Side note. It's not obvious from the patch but --discard-changes also affects submodules if --recurse-submodules is used. The knob to force updating submodules is hidden behind unpack-trees.c

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)
Show changes to builtin/checkout.c +10 −2
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 319ba372e3..6d0b2ef565 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -53,6 +53,7 @@ struct checkout_opts {
 	int count_checkout_paths;
 	int overlay_mode;
 	int no_dwim_new_local_branch;
+	int discard_changes;
 
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
@@ -680,7 +681,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)
@@ -802,7 +803,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;
@@ -1309,6 +1310,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");
@@ -1445,6 +1449,8 @@ static int checkout_main(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"));
@@ -1600,6 +1606,8 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 			   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;
-- 
2.21.0.479.g47ac719cd3
Phillip Wood· Apr 25, 2019, 10:02 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v6 15/27] switch: add --discard-changes

On 29/03/2019 10:39, Nguyễn Thái Ngọc Duy wrote:
> --discard-changes is a better name than --force for this option since
> it's what really happens.

I didn't realize when I suggested the name that --force overwrites untracked files as well as discarding changes from tracked files. I think we should document that. It would be nice if read-tree --reset -u took an optional argument so read-tree --reset=tracked -u would not overwrite untracked files. Then we could have --discard-changes just discard the changes and not overwrite untracked files. I had a quick look at unpack trees and it looks like a fairly straight forward change (famous last words) - perhaps I'll have a go at it next week.

Best Wishes
Phillip
Show 72 quoted lines
> --force is turned to an alias for
> --discard-changes. But it's meant to be an alias for potentially more
> force options in the future.
> 
> Side note. It's not obvious from the patch but --discard-changes also
> affects submodules if --recurse-submodules is used. The knob to force
> updating submodules is hidden behind unpack-trees.c
> 
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  builtin/checkout.c | 12 ++++++++++--
>  1 file changed, 10 insertions(+), 2 deletions(-)
> 
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index 319ba372e3..6d0b2ef565 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -53,6 +53,7 @@ struct checkout_opts {
>  	int count_checkout_paths;
>  	int overlay_mode;
>  	int no_dwim_new_local_branch;
> +	int discard_changes;
>  
>  	/*
>  	 * If new checkout options are added, skip_merge_working_tree
> @@ -680,7 +681,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)
> @@ -802,7 +803,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;
> @@ -1309,6 +1310,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");
> @@ -1445,6 +1449,8 @@ static int checkout_main(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"));
> @@ -1600,6 +1606,8 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
>  			   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;
> 
Duy Nguyen· Apr 25, 2019, 10:12 UTC · re: Phillip Wood · lore

Re: [PATCH v6 15/27] switch: add --discard-changes

On Thu, Apr 25, 2019 at 5:02 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 13 quoted lines
>
> On 29/03/2019 10:39, Nguyễn Thái Ngọc Duy wrote:
> > --discard-changes is a better name than --force for this option since
> > it's what really happens.
>
> I didn't realize when I suggested the name that --force overwrites
> untracked files as well as discarding changes from tracked files. I
> think we should document that. It would be nice if read-tree --reset -u
> took an optional argument so read-tree --reset=tracked -u would not
> overwrite untracked files. Then we could have --discard-changes just
> discard the changes and not overwrite untracked files. I had a quick
> look at unpack trees and it looks like a fairly straight forward change
> (famous last words) - perhaps I'll have a go at it next week.

So, --discard-changes is all about tracked changes, and we may have --overwrite-untracked to cover the other part, and --force enables both? That does not sound so bad (and maybe a good cure for those "overwriting untracked" reports we've seen quite often lately).

Good luck with unpack-trees.c. But if it turns out you're too busy, just let me know if want to hand that back to me.

-- 
Duy
Phillip Wood· Apr 29, 2019, 15:14 UTC · re: Duy Nguyen · lore

Re: [PATCH v6 15/27] switch: add --discard-changes

On 25/04/2019 11:12, Duy Nguyen wrote:
Show 18 quoted lines
> On Thu, Apr 25, 2019 at 5:02 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
>>
>> On 29/03/2019 10:39, Nguyễn Thái Ngọc Duy wrote:
>>> --discard-changes is a better name than --force for this option since
>>> it's what really happens.
>>
>> I didn't realize when I suggested the name that --force overwrites
>> untracked files as well as discarding changes from tracked files. I
>> think we should document that. It would be nice if read-tree --reset -u
>> took an optional argument so read-tree --reset=tracked -u would not
>> overwrite untracked files. Then we could have --discard-changes just
>> discard the changes and not overwrite untracked files. I had a quick
>> look at unpack trees and it looks like a fairly straight forward change
>> (famous last words) - perhaps I'll have a go at it next week.
> 
> So, --discard-changes is all about tracked changes, and we may have
> --overwrite-untracked to cover the other part, and --force enables
> both? 

I was thinking of --discard-changes dealing with tracked changes and having --force for untracked changes as well. I'm not sure we need --overwrite-untracked for switch, just for read-tree.

That does not sound so bad (and maybe a good cure for those
> "overwriting untracked" reports we've seen quite often lately).
> 
> Good luck with unpack-trees.c. But if it turns out you're too busy,
> just let me know if want to hand that back to me.

Thanks, I've got something working, I'll clean it up and send it later in the week.

Best Wishes
Phillip
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 16/27] switch: remove -l

This option is ancient. Nowadays reflog is enabled by default and automatically created for new branches. Keep it in git-checkout only.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/checkout.c +1 −1
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 6d0b2ef565..d63116b686 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1376,7 +1376,6 @@ static struct option *add_common_switch_branch_options(
 	struct checkout_opts *opts, struct option *prevopts)
 {
 	struct option options[] = {
-		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),
@@ -1579,6 +1578,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			   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;
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 17/27] switch: stop accepting pathspec

This command is about switching branch (or creating a new one) and should not accept pathspec. This helps simplify ambiguation handling. The other two ("git checkout" and "git restore") of course do accept pathspec as before.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)
Show changes to builtin/checkout.c +15 −5
diff --git a/builtin/checkout.c b/builtin/checkout.c
index d63116b686..0584dc484d 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -54,6 +54,7 @@ struct checkout_opts {
 	int overlay_mode;
 	int no_dwim_new_local_branch;
 	int discard_changes;
+	int accept_pathspec;
 
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
@@ -1176,10 +1177,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;
 		}
@@ -1213,11 +1220,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) {
@@ -1262,7 +1270,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--;
@@ -1585,6 +1593,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
+	opts.accept_pathspec = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1614,6 +1623,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	opts.no_dwim_new_local_branch = 0;
+	opts.accept_pathspec = 0;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 18/27] switch: reject "do nothing" case

"git checkout" can be executed without any arguments. What it does is not exactly great: it switches from HEAD to HEAD and shows worktree modification as a side effect.

Make switch reject this case. Just use "git status" if you want that side effect. For switch, you have to either

- really switch a branch
- (explicitly) detach from the current branch
- create a new branch
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 9 +++++++++
 1 file changed, 9 insertions(+)
Show changes to builtin/checkout.c +9 −0
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 0584dc484d..e9dfe38d69 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -55,6 +55,7 @@ struct checkout_opts {
 	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
@@ -1338,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;
@@ -1593,6 +1600,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	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);
@@ -1624,6 +1632,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	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);
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 19/27] switch: only allow explicit detached HEAD

"git checkout <commit>" will checkout the commit in question and detach HEAD from the current branch. It is naturally a right thing to do once you get git references. But detached HEAD is a scary concept to new users because we show a lot of warnings and stuff, and it could be hard to get out of (until you know better).

To keep switch a bit more friendly to new users, we only allow entering detached HEAD mode when --detach is given. "git switch" must take a branch (unless you create a new branch, then of course switch can take any commit-ish)

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)
Show changes to builtin/checkout.c +34 −0
diff --git a/builtin/checkout.c b/builtin/checkout.c
index e9dfe38d69..4416b82bfd 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -45,6 +45,7 @@ struct checkout_opts {
 	int merge;
 	int force;
 	int force_detach;
+	int implicit_detach;
 	int writeout_stage;
 	int overwrite_ignore;
 	int ignore_skipworktree;
@@ -1298,6 +1299,29 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)
 	return status;
 }
 
+static void die_expecting_a_branch(const struct branch_info *branch_info)
+{
+	struct object_id oid;
+	char *to_free;
+
+	if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free) == 1) {
+		const char *ref = to_free;
+
+		if (skip_prefix(ref, "refs/tags/", &ref))
+			die(_("a branch is expected, got tag '%s'"), ref);
+		if (skip_prefix(ref, "refs/remotes/", &ref))
+			die(_("a branch is expected, got remote branch '%s'"), ref);
+		die(_("a branch is expected, got '%s'"), ref);
+	}
+	if (branch_info->commit)
+		die(_("a branch is expected, got commit '%s'"), branch_info->name);
+	/*
+	 * This case should never happen because we already die() on
+	 * non-commit, but just in case.
+	 */
+	die(_("a branch is expected, got '%s'"), branch_info->name);
+}
+
 static int checkout_branch(struct checkout_opts *opts,
 			   struct branch_info *new_branch_info)
 {
@@ -1345,6 +1369,14 @@ static int checkout_branch(struct checkout_opts *opts,
 	    !opts->force_detach)
 		die(_("missing branch or commit argument"));
 
+	if (!opts->implicit_detach &&
+	    !opts->force_detach &&
+	    !opts->new_branch &&
+	    !opts->new_branch_force &&
+	    new_branch_info->name &&
+	    !new_branch_info->path)
+		die_expecting_a_branch(new_branch_info);
+
 	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
 	    !opts->ignore_other_worktrees) {
 		int flag;
@@ -1602,6 +1634,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.no_dwim_new_local_branch = 0;
 	opts.switch_branch_doing_nothing_is_ok = 1;
 	opts.accept_pathspec = 1;
+	opts.implicit_detach = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1633,6 +1666,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	opts.no_dwim_new_local_branch = 0;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
+	opts.implicit_detach = 0;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 20/27] switch: add short option for --detach

"git checkout" automatically detaches branches and --detach is not that useful (--no-detach is more likely). But for "switch", you may want to use it more often once you're used to detached HEAD. This of course adds -d to git-checkout but it does not harm (yet?) to do it.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/checkout.c +1 −1
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 4416b82bfd..11308ab80c 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1423,7 +1423,7 @@ static struct option *add_common_switch_branch_options(
 	struct checkout_opts *opts, struct option *prevopts)
 {
 	struct option options[] = {
-		OPT_BOOL(0, "detach", &opts->force_detach, N_("detach HEAD at named commit")),
+		OPT_BOOL('d', "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),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 21/27] switch: implicit dwim, use --no-guess to disable it

This is already the default in git-checkout. The real change in here is just minor cleanup. The main excuse is to explain why dwim is kept default.

Contrary to detach mode that is easy to get into and confusing to get back out. Automatically creating a tracking branch often does not kick in as often (you would need a branch of the same name on a remote). And since the branch creation is reported clearly, the user should be able to undo/delete it if it's unwanted.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt | 38 ++++++++++++++++++++--------------
 builtin/checkout.c             | 16 +++++++-------
 2 files changed, 31 insertions(+), 23 deletions(-)
Show changes to 2 files +31 −22

Documentation/git-checkout.txt, builtin/checkout.c

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index c7192bdefe..58f18a0842 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -31,22 +31,13 @@ branch.
 	`<branch>`.
 +
 If `<branch>` is not found but there does exist a tracking branch in
-exactly one remote (call it `<remote>`) with a matching name, treat as
-equivalent to
+exactly one remote (call it `<remote>`) with a matching name and
+`--no-guess` is not specified, treat as equivalent to
 +
 ------------
 $ git checkout -b <branch> --track <remote>/<branch>
 ------------
 +
-If the branch exists in multiple remotes and one of them is named by
-the `checkout.defaultRemote` configuration variable, we'll use that
-one for the purposes of disambiguation, even if the `<branch>` isn't
-unique across all remotes. Set it to
-e.g. `checkout.defaultRemote=origin` to always checkout remote
-branches from there if `<branch>` is ambiguous but exists on the
-'origin' remote. See also `checkout.defaultRemote` in
-linkgit:git-config[1].
-+
 You could omit `<branch>`, in which case the command degenerates to
 "check out the current branch", which is a glorified no-op with
 rather expensive side-effects to show only the tracking information,
@@ -183,6 +174,27 @@ explicitly give a name with `-b` in such a case.
 	Do not set up "upstream" configuration, even if the
 	`branch.autoSetupMerge` configuration variable is true.
 
+--guess::
+--no-guess::
+	If `<branch>` is not found but there does exist a tracking
+	branch in exactly one remote (call it `<remote>`) with a
+	matching name, treat as equivalent to
++
+------------
+$ git checkout -b <branch> --track <remote>/<branch>
+------------
++
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to
+e.g. `checkout.defaultRemote=origin` to always checkout remote
+branches from there if `<branch>` is ambiguous but exists on the
+'origin' remote. See also `checkout.defaultRemote` in
+linkgit:git-config[1].
++
+Use `--no-guess` to disable this.
+
 -l::
 	Create the new branch's reflog; see linkgit:git-branch[1] for
 	details.
@@ -287,10 +299,6 @@ Note that this option uses the no overlay mode by default (see also
 	Just like linkgit:git-submodule[1], this will detach `HEAD` of the
 	submodule.
 
---no-guess::
-	Do not attempt to create a branch if a remote tracking branch
-	of the same name exists.
-
 --overlay::
 --no-overlay::
 	In the default overlay mode, `git checkout` never
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 11308ab80c..ff9b96993d 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -53,7 +53,7 @@ struct checkout_opts {
 	int show_progress;
 	int count_checkout_paths;
 	int overlay_mode;
-	int no_dwim_new_local_branch;
+	int dwim_new_local_branch;
 	int discard_changes;
 	int accept_pathspec;
 	int switch_branch_doing_nothing_is_ok;
@@ -1430,8 +1430,6 @@ static struct option *add_common_switch_branch_options(
 		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL(0, "no-guess", &opts->no_dwim_new_local_branch,
-			 N_("second guess 'git checkout <no-such-branch>'")),
 		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
 		OPT_END()
@@ -1468,7 +1466,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 {
 	struct branch_info new_branch_info;
 	int dwim_remotes_matched = 0;
-	int dwim_new_local_branch;
 
 	memset(&new_branch_info, 0, sizeof(new_branch_info));
 	opts->overwrite_ignore = 1;
@@ -1483,7 +1480,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 	argc = parse_options(argc, argv, prefix, options, usagestr,
 			     PARSE_OPT_KEEP_DASHDASH);
 
-	dwim_new_local_branch = !opts->no_dwim_new_local_branch;
 	if (opts->show_progress < 0) {
 		if (opts->quiet)
 			opts->show_progress = 0;
@@ -1545,7 +1541,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 		struct object_id rev;
 		int dwim_ok =
 			!opts->patch_mode &&
-			dwim_new_local_branch &&
+			opts->dwim_new_local_branch &&
 			opts->track == BRANCH_TRACK_UNSPECIFIED &&
 			!opts->new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
@@ -1626,12 +1622,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		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, "guess", &opts.dwim_new_local_branch,
+			 N_("second guess 'git checkout <no-such-branch>' (default)")),
 		OPT_END()
 	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
-	opts.no_dwim_new_local_branch = 0;
+	opts.dwim_new_local_branch = 1;
 	opts.switch_branch_doing_nothing_is_ok = 1;
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
@@ -1656,6 +1654,8 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 			   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()
@@ -1663,7 +1663,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
-	opts.no_dwim_new_local_branch = 0;
+	opts.dwim_new_local_branch = 1;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
 	opts.implicit_detach = 0;
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 22/27] switch: no worktree status unless real branch switch happens

When we switch from one branch to another, it makes sense to show a summary of local changes since there could be conflicts, or some files left modified.... When switch is used solely for creating a new branch (and "switch" to the same commit) or detaching, we don't really need to show anything.

"git checkout" does it anyway for historical reasons. But we can start with a clean slate with switch and don't have to.

This essentially reverts fa655d8411 (checkout: optimize "git checkout -b <new_branch>" - 2018-08-16) and make it default for switch, but also for -B and --detach. Users of big repos are encouraged to move to switch.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/config/checkout.txt |   8 --
 builtin/checkout.c                | 134 ++----------------------------
 t/t1090-sparse-checkout-scope.sh  |  14 ----
 3 files changed, 8 insertions(+), 148 deletions(-)
Show changes to 3 files +8 −148

Documentation/config/checkout.txt, builtin/checkout.c, t/t1090-sparse-checkout-scope.sh

diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt
index d6872ffa83..6b646813ab 100644
--- a/Documentation/config/checkout.txt
+++ b/Documentation/config/checkout.txt
@@ -16,11 +16,3 @@ will checkout the '<something>' branch on another remote,
 and by linkgit:git-worktree[1] when 'git worktree add' refers to a
 remote branch. This setting might be used for other checkout-like
 commands or functionality in the future.
-
-checkout.optimizeNewBranch::
-	Optimizes the performance of "git checkout -b <new_branch>" when
-	using sparse-checkout.  When set to true, git will not update the
-	repo based on the current sparse-checkout settings.  This means it
-	will not update the skip-worktree bit in the index nor add/remove
-	files in the working directory to reflect the current sparse checkout
-	settings nor will it show the local changes.
diff --git a/builtin/checkout.c b/builtin/checkout.c
index ff9b96993d..f7967cdb7c 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -26,8 +26,6 @@
 #include "unpack-trees.h"
 #include "xdiff-interface.h"
 
-static int checkout_optimize_new_branch;
-
 static const char * const checkout_usage[] = {
 	N_("git checkout [<options>] <branch>"),
 	N_("git checkout [<options>] [<branch>] -- <file>..."),
@@ -57,11 +55,7 @@ struct checkout_opts {
 	int discard_changes;
 	int accept_pathspec;
 	int switch_branch_doing_nothing_is_ok;
-
-	/*
-	 * If new checkout options are added, skip_merge_working_tree
-	 * should be updated accordingly.
-	 */
+	int only_merge_on_switching_branches;
 
 	const char *new_branch;
 	const char *new_branch_force;
@@ -565,112 +559,6 @@ static void setup_branch_path(struct branch_info *branch)
 	branch->path = strbuf_detach(&buf, NULL);
 }
 
-/*
- * Skip merging the trees, updating the index and working directory if and
- * only if we are creating a new branch via "git checkout -b <new_branch>."
- */
-static int skip_merge_working_tree(const struct checkout_opts *opts,
-	const struct branch_info *old_branch_info,
-	const struct branch_info *new_branch_info)
-{
-	/*
-	 * Do the merge if sparse checkout is on and the user has not opted in
-	 * to the optimized behavior
-	 */
-	if (core_apply_sparse_checkout && !checkout_optimize_new_branch)
-		return 0;
-
-	/*
-	 * We must do the merge if we are actually moving to a new commit.
-	 */
-	if (!old_branch_info->commit || !new_branch_info->commit ||
-		!oideq(&old_branch_info->commit->object.oid,
-		       &new_branch_info->commit->object.oid))
-		return 0;
-
-	/*
-	 * opts->patch_mode cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * opts->quiet only impacts output so doesn't require a merge
-	 */
-
-	/*
-	 * Honor the explicit request for a three-way merge or to throw away
-	 * local changes
-	 */
-	if (opts->merge || opts->force)
-		return 0;
-
-	/*
-	 * --detach is documented as "updating the index and the files in the
-	 * working tree" but this optimization skips those steps so fall through
-	 * to the regular code path.
-	 */
-	if (opts->force_detach)
-		return 0;
-
-	/*
-	 * opts->writeout_stage cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * Honor the explicit ignore requests
-	 */
-	if (!opts->overwrite_ignore || opts->ignore_skipworktree ||
-		opts->ignore_other_worktrees)
-		return 0;
-
-	/*
-	 * opts->show_progress only impacts output so doesn't require a merge
-	 */
-
-	/*
-	 * opts->overlay_mode cannot be used with switching branches so is
-	 * not tested here
-	 */
-
-	/*
-	 * If we aren't creating a new branch any changes or updates will
-	 * happen in the existing branch.  Since that could only be updating
-	 * the index and working directory, we don't want to skip those steps
-	 * or we've defeated any purpose in running the command.
-	 */
-	if (!opts->new_branch)
-		return 0;
-
-	/*
-	 * new_branch_force is defined to "create/reset and checkout a branch"
-	 * so needs to go through the merge to do the reset
-	 */
-	if (opts->new_branch_force)
-		return 0;
-
-	/*
-	 * A new orphaned branch requrires the index and the working tree to be
-	 * adjusted to <start_point>
-	 */
-	if (opts->new_orphan_branch)
-		return 0;
-
-	/*
-	 * Remaining variables are not checkout options but used to track state
-	 */
-
-	 /*
-	  * Do the merge if this is the initial checkout. We cannot use
-	  * is_cache_unborn() here because the index hasn't been loaded yet
-	  * so cache_nr and timestamp.sec are always zero.
-	  */
-	if (!file_exists(get_index_file()))
-		return 0;
-
-	return 1;
-}
-
 static int merge_working_tree(const struct checkout_opts *opts,
 			      struct branch_info *old_branch_info,
 			      struct branch_info *new_branch_info,
@@ -1028,6 +916,7 @@ static int switch_branches(const struct checkout_opts *opts,
 	void *path_to_free;
 	struct object_id rev;
 	int flag, writeout_error = 0;
+	int do_merge = 1;
 
 	trace2_cmd_mode("branch");
 
@@ -1047,16 +936,12 @@ static int switch_branches(const struct checkout_opts *opts,
 		if (!new_branch_info->commit)
 			die(_("You are on a branch yet to be born"));
 		parse_commit_or_die(new_branch_info->commit);
+
+		if (opts->only_merge_on_switching_branches)
+			do_merge = 0;
 	}
 
-	/* optimize the "checkout -b <new_branch> path */
-	if (skip_merge_working_tree(opts, &old_branch_info, new_branch_info)) {
-		if (!checkout_optimize_new_branch && !opts->quiet) {
-			if (read_cache_preload(NULL) < 0)
-				return error(_("index file corrupt"));
-			show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
-		}
-	} else {
+	if (do_merge) {
 		ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
 		if (ret) {
 			free(path_to_free);
@@ -1076,11 +961,6 @@ static int switch_branches(const struct checkout_opts *opts,
 
 static int git_checkout_config(const char *var, const char *value, void *cb)
 {
-	if (!strcmp(var, "checkout.optimizenewbranch")) {
-		checkout_optimize_new_branch = git_config_bool(var, value);
-		return 0;
-	}
-
 	if (!strcmp(var, "diff.ignoresubmodules")) {
 		struct checkout_opts *opts = cb;
 		handle_ignore_submodules_arg(&opts->diff_options, value);
@@ -1631,6 +1511,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	memset(&opts, 0, sizeof(opts));
 	opts.dwim_new_local_branch = 1;
 	opts.switch_branch_doing_nothing_is_ok = 1;
+	opts.only_merge_on_switching_branches = 0;
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
 
@@ -1666,6 +1547,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	opts.dwim_new_local_branch = 1;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_is_ok = 0;
+	opts.only_merge_on_switching_branches = 1;
 	opts.implicit_detach = 0;
 
 	options = parse_options_dup(switch_options);
diff --git a/t/t1090-sparse-checkout-scope.sh b/t/t1090-sparse-checkout-scope.sh
index 090b7fc3d3..40cc004326 100755
--- a/t/t1090-sparse-checkout-scope.sh
+++ b/t/t1090-sparse-checkout-scope.sh
@@ -31,20 +31,6 @@ test_expect_success 'perform sparse checkout of master' '
 	test_path_is_file c
 '
 
-test_expect_success 'checkout -b checkout.optimizeNewBranch interaction' '
-	cp .git/info/sparse-checkout .git/info/sparse-checkout.bak &&
-	test_when_finished "
-		mv -f .git/info/sparse-checkout.bak .git/info/sparse-checkout
-		git checkout master
-	" &&
-	echo "/b" >>.git/info/sparse-checkout &&
-	test "$(git ls-files -t b)" = "S b" &&
-	git -c checkout.optimizeNewBranch=true checkout -b fast &&
-	test "$(git ls-files -t b)" = "S b" &&
-	git checkout -b slow &&
-	test "$(git ls-files -t b)" = "H b"
-'
-
 test_expect_success 'merge feature branch into sparse checkout of master' '
 	git merge feature &&
 	test_path_is_file a &&
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 23/27] switch: reject if some operation is in progress

Unless you know what you're doing, switching to another branch to do something then switching back could be confusing. Worse, you may even forget that you're in the middle of something. By the time you realize, you may have done a ton of work and it gets harder to go back.

A new option --ignore-in-progress was considered but dropped because it was not exactly clear what should happen. Sometimes you can switch away and get back safely and resume the operation. Sometimes not. And the git-checkout behavior is automatically clear merge/revert/cherry-pick, which makes it a bit even more confusing [1].

We may revisit and add this option in the future. But for now play it safe and not allow it (you can't even skip this check with --force). The user is suggested to cancel the operation by themselves (and hopefully they do consider the consequences, not blindly type the command), or to create a separate worktree instead of switching. The third option is the good old "git checkout", but it's not mentioned.

[1] CACsJy8Axa5WsLSjiscjnxVK6jQHkfs-gH959=YtUvQkWriAk5w@mail.gmail.com
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)
Show changes to builtin/checkout.c +40 −0
diff --git a/builtin/checkout.c b/builtin/checkout.c
index f7967cdb7c..5f100c1552 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -24,6 +24,7 @@
 #include "tree.h"
 #include "tree-walk.h"
 #include "unpack-trees.h"
+#include "wt-status.h"
 #include "xdiff-interface.h"
 
 static const char * const checkout_usage[] = {
@@ -56,6 +57,7 @@ struct checkout_opts {
 	int accept_pathspec;
 	int switch_branch_doing_nothing_is_ok;
 	int only_merge_on_switching_branches;
+	int can_switch_when_in_progress;
 
 	const char *new_branch;
 	const char *new_branch_force;
@@ -1202,6 +1204,39 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)
 	die(_("a branch is expected, got '%s'"), branch_info->name);
 }
 
+static void die_if_some_operation_in_progress(void)
+{
+	struct wt_status_state state;
+
+	memset(&state, 0, sizeof(state));
+	wt_status_get_state(the_repository, &state, 0);
+
+	if (state.merge_in_progress)
+		die(_("cannot switch branch while merging\n"
+		      "Consider \"git merge --quit\" "
+		      "or \"git worktree add\"."));
+	if (state.am_in_progress)
+		die(_("cannot switch branch in the middle of an am session\n"
+		      "Consider \"git am --quit\" "
+		      "or \"git worktree add\"."));
+	if (state.rebase_interactive_in_progress || state.rebase_in_progress)
+		die(_("cannot switch branch while rebasing\n"
+		      "Consider \"git rebase --quit\" "
+		      "or \"git worktree add\"."));
+	if (state.cherry_pick_in_progress)
+		die(_("cannot switch branch while cherry-picking\n"
+		      "Consider \"git cherry-pick --quit\" "
+		      "or \"git worktree add\"."));
+	if (state.revert_in_progress)
+		die(_("cannot switch branch while reverting\n"
+		      "Consider \"git revert --quit\" "
+		      "or \"git worktree add\"."));
+	if (state.bisect_in_progress)
+		die(_("cannot switch branch while bisecting\n"
+		      "Consider \"git bisect reset HEAD\" "
+		      "or \"git worktree add\"."));
+}
+
 static int checkout_branch(struct checkout_opts *opts,
 			   struct branch_info *new_branch_info)
 {
@@ -1257,6 +1292,9 @@ static int checkout_branch(struct checkout_opts *opts,
 	    !new_branch_info->path)
 		die_expecting_a_branch(new_branch_info);
 
+	if (!opts->can_switch_when_in_progress)
+		die_if_some_operation_in_progress();
+
 	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
 	    !opts->ignore_other_worktrees) {
 		int flag;
@@ -1514,6 +1552,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.only_merge_on_switching_branches = 0;
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
+	opts.can_switch_when_in_progress = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1549,6 +1588,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	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;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.21.0.479.g47ac719cd3
Elijah Newren· Mar 29, 2019, 15:47 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v6 23/27] switch: reject if some operation is in progress

On Fri, Mar 29, 2019 at 3:42 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 18 quoted lines
>
> Unless you know what you're doing, switching to another branch to do
> something then switching back could be confusing. Worse, you may even
> forget that you're in the middle of something. By the time you realize,
> you may have done a ton of work and it gets harder to go back.
>
> A new option --ignore-in-progress was considered but dropped because it
> was not exactly clear what should happen. Sometimes you can switch away
> and get back safely and resume the operation. Sometimes not. And the
> git-checkout behavior is automatically clear merge/revert/cherry-pick,
> which makes it a bit even more confusing [1].
>
> We may revisit and add this option in the future. But for now play it
> safe and not allow it (you can't even skip this check with --force). The
> user is suggested to cancel the operation by themselves (and hopefully
> they do consider the consequences, not blindly type the command), or to
> create a separate worktree instead of switching. The third option is
> the good old "git checkout", but it's not mentioned.
I think these safety checks are pretty important for checkout too...
Show 83 quoted lines
>
> [1] CACsJy8Axa5WsLSjiscjnxVK6jQHkfs-gH959=YtUvQkWriAk5w@mail.gmail.com
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  builtin/checkout.c | 40 ++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 40 insertions(+)
>
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index f7967cdb7c..5f100c1552 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -24,6 +24,7 @@
>  #include "tree.h"
>  #include "tree-walk.h"
>  #include "unpack-trees.h"
> +#include "wt-status.h"
>  #include "xdiff-interface.h"
>
>  static const char * const checkout_usage[] = {
> @@ -56,6 +57,7 @@ struct checkout_opts {
>         int accept_pathspec;
>         int switch_branch_doing_nothing_is_ok;
>         int only_merge_on_switching_branches;
> +       int can_switch_when_in_progress;
>
>         const char *new_branch;
>         const char *new_branch_force;
> @@ -1202,6 +1204,39 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)
>         die(_("a branch is expected, got '%s'"), branch_info->name);
>  }
>
> +static void die_if_some_operation_in_progress(void)
> +{
> +       struct wt_status_state state;
> +
> +       memset(&state, 0, sizeof(state));
> +       wt_status_get_state(the_repository, &state, 0);
> +
> +       if (state.merge_in_progress)
> +               die(_("cannot switch branch while merging\n"
> +                     "Consider \"git merge --quit\" "
> +                     "or \"git worktree add\"."));
> +       if (state.am_in_progress)
> +               die(_("cannot switch branch in the middle of an am session\n"
> +                     "Consider \"git am --quit\" "
> +                     "or \"git worktree add\"."));
> +       if (state.rebase_interactive_in_progress || state.rebase_in_progress)
> +               die(_("cannot switch branch while rebasing\n"
> +                     "Consider \"git rebase --quit\" "
> +                     "or \"git worktree add\"."));
> +       if (state.cherry_pick_in_progress)
> +               die(_("cannot switch branch while cherry-picking\n"
> +                     "Consider \"git cherry-pick --quit\" "
> +                     "or \"git worktree add\"."));
> +       if (state.revert_in_progress)
> +               die(_("cannot switch branch while reverting\n"
> +                     "Consider \"git revert --quit\" "
> +                     "or \"git worktree add\"."));
> +       if (state.bisect_in_progress)
> +               die(_("cannot switch branch while bisecting\n"
> +                     "Consider \"git bisect reset HEAD\" "
> +                     "or \"git worktree add\"."));
> +}
> +
>  static int checkout_branch(struct checkout_opts *opts,
>                            struct branch_info *new_branch_info)
>  {
> @@ -1257,6 +1292,9 @@ static int checkout_branch(struct checkout_opts *opts,
>             !new_branch_info->path)
>                 die_expecting_a_branch(new_branch_info);
>
> +       if (!opts->can_switch_when_in_progress)
> +               die_if_some_operation_in_progress();
> +
>         if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
>             !opts->ignore_other_worktrees) {
>                 int flag;
> @@ -1514,6 +1552,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
>         opts.only_merge_on_switching_branches = 0;
>         opts.accept_pathspec = 1;
>         opts.implicit_detach = 1;
> +       opts.can_switch_when_in_progress = 1;

I think this should be 0 too; this check is good for both checkout and switch. And if people really do want to use it during in-progress operations because it is sometimes safe enough, then both operations deserve some kind of override flag that checks for the appropriate safety conditions (as we're discussing in the other thread) and then allows or rejects it.

However, I'm totally fine with proposing another patch after your series lands to do all of this; this patch is fine as-is for now.

Show 12 quoted lines
>         options = parse_options_dup(checkout_options);
>         options = add_common_options(&opts, options);
> @@ -1549,6 +1588,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
>         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;
>
>         options = parse_options_dup(switch_options);
>         options = add_common_options(&opts, options);
> --
> 2.21.0.479.g47ac719cd3
Phillip Wood· Apr 25, 2019, 10:33 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v6 23/27] switch: reject if some operation is in progress

On 29/03/2019 10:39, Nguyễn Thái Ngọc Duy wrote:
Show 13 quoted lines
> Unless you know what you're doing, switching to another branch to do
> something then switching back could be confusing. Worse, you may even
> forget that you're in the middle of something. By the time you realize,
> you may have done a ton of work and it gets harder to go back.
> 
> A new option --ignore-in-progress was considered but dropped because it
> was not exactly clear what should happen. Sometimes you can switch away
> and get back safely and resume the operation. Sometimes not. And the
> git-checkout behavior is automatically clear merge/revert/cherry-pick,
> which makes it a bit even more confusing [1].
> 
> We may revisit and add this option in the future. But for now play it
> safe and not allow it (you can't even skip this check with --force).

I think this is a good compromise, lets see how it goes (I think I broadly agree with Elijah's suggestion to allow the switch if we can safely switch back again if we want to add --ignore-in-progress in the future).

Show 48 quoted lines
> The
> user is suggested to cancel the operation by themselves (and hopefully
> they do consider the consequences, not blindly type the command), or to
> create a separate worktree instead of switching. The third option is
> the good old "git checkout", but it's not mentioned.
> 
> [1] CACsJy8Axa5WsLSjiscjnxVK6jQHkfs-gH959=YtUvQkWriAk5w@mail.gmail.com
> 
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  builtin/checkout.c | 40 ++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 40 insertions(+)
> 
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index f7967cdb7c..5f100c1552 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -24,6 +24,7 @@
>  #include "tree.h"
>  #include "tree-walk.h"
>  #include "unpack-trees.h"
> +#include "wt-status.h"
>  #include "xdiff-interface.h"
>  
>  static const char * const checkout_usage[] = {
> @@ -56,6 +57,7 @@ struct checkout_opts {
>  	int accept_pathspec;
>  	int switch_branch_doing_nothing_is_ok;
>  	int only_merge_on_switching_branches;
> +	int can_switch_when_in_progress;
>  
>  	const char *new_branch;
>  	const char *new_branch_force;
> @@ -1202,6 +1204,39 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)
>  	die(_("a branch is expected, got '%s'"), branch_info->name);
>  }
>  
> +static void die_if_some_operation_in_progress(void)
> +{
> +	struct wt_status_state state;
> +
> +	memset(&state, 0, sizeof(state));
> +	wt_status_get_state(the_repository, &state, 0);
> +
> +	if (state.merge_in_progress)
> +		die(_("cannot switch branch while merging\n"
> +		      "Consider \"git merge --quit\" "
> +		      "or \"git worktree add\"."));

I'm not sure merge --quit exists, 'git grep \"quit origin/pu' shows matches for builtin/{am.c,rebase.c,revert.c}. The --quit option for the sequencer command does not touch the index or working tree (that's the difference between --quit and --abort) so the switch can still fail due changes in the index and worktree that would be overwritten by the switch.

Best Wishes
Phillip
Show 52 quoted lines
> +	if (state.am_in_progress)
> +		die(_("cannot switch branch in the middle of an am session\n"
> +		      "Consider \"git am --quit\" "
> +		      "or \"git worktree add\"."));
> +	if (state.rebase_interactive_in_progress || state.rebase_in_progress)
> +		die(_("cannot switch branch while rebasing\n"
> +		      "Consider \"git rebase --quit\" "
> +		      "or \"git worktree add\"."));
> +	if (state.cherry_pick_in_progress)
> +		die(_("cannot switch branch while cherry-picking\n"
> +		      "Consider \"git cherry-pick --quit\" "
> +		      "or \"git worktree add\"."));
> +	if (state.revert_in_progress)
> +		die(_("cannot switch branch while reverting\n"
> +		      "Consider \"git revert --quit\" "
> +		      "or \"git worktree add\"."));
> +	if (state.bisect_in_progress)
> +		die(_("cannot switch branch while bisecting\n"
> +		      "Consider \"git bisect reset HEAD\" "
> +		      "or \"git worktree add\"."));
> +}
> +
>  static int checkout_branch(struct checkout_opts *opts,
>  			   struct branch_info *new_branch_info)
>  {
> @@ -1257,6 +1292,9 @@ static int checkout_branch(struct checkout_opts *opts,
>  	    !new_branch_info->path)
>  		die_expecting_a_branch(new_branch_info);
>  
> +	if (!opts->can_switch_when_in_progress)
> +		die_if_some_operation_in_progress();
> +
>  	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
>  	    !opts->ignore_other_worktrees) {
>  		int flag;
> @@ -1514,6 +1552,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
>  	opts.only_merge_on_switching_branches = 0;
>  	opts.accept_pathspec = 1;
>  	opts.implicit_detach = 1;
> +	opts.can_switch_when_in_progress = 1;
>  
>  	options = parse_options_dup(checkout_options);
>  	options = add_common_options(&opts, options);
> @@ -1549,6 +1588,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
>  	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;
>  
>  	options = parse_options_dup(switch_options);
>  	options = add_common_options(&opts, options);
> 
Duy Nguyen· Apr 29, 2019, 09:16 UTC · re: Phillip Wood · lore

Re: [PATCH v6 23/27] switch: reject if some operation is in progress

On Thu, Apr 25, 2019 at 5:33 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 20 quoted lines
>
> On 29/03/2019 10:39, Nguyễn Thái Ngọc Duy wrote:
> > Unless you know what you're doing, switching to another branch to do
> > something then switching back could be confusing. Worse, you may even
> > forget that you're in the middle of something. By the time you realize,
> > you may have done a ton of work and it gets harder to go back.
> >
> > A new option --ignore-in-progress was considered but dropped because it
> > was not exactly clear what should happen. Sometimes you can switch away
> > and get back safely and resume the operation. Sometimes not. And the
> > git-checkout behavior is automatically clear merge/revert/cherry-pick,
> > which makes it a bit even more confusing [1].
> >
> > We may revisit and add this option in the future. But for now play it
> > safe and not allow it (you can't even skip this check with --force).
>
> I think this is a good compromise, lets see how it goes (I think I
> broadly agree with Elijah's suggestion to allow the switch if we can
> safely switch back again if we want to add --ignore-in-progress in the
> future).

I probably will revisit this topic much sooner than I thought. I did a bisect today and found out "git switch" would not let me choose some "random" commit to test, which I suspected more likely where the problem was, or at least helped reduce the bisect steps. I had to go back to "git checkout" and was not so happy.

This probably falls under the "safe to switch" (and not even back) category, as long as switching does not destroy any data, since bisect is basically jumping between commits with a clean worktree/index until you find the right one.

Show 17 quoted lines
> > +static void die_if_some_operation_in_progress(void)
> > +{
> > +     struct wt_status_state state;
> > +
> > +     memset(&state, 0, sizeof(state));
> > +     wt_status_get_state(the_repository, &state, 0);
> > +
> > +     if (state.merge_in_progress)
> > +             die(_("cannot switch branch while merging\n"
> > +                   "Consider \"git merge --quit\" "
> > +                   "or \"git worktree add\"."));
>
> I'm not sure merge --quit exists, 'git grep \"quit origin/pu' shows
> matches for builtin/{am.c,rebase.c,revert.c}. The --quit option for the
> sequencer command does not touch the index or working tree (that's the
> difference between --quit and --abort) so the switch can still fail due
> changes in the index and worktree that would be overwritten by the switch.
Eck! Let me check if --abort is the same thing there or we need to add --quit...
-- 
Duy
Phillip Wood· Apr 29, 2019, 15:10 UTC · re: Duy Nguyen · lore

Re: [PATCH v6 23/27] switch: reject if some operation is in progress

On 29/04/2019 10:16, Duy Nguyen wrote:
Show 27 quoted lines
> On Thu, Apr 25, 2019 at 5:33 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
>>
>> On 29/03/2019 10:39, Nguyễn Thái Ngọc Duy wrote:
>>> Unless you know what you're doing, switching to another branch to do
>>> something then switching back could be confusing. Worse, you may even
>>> forget that you're in the middle of something. By the time you realize,
>>> you may have done a ton of work and it gets harder to go back.
>>>
>>> A new option --ignore-in-progress was considered but dropped because it
>>> was not exactly clear what should happen. Sometimes you can switch away
>>> and get back safely and resume the operation. Sometimes not. And the
>>> git-checkout behavior is automatically clear merge/revert/cherry-pick,
>>> which makes it a bit even more confusing [1].
>>>
>>> We may revisit and add this option in the future. But for now play it
>>> safe and not allow it (you can't even skip this check with --force).
>>
>> I think this is a good compromise, lets see how it goes (I think I
>> broadly agree with Elijah's suggestion to allow the switch if we can
>> safely switch back again if we want to add --ignore-in-progress in the
>> future).
> 
> I probably will revisit this topic much sooner than I thought. I did a
> bisect today and found out "git switch" would not let me choose some
> "random" commit to test, which I suspected more likely where the
> problem was, or at least helped reduce the bisect steps. I had to go
> back to "git checkout" and was not so happy.
Oh that's a pain, but should be safe as you describe below.
Show 22 quoted lines
> This probably falls under the "safe to switch" (and not even back)
> category, as long as switching does not destroy any data, since bisect
> is basically jumping between commits with a clean worktree/index until
> you find the right one. >>> +static void die_if_some_operation_in_progress(void)
>>> +{
>>> +     struct wt_status_state state;
>>> +
>>> +     memset(&state, 0, sizeof(state));
>>> +     wt_status_get_state(the_repository, &state, 0);
>>> +
>>> +     if (state.merge_in_progress)
>>> +             die(_("cannot switch branch while merging\n"
>>> +                   "Consider \"git merge --quit\" "
>>> +                   "or \"git worktree add\"."));
>>
>> I'm not sure merge --quit exists, 'git grep \"quit origin/pu' shows
>> matches for builtin/{am.c,rebase.c,revert.c}. The --quit option for the
>> sequencer command does not touch the index or working tree (that's the
>> difference between --quit and --abort) so the switch can still fail due
>> changes in the index and worktree that would be overwritten by the switch.
> 
> Eck! Let me check if --abort is the same thing there or we need to add --quit...

I think the --quit options generally leave the index and worktree alone, they just remove the state files whereas abort resets the index and worktree (with reset --mixed for merge and cherry-pick/revert, I think rebase does reset --hard) and also rewinds HEAD for rebases and sequences of cherry-picks and revert but that wont apply to merges as they are one-shot operations.

Best Wishes
Phillip
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 24/27] switch: make --orphan switch to an empty tree

Switching and creating branches always involves knowing the <start-point> to begin the new branch from. Sometimes, people want to create a new branch that does not have any commits yet; --orphan is a flag to allow that.

--orphan overrides the default of HEAD for <start-point> instead causing us to start from an empty history with all tracked files removed from the index and working tree. The use of --orphan is incompatible with specifying a <start-point>.

A note on the implementation. An alternative is just create a dummy commit in-core with empty tree and switch to it. But there's a chance the commit's SHA-1 may end up somewhere permanent like reflog. It's best to make sure "commit" pointer is NULL to avoid it.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 39 +++++++++++++++++++++++++++++++--------
 1 file changed, 31 insertions(+), 8 deletions(-)
Show changes to builtin/checkout.c +31 −8
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 5f100c1552..0351735c6e 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -58,6 +58,7 @@ struct checkout_opts {
 	int switch_branch_doing_nothing_is_ok;
 	int only_merge_on_switching_branches;
 	int can_switch_when_in_progress;
+	int orphan_from_empty_tree;
 
 	const char *new_branch;
 	const char *new_branch_force;
@@ -568,15 +569,21 @@ static int merge_working_tree(const struct checkout_opts *opts,
 {
 	int ret;
 	struct lock_file lock_file = LOCK_INIT;
+	struct tree *new_tree;
 
 	hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
 	if (read_cache_preload(NULL) < 0)
 		return error(_("index file corrupt"));
 
 	resolve_undo_clear();
+	if (opts->new_orphan_branch && opts->orphan_from_empty_tree) {
+		if (new_branch_info->commit)
+			BUG("'switch --orphan' should never accept a commit as starting point");
+		new_tree = parse_tree_indirect(the_hash_algo->empty_tree);
+	} else
+		new_tree = get_commit_tree(new_branch_info->commit);
 	if (opts->discard_changes) {
-		ret = reset_tree(get_commit_tree(new_branch_info->commit),
-				 opts, 1, writeout_error);
+		ret = reset_tree(new_tree, opts, 1, writeout_error);
 		if (ret)
 			return ret;
 	} else {
@@ -614,7 +621,8 @@ static int merge_working_tree(const struct checkout_opts *opts,
 					   &old_branch_info->commit->object.oid :
 					   the_hash_algo->empty_tree);
 		init_tree_desc(&trees[0], tree->buffer, tree->size);
-		tree = parse_tree_indirect(&new_branch_info->commit->object.oid);
+		parse_tree(new_tree);
+		tree = new_tree;
 		init_tree_desc(&trees[1], tree->buffer, tree->size);
 
 		ret = unpack_trees(2, trees, &topts);
@@ -663,7 +671,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			o.verbosity = 0;
 			work = write_tree_from_memory(&o);
 
-			ret = reset_tree(get_commit_tree(new_branch_info->commit),
+			ret = reset_tree(new_tree,
 					 opts, 1,
 					 writeout_error);
 			if (ret)
@@ -672,13 +680,13 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			o.branch1 = new_branch_info->name;
 			o.branch2 = "local";
 			ret = merge_trees(&o,
-					  get_commit_tree(new_branch_info->commit),
+					  new_tree,
 					  work,
 					  get_commit_tree(old_branch_info->commit),
 					  &result);
 			if (ret < 0)
 				exit(128);
-			ret = reset_tree(get_commit_tree(new_branch_info->commit),
+			ret = reset_tree(new_tree,
 					 opts, 0,
 					 writeout_error);
 			strbuf_release(&o.obuf);
@@ -696,7 +704,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->discard_changes && !opts->quiet)
+	if (!opts->discard_changes && !opts->quiet && new_branch_info->commit)
 		show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
 
 	return 0;
@@ -897,7 +905,10 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne
 	add_pending_object(&revs, object, oid_to_hex(&object->oid));
 
 	for_each_ref(add_pending_uninteresting_ref, &revs);
-	add_pending_oid(&revs, "HEAD", &new_commit->object.oid, UNINTERESTING);
+	if (new_commit)
+		add_pending_oid(&revs, "HEAD",
+				&new_commit->object.oid,
+				UNINTERESTING);
 
 	if (prepare_revision_walk(&revs))
 		die(_("internal error in revision walk"));
@@ -932,6 +943,14 @@ static int switch_branches(const struct checkout_opts *opts,
 	if (old_branch_info.path)
 		skip_prefix(old_branch_info.path, "refs/heads/", &old_branch_info.name);
 
+	if (opts->new_orphan_branch && opts->orphan_from_empty_tree) {
+		if (new_branch_info->name)
+			BUG("'switch --orphan' should never accept a commit as starting point");
+		new_branch_info->commit = NULL;
+		new_branch_info->name = "(empty)";
+		do_merge = 1;
+	}
+
 	if (!new_branch_info->name) {
 		new_branch_info->name = "HEAD";
 		new_branch_info->commit = old_branch_info.commit;
@@ -1268,6 +1287,8 @@ static int checkout_branch(struct checkout_opts *opts,
 	if (opts->new_orphan_branch) {
 		if (opts->track != BRANCH_TRACK_UNSPECIFIED)
 			die(_("'%s' cannot be used with '%s'"), "--orphan", "-t");
+		if (opts->orphan_from_empty_tree && new_branch_info->name)
+			die(_("'%s' cannot take <start-point>"), "--orphan");
 	} else if (opts->force_detach) {
 		if (opts->track != BRANCH_TRACK_UNSPECIFIED)
 			die(_("'%s' cannot be used with '%s'"), "--detach", "-t");
@@ -1553,6 +1574,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
 	opts.can_switch_when_in_progress = 1;
+	opts.orphan_from_empty_tree = 0;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1589,6 +1611,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	opts.only_merge_on_switching_branches = 1;
 	opts.implicit_detach = 0;
 	opts.can_switch_when_in_progress = 0;
+	opts.orphan_from_empty_tree = 1;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 25/27] t: add tests for switch

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 t/t2060-switch.sh (new +x) | 96 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 96 insertions(+)
Show changes to t/t2060-switch.sh +96 −0
diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
new file mode 100755
index 0000000000..f9efa29dfb
--- /dev/null
+++ b/t/t2060-switch.sh
@@ -0,0 +1,96 @@
+#!/bin/sh
+
+test_description='switch basic functionality'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit first &&
+	git branch first-branch &&
+	test_commit second &&
+	test_commit third &&
+	git remote add origin nohost:/nopath &&
+	git update-ref refs/remotes/origin/foo first-branch
+'
+
+test_expect_success 'switch branch no arguments' '
+	test_must_fail git switch
+'
+
+test_expect_success 'switch branch' '
+	git switch first-branch &&
+	test_path_is_missing second.t
+'
+
+test_expect_success 'switch and detach' '
+	test_when_finished git switch master &&
+	test_must_fail git switch master^{commit} &&
+	git switch --detach master^{commit} &&
+	test_must_fail git symbolic-ref HEAD
+'
+
+test_expect_success 'switch and detach current branch' '
+	test_when_finished git switch master &&
+	git switch master &&
+	git switch --detach &&
+	test_must_fail git symbolic-ref HEAD
+'
+
+test_expect_success 'switch and create branch' '
+	test_when_finished git switch master &&
+	git switch -c temp master^ &&
+	test_cmp_rev master^ refs/heads/temp &&
+	echo refs/heads/temp >expected-branch &&
+	git symbolic-ref HEAD >actual-branch &&
+	test_cmp expected-branch actual-branch
+'
+
+test_expect_success 'force create branch from HEAD' '
+	test_when_finished git switch master &&
+	git switch --detach master &&
+	test_must_fail git switch -c temp &&
+	git switch -C temp &&
+	test_cmp_rev master refs/heads/temp &&
+	echo refs/heads/temp >expected-branch &&
+	git symbolic-ref HEAD >actual-branch &&
+	test_cmp expected-branch actual-branch
+'
+
+test_expect_success 'new orphan branch from empty' '
+	test_when_finished git switch master &&
+	test_must_fail git switch --orphan new-orphan HEAD &&
+	git switch --orphan new-orphan &&
+	test_commit orphan &&
+	git cat-file commit refs/heads/new-orphan >commit &&
+	! grep ^parent commit &&
+	git ls-files >tracked-files &&
+	echo orphan.t >expected &&
+	test_cmp expected tracked-files
+'
+
+test_expect_success 'switching ignores file of same branch name' '
+	test_when_finished git switch master &&
+	: >first-branch &&
+	git switch first-branch &&
+	echo refs/heads/first-branch >expected &&
+	git symbolic-ref HEAD >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'guess and create branch ' '
+	test_when_finished git switch master &&
+	test_must_fail git switch --no-guess foo &&
+	git switch foo &&
+	echo refs/heads/foo >expected &&
+	git symbolic-ref HEAD >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'not switching when something is in progress' '
+	test_when_finished rm -f .git/MERGE_HEAD &&
+	# fake a merge-in-progress
+	cp .git/HEAD .git/MERGE_HEAD &&
+	test_must_fail git switch -d @^
+'
+
+test_done
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 26/27] completion: support switch

Completion support for --guess could be made better. If no --detach is given, we should only provide a list of refs/heads/* and dwim ones, not the entire ref space. But I still can't penetrate that __git_refs() function yet.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 contrib/completion/git-completion.bash | 37 +++++++++++++++++++++++++-
 1 file changed, 36 insertions(+), 1 deletion(-)
Show changes to contrib/completion/git-completion.bash +36 −1
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 976e4a6548..b24bc48276 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -37,7 +37,8 @@
 #   GIT_COMPLETION_CHECKOUT_NO_GUESS
 #
 #     When set to "1", do not include "DWIM" suggestions in git-checkout
-#     completion (e.g., completing "foo" when "origin/foo" exists).
+#     and git-switch completion (e.g., completing "foo" when "origin/foo"
+#     exists).
 
 case "$COMP_WORDBREAKS" in
 *:*) : great ;;
@@ -2158,6 +2159,40 @@ _git_status ()
 	__git_complete_index_file "$complete_opt"
 }
 
+_git_switch ()
+{
+	case "$cur" in
+	--conflict=*)
+		__gitcomp "diff3 merge" "" "${cur##--conflict=}"
+		;;
+	--*)
+		__gitcomp_builtin switch
+		;;
+	*)
+		# check if --track, --no-track, or --no-guess was specified
+		# if so, disable DWIM mode
+		local track_opt="--track" only_local_ref=n
+		if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ] ||
+		   [ -n "$(__git_find_on_cmdline "--track --no-track --no-guess")" ]; then
+			track_opt=''
+		fi
+		# explicit --guess enables DWIM mode regardless of
+		# $GIT_COMPLETION_CHECKOUT_NO_GUESS
+		if [ -n "$(__git_find_on_cmdline "--guess")" ]; then
+			track_opt='--track'
+		fi
+		if [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then
+			only_local_ref=y
+		fi
+		if [ $only_local_ref = y -a -z "$track_opt" ]; then
+			__gitcomp_direct "$(__git_heads "" "$cur" " ")"
+		else
+			__git_complete_refs $track_opt
+		fi
+		;;
+	esac
+}
+
 __git_config_get_set_variables ()
 {
 	local prevword word config_file= c=$cword
-- 
2.21.0.479.g47ac719cd3
Nguyễn Thái Ngọc Duy· Mar 29, 2019, 10:39 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH v6 27/27] doc: promote "git switch"

The new command "git switch" is added to avoid the confusion of one-command-do-all "git checkout" for new users. They are also helpful to avoid ambiguation context.

For these reasons, promote it everywhere possible. This includes documentation, suggestions/advice from other commands...

The "Checking out files" progress line in unpack-trees.c is also updated to "Updating files" to be neutral to both git-checkout and git-switch.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-branch.txt           | 12 +++---
 Documentation/git-check-ref-format.txt |  3 +-
 Documentation/git-format-patch.txt     |  2 +-
 Documentation/git-merge-base.txt       |  2 +-
 Documentation/git-rebase.txt           |  2 +-
 Documentation/git-remote.txt           |  2 +-
 Documentation/git-rerere.txt           | 10 ++---
 Documentation/git-reset.txt            | 20 ++++-----
 Documentation/git-stash.txt            |  9 +++--
 Documentation/gitcore-tutorial.txt     | 19 +++++----
 Documentation/giteveryday.txt          | 24 +++++------
 Documentation/gittutorial.txt          |  4 +-
 Documentation/gitworkflows.txt         |  3 +-
 Documentation/revisions.txt            |  2 +-
 Documentation/user-manual.txt          | 56 +++++++++++++-------------
 advice.c                               | 12 +++---
 sha1-name.c                            |  2 +-
 t/t2020-checkout-detach.sh             | 24 +++++------
 unpack-trees.c                         |  2 +-
 19 files changed, 105 insertions(+), 105 deletions(-)
Show changes to 19 files +105 −105

Documentation/git-branch.txt, Documentation/git-check-ref-format.txt, Documentation/git-format-patch.txt, Documentation/git-merge-base.txt, Documentation/git-rebase.txt, Documentation/git-remote.txt, Documentation/git-rerere.txt, Documentation/git-reset.txt, Documentation/git-stash.txt, Documentation/gitcore-tutorial.txt, Documentation/giteveryday.txt, Documentation/gittutorial.txt, Documentation/gitworkflows.txt, Documentation/revisions.txt, Documentation/user-manual.txt, advice.c, sha1-name.c, t/t2020-checkout-detach.sh, unpack-trees.c

diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 0cd87ddeff..1e2d89b174 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -48,7 +48,7 @@ The command's second form creates a new branch head named <branchname>
 which points to the current `HEAD`, or <start-point> if given.
 
 Note that this will create the new branch, but it will not switch the
-working tree to it; use "git checkout <newbranch>" to switch to the
+working tree to it; use "git switch <newbranch>" to switch to the
 new branch.
 
 When a local branch is started off a remote-tracking branch, Git sets up the
@@ -198,7 +198,7 @@ This option is only applicable in non-verbose mode.
 +
 This behavior is the default when the start point is a remote-tracking branch.
 Set the branch.autoSetupMerge configuration variable to `false` if you
-want `git checkout` and `git branch` to always behave as if `--no-track`
+want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
 were given. Set it to `always` if you want this behavior when the
 start-point is either a local or remote-tracking branch.
 
@@ -297,7 +297,7 @@ Start development from a known tag::
 $ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6
 $ cd my2.6
 $ git branch my2.6.14 v2.6.14   <1>
-$ git checkout my2.6.14
+$ git switch my2.6.14
 ------------
 +
 <1> This step and the next one could be combined into a single step with
@@ -322,9 +322,9 @@ $ git branch -D test                                    <2>
 NOTES
 -----
 
-If you are creating a branch that you want to checkout immediately, it is
-easier to use the git checkout command with its `-b` option to create
-a branch and check it out with a single command.
+If you are creating a branch that you want to switch to immediately,
+it is easier to use the "git switch" command with its `-c` option to
+do the same thing with a single command.
 
 The options `--contains`, `--no-contains`, `--merged` and `--no-merged`
 serve four related but different purposes:
diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt
index d9de992585..ee6a4144fb 100644
--- a/Documentation/git-check-ref-format.txt
+++ b/Documentation/git-check-ref-format.txt
@@ -88,7 +88,8 @@ but it is explicitly forbidden at the beginning of a branch name).
 When run with `--branch` option in a repository, the input is first
 expanded for the ``previous checkout syntax''
 `@{-n}`.  For example, `@{-1}` is a way to refer the last thing that
-was checked out using "git checkout" operation. This option should be
+was checked out using "git switch" or "git checkout" operation.
+This option should be
 used by porcelains to accept this syntax anywhere a branch name is
 expected, so they can act as if you typed the branch name. As an
 exception note that, the ``previous checkout operation'' might result
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 1af85d404f..0a24a5679e 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -421,7 +421,7 @@ One way to test if your MUA is set up correctly is:
 * Apply it:
 
     $ git fetch <project> master:test-apply
-    $ git checkout test-apply
+    $ git switch test-apply
     $ git reset --hard
     $ git am a.patch
 
diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt
index 9f07f4f6ed..261d5c1164 100644
--- a/Documentation/git-merge-base.txt
+++ b/Documentation/git-merge-base.txt
@@ -149,7 +149,7 @@ instead.
 Discussion on fork-point mode
 -----------------------------
 
-After working on the `topic` branch created with `git checkout -b
+After working on the `topic` branch created with `git switch -c
 topic origin/master`, the history of remote-tracking branch
 `origin/master` may have been rewound and rebuilt, leading to a
 history of this shape:
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 5629ba4c5d..cb6fc166e2 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -17,7 +17,7 @@ SYNOPSIS
 DESCRIPTION
 -----------
 If <branch> is specified, 'git rebase' will perform an automatic
-`git checkout <branch>` before doing anything else.  Otherwise
+`git switch <branch>` before doing anything else.  Otherwise
 it remains on the current branch.
 
 If <upstream> is not specified, the upstream configured in
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index 0cad37fb81..9659abbf8e 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -230,7 +230,7 @@ $ git branch -r
   staging/master
   staging/staging-linus
   staging/staging-next
-$ git checkout -b staging staging/master
+$ git switch -c staging staging/master
 ...
 ------------
 
diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt
index df310d2a58..fe4434ad9d 100644
--- a/Documentation/git-rerere.txt
+++ b/Documentation/git-rerere.txt
@@ -91,7 +91,7 @@ For such a test, you need to merge master and topic somehow.
 One way to do it is to pull master into the topic branch:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 
               o---*---o---+ topic
@@ -113,10 +113,10 @@ the upstream might have been advanced since the test merge `+`,
 in which case the final commit graph would look like this:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 	$ ... work on both topic and master branches
-	$ git checkout master
+	$ git switch master
 	$ git merge topic
 
               o---*---o---+---o---o topic
@@ -136,11 +136,11 @@ merges, you could blow away the test merge, and keep building on
 top of the tip before the test merge:
 
 ------------
-	$ git checkout topic
+	$ git switch topic
 	$ git merge master
 	$ git reset --hard HEAD^ ;# rewind the test merge
 	$ ... work on both topic and master branches
-	$ git checkout master
+	$ git switch master
 	$ git merge topic
 
               o---*---o-------o---o topic
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 132f8e55f6..cbf901efb4 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -149,9 +149,9 @@ See also the `--amend` option to linkgit:git-commit[1].
 Undo a commit, making it a topic branch::
 +
 ------------
-$ git branch topic/wip     <1>
-$ git reset --hard HEAD~3  <2>
-$ git checkout topic/wip   <3>
+$ git branch topic/wip          <1>
+$ git reset --hard HEAD~3       <2>
+$ git switch topic/wip          <3>
 ------------
 +
 <1> You have made some commits, but realize they were premature
@@ -232,13 +232,13 @@ working tree are not in any shape to be committed yet, but you
 need to get to the other branch for a quick bugfix.
 +
 ------------
-$ git checkout feature ;# you were working in "feature" branch and
-$ work work work       ;# got interrupted
+$ git switch feature  ;# you were working in "feature" branch and
+$ work work work      ;# got interrupted
 $ git commit -a -m "snapshot WIP"                 <1>
-$ git checkout master
+$ git switch master
 $ fix fix fix
 $ git commit ;# commit with real log
-$ git checkout feature
+$ git switch feature
 $ git reset --soft HEAD^ ;# go back to WIP state  <2>
 $ git reset                                       <3>
 ------------
@@ -279,18 +279,18 @@ reset it while keeping the changes in your working tree.
 +
 ------------
 $ git tag start
-$ git checkout -b branch1
+$ git switch -c branch1
 $ edit
 $ git commit ...                            <1>
 $ edit
-$ git checkout -b branch2                   <2>
+$ git switch -c branch2                     <2>
 $ git reset --keep start                    <3>
 ------------
 +
 <1> This commits your first edits in `branch1`.
 <2> In the ideal world, you could have realized that the earlier
     commit did not belong to the new topic when you created and switched
-    to `branch2` (i.e. `git checkout -b branch2 start`), but nobody is
+    to `branch2` (i.e. `git switch -c branch2 start`), but nobody is
     perfect.
 <3> But you can use `reset --keep` to remove the unwanted commit after
     you switched to `branch2`.
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 7ef8c47911..ebb6282db3 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -235,12 +235,12 @@ return to your original branch to make the emergency fix, like this:
 +
 ----------------------------------------------------------------
 # ... hack hack hack ...
-$ git checkout -b my_wip
+$ git switch -c my_wip
 $ git commit -a -m "WIP"
-$ git checkout master
+$ git switch master
 $ edit emergency fix
 $ git commit -a -m "Fix in a hurry"
-$ git checkout my_wip
+$ git switch my_wip
 $ git reset --soft HEAD^
 # ... continue hacking ...
 ----------------------------------------------------------------
@@ -293,7 +293,8 @@ SEE ALSO
 linkgit:git-checkout[1],
 linkgit:git-commit[1],
 linkgit:git-reflog[1],
-linkgit:git-reset[1]
+linkgit:git-reset[1],
+linkgit:git-switch[1]
 
 GIT
 ---
diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt
index e29a9effcc..f880d21dfb 100644
--- a/Documentation/gitcore-tutorial.txt
+++ b/Documentation/gitcore-tutorial.txt
@@ -741,7 +741,7 @@ used earlier, and create a branch in it. You do that by simply just
 saying that you want to check out a new branch:
 
 ------------
-$ git checkout -b mybranch
+$ git switch -c mybranch
 ------------
 
 will create a new branch based at the current `HEAD` position, and switch
@@ -755,7 +755,7 @@ just telling 'git checkout' what the base of the checkout would be.
 In other words, if you have an earlier tag or branch, you'd just do
 
 ------------
-$ git checkout -b mybranch earlier-commit
+$ git switch -c mybranch earlier-commit
 ------------
 
 and it would create the new branch `mybranch` at the earlier commit,
@@ -765,7 +765,7 @@ and check out the state at that time.
 You can always just jump back to your original `master` branch by doing
 
 ------------
-$ git checkout master
+$ git switch master
 ------------
 
 (or any other branch-name, for that matter) and if you forget which
@@ -794,7 +794,7 @@ $ git branch <branchname> [startingpoint]
 
 which will simply _create_ the branch, but will not do anything further.
 You can then later -- once you decide that you want to actually develop
-on that branch -- switch to that branch with a regular 'git checkout'
+on that branch -- switch to that branch with a regular 'git switch'
 with the branchname as the argument.
 
 
@@ -808,7 +808,7 @@ being the same as the original `master` branch, let's make sure we're in
 that branch, and do some work there.
 
 ------------------------------------------------
-$ git checkout mybranch
+$ git switch mybranch
 $ echo "Work, work, work" >>hello
 $ git commit -m "Some work." -i hello
 ------------------------------------------------
@@ -825,7 +825,7 @@ does some work in the original branch, and simulate that by going back
 to the master branch, and editing the same file differently there:
 
 ------------
-$ git checkout master
+$ git switch master
 ------------
 
 Here, take a moment to look at the contents of `hello`, and notice how they
@@ -958,7 +958,7 @@ to the `master` branch. Let's go back to `mybranch`, and run
 'git merge' to get the "upstream changes" back to your branch.
 
 ------------
-$ git checkout mybranch
+$ git switch mybranch
 $ git merge -m "Merge upstream changes." master
 ------------
 
@@ -1133,9 +1133,8 @@ Remember, before running 'git merge', our `master` head was at
 work." commit.
 
 ------------
-$ git checkout mybranch
-$ git reset --hard master^2
-$ git checkout master
+$ git switch -C mybranch master^2
+$ git switch master
 $ git reset --hard master^
 ------------
 
diff --git a/Documentation/giteveryday.txt b/Documentation/giteveryday.txt
index 9f2528fc8c..ad455f3e39 100644
--- a/Documentation/giteveryday.txt
+++ b/Documentation/giteveryday.txt
@@ -41,7 +41,7 @@ following commands.
 
   * linkgit:git-log[1] to see what happened.
 
-  * linkgit:git-checkout[1] and linkgit:git-branch[1] to switch
+  * linkgit:git-switch[1] and linkgit:git-branch[1] to switch
     branches.
 
   * linkgit:git-add[1] to manage the index file.
@@ -80,7 +80,7 @@ $ git tag v2.43 <2>
 Create a topic branch and develop.::
 +
 ------------
-$ git checkout -b alsa-audio <1>
+$ git switch -c alsa-audio <1>
 $ edit/compile/test
 $ git checkout -- curses/ux_audio_oss.c <2>
 $ git add curses/ux_audio_alsa.c <3>
@@ -90,7 +90,7 @@ $ git commit -a -s <5>
 $ edit/compile/test
 $ git diff HEAD^ <6>
 $ git commit -a --amend <7>
-$ git checkout master <8>
+$ git switch master <8>
 $ git merge alsa-audio <9>
 $ git log --since='3 days ago' <10>
 $ git log v2.43.. curses/ <11>
@@ -148,11 +148,11 @@ Clone the upstream and work on it.  Feed changes to upstream.::
 ------------
 $ git clone git://git.kernel.org/pub/scm/.../torvalds/linux-2.6 my2.6
 $ cd my2.6
-$ git checkout -b mine master <1>
+$ git switch -c mine master <1>
 $ edit/compile/test; git commit -a -s <2>
 $ git format-patch master <3>
 $ git send-email --to="person <email@example.com>" 00*.patch <4>
-$ git checkout master <5>
+$ git switch master <5>
 $ git pull <6>
 $ git log -p ORIG_HEAD.. arch/i386 include/asm-i386 <7>
 $ git ls-remote --heads http://git.kernel.org/.../jgarzik/libata-dev.git <8>
@@ -194,7 +194,7 @@ satellite$ edit/compile/test/commit
 satellite$ git push origin <4>
 
 mothership$ cd frotz
-mothership$ git checkout master
+mothership$ git switch master
 mothership$ git merge satellite/master <5>
 ------------
 +
@@ -216,7 +216,7 @@ machine into the master branch.
 Branch off of a specific tag.::
 +
 ------------
-$ git checkout -b private2.6.14 v2.6.14 <1>
+$ git switch -c private2.6.14 v2.6.14 <1>
 $ edit/compile/test; git commit -a
 $ git checkout master
 $ git cherry-pick v2.6.14..private2.6.14 <2>
@@ -274,14 +274,14 @@ $ mailx <3>
 & s 2 3 4 5 ./+to-apply
 & s 7 8 ./+hold-linus
 & q
-$ git checkout -b topic/one master
+$ git switch -c topic/one master
 $ git am -3 -i -s ./+to-apply <4>
 $ compile/test
-$ git checkout -b hold/linus && git am -3 -i -s ./+hold-linus <5>
-$ git checkout topic/one && git rebase master <6>
-$ git checkout pu && git reset --hard next <7>
+$ git switch -c hold/linus && git am -3 -i -s ./+hold-linus <5>
+$ git switch topic/one && git rebase master <6>
+$ git switch -C pu next <7>
 $ git merge topic/one topic/two && git merge hold/linus <8>
-$ git checkout maint
+$ git switch maint
 $ git cherry-pick master~4 <9>
 $ compile/test
 $ git tag -s -m "GIT 0.99.9x" v0.99.9x <10>
diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt
index 242de31cb6..e6ad6b5f8d 100644
--- a/Documentation/gittutorial.txt
+++ b/Documentation/gittutorial.txt
@@ -207,7 +207,7 @@ automatically.  The asterisk marks the branch you are currently on;
 type
 
 ------------------------------------------------
-$ git checkout experimental
+$ git switch experimental
 ------------------------------------------------
 
 to switch to the experimental branch.  Now edit a file, commit the
@@ -216,7 +216,7 @@ change, and switch back to the master branch:
 ------------------------------------------------
 (edit file)
 $ git commit -a
-$ git checkout master
+$ git switch master
 ------------------------------------------------
 
 Check that the change you made is no longer visible, since it was
diff --git a/Documentation/gitworkflows.txt b/Documentation/gitworkflows.txt
index ca11c7bdaf..abc0dc6bc7 100644
--- a/Documentation/gitworkflows.txt
+++ b/Documentation/gitworkflows.txt
@@ -301,8 +301,7 @@ topics on 'next':
 .Rewind and rebuild next
 [caption="Recipe: "]
 =====================================
-* `git checkout next`
-* `git reset --hard master`
+* `git switch -C next master`
 * `git merge ai/topic_in_next1`
 * `git merge ai/topic_in_next2`
 * ...
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
index 72daa20e76..a1c7a65da6 100644
--- a/Documentation/revisions.txt
+++ b/Documentation/revisions.txt
@@ -115,7 +115,7 @@ Here's an example to make it more clear:
 ------------------------------
 $ git config push.default current
 $ git config remote.pushdefault myfork
-$ git checkout -b mybranch origin/master
+$ git switch -c mybranch origin/master
 
 $ git rev-parse --symbolic-full-name @{upstream}
 refs/remotes/origin/master
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index eff7890274..94799faa2b 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -122,10 +122,10 @@ Tags are expected to always point at the same version of a project,
 while heads are expected to advance as development progresses.
 
 Create a new branch head pointing to one of these versions and check it
-out using linkgit:git-checkout[1]:
+out using linkgit:git-switch[1]:
 
 ------------------------------------------------
-$ git checkout -b new v2.6.13
+$ git switch -c new v2.6.13
 ------------------------------------------------
 
 The working directory then reflects the contents that the project had
@@ -282,10 +282,10 @@ a summary of the commands:
 	this command will fail with a warning.
 `git branch -D <branch>`::
 	delete the branch `<branch>` irrespective of its merged status.
-`git checkout <branch>`::
+`git switch <branch>`::
 	make the current branch `<branch>`, updating the working
 	directory to reflect the version referenced by `<branch>`.
-`git checkout -b <new> <start-point>`::
+`git switch -c <new> <start-point>`::
 	create a new branch `<new>` referencing `<start-point>`, and
 	check it out.
 
@@ -302,22 +302,22 @@ ref: refs/heads/master
 Examining an old version without creating a new branch
 ------------------------------------------------------
 
-The `git checkout` command normally expects a branch head, but will also
-accept an arbitrary commit; for example, you can check out the commit
-referenced by a tag:
+The `git switch` command normally expects a branch head, but will also
+accept an arbitrary commit when invoked with --detach; for example,
+you can check out the commit referenced by a tag:
 
 ------------------------------------------------
-$ git checkout v2.6.17
+$ git switch --detach v2.6.17
 Note: checking out 'v2.6.17'.
 
 You are in 'detached HEAD' state. You can look around, make experimental
 changes and commit them, and you can discard any commits you make in this
-state without impacting any branches by performing another checkout.
+state without impacting any branches by performing another switch.
 
 If you want to create a new branch to retain commits you create, you may
-do so (now or later) by using -b with the checkout command again. Example:
+do so (now or later) by using -c with the switch command again. Example:
 
-  git checkout -b new_branch_name
+  git switch -c new_branch_name
 
 HEAD is now at 427abfa Linux v2.6.17
 ------------------------------------------------
@@ -373,7 +373,7 @@ You might want to build on one of these remote-tracking branches
 on a branch of your own, just as you would for a tag:
 
 ------------------------------------------------
-$ git checkout -b my-todo-copy origin/todo
+$ git switch -c my-todo-copy origin/todo
 ------------------------------------------------
 
 You can also check out `origin/todo` directly to examine it or
@@ -2211,8 +2211,8 @@ $ git branch --track release origin/master
 These can be easily kept up to date using linkgit:git-pull[1].
 
 -------------------------------------------------
-$ git checkout test && git pull
-$ git checkout release && git pull
+$ git switch test && git pull
+$ git switch release && git pull
 -------------------------------------------------
 
 Important note!  If you have any local changes in these branches, then
@@ -2264,7 +2264,7 @@ tested changes
 2) help future bug hunters that use `git bisect` to find problems
 
 -------------------------------------------------
-$ git checkout -b speed-up-spinlocks v2.6.35
+$ git switch -c speed-up-spinlocks v2.6.35
 -------------------------------------------------
 
 Now you apply the patch(es), run some tests, and commit the change(s).  If
@@ -2279,7 +2279,7 @@ When you are happy with the state of this change, you can merge it into the
 "test" branch in preparation to make it public:
 
 -------------------------------------------------
-$ git checkout test && git merge speed-up-spinlocks
+$ git switch test && git merge speed-up-spinlocks
 -------------------------------------------------
 
 It is unlikely that you would have any conflicts here ... but you might if you
@@ -2291,7 +2291,7 @@ see the value of keeping each patch (or patch series) in its own branch.  It
 means that the patches can be moved into the `release` tree in any order.
 
 -------------------------------------------------
-$ git checkout release && git merge speed-up-spinlocks
+$ git switch release && git merge speed-up-spinlocks
 -------------------------------------------------
 
 After a while, you will have a number of branches, and despite the
@@ -2512,7 +2512,7 @@ Suppose that you create a branch `mywork` on a remote-tracking branch
 `origin`, and create some commits on top of it:
 
 -------------------------------------------------
-$ git checkout -b mywork origin
+$ git switch -c mywork origin
 $ vi file.txt
 $ git commit
 $ vi otherfile.txt
@@ -2552,7 +2552,7 @@ commits without any merges, you may instead choose to use
 linkgit:git-rebase[1]:
 
 -------------------------------------------------
-$ git checkout mywork
+$ git switch mywork
 $ git rebase origin
 -------------------------------------------------
 
@@ -3668,13 +3668,13 @@ change within the submodule, and then update the superproject to reference the
 new commit:
 
 -------------------------------------------------
-$ git checkout master
+$ git switch master
 -------------------------------------------------
 
 or
 
 -------------------------------------------------
-$ git checkout -b fix-up
+$ git switch -c fix-up
 -------------------------------------------------
 
 then
@@ -4194,7 +4194,7 @@ start.
 A good place to start is with the contents of the initial commit, with:
 
 ----------------------------------------------------
-$ git checkout e83c5163
+$ git switch --detach e83c5163
 ----------------------------------------------------
 
 The initial revision lays the foundation for almost everything Git has
@@ -4437,10 +4437,10 @@ Managing branches
 -----------------
 
 -----------------------------------------------
-$ git branch	     # list all local branches in this repo
-$ git checkout test  # switch working directory to branch "test"
-$ git branch new     # create branch "new" starting at current HEAD
-$ git branch -d new  # delete branch "new"
+$ git branch			# list all local branches in this repo
+$ git switch test	        # switch working directory to branch "test"
+$ git branch new		# create branch "new" starting at current HEAD
+$ git branch -d new		# delete branch "new"
 -----------------------------------------------
 
 Instead of basing a new branch on current HEAD (the default), use:
@@ -4456,7 +4456,7 @@ $ git branch new test~10 # ten commits before tip of branch "test"
 Create and switch to a new branch at the same time:
 
 -----------------------------------------------
-$ git checkout -b new v2.6.15
+$ git switch -c new v2.6.15
 -----------------------------------------------
 
 Update and examine branches from the repository you cloned from:
@@ -4467,7 +4467,7 @@ $ git branch -r		# list
   origin/master
   origin/next
   ...
-$ git checkout -b masterwork origin/master
+$ git switch -c masterwork origin/master
 -----------------------------------------------
 
 Fetch a branch from a different repository, and give it a new
diff --git a/advice.c b/advice.c
index b224825637..24a7741083 100644
--- a/advice.c
+++ b/advice.c
@@ -191,20 +191,20 @@ void NORETURN die_conclude_merge(void)
 void detach_advice(const char *new_name)
 {
 	const char *fmt =
-	_("Note: checking out '%s'.\n"
+	_("Note: switching to '%s'.\n"
 	"\n"
 	"You are in 'detached HEAD' state. You can look around, make experimental\n"
 	"changes and commit them, and you can discard any commits you make in this\n"
-	"state without impacting any branches by performing another checkout.\n"
+	"state without impacting any branches by switching back to a branch.\n"
 	"\n"
 	"If you want to create a new branch to retain commits you create, you may\n"
-	"do so (now or later) by using -b with the checkout command again. Example:\n"
+	"do so (now or later) by using -c with the switch command. Example:\n"
 	"\n"
-	"  git checkout -b <new-branch-name>\n"
+	"  git switch -c <new-branch-name>\n"
 	"\n"
-	"Or undo this checkout with:\n"
+	"Or undo this operation with:\n"
 	"\n"
-	"  git checkout -\n"
+	"  git switch -\n"
 	"\n"
 	"Turn off this advice by setting config variable advice.detachedHead to false\n\n");
 
diff --git a/sha1-name.c b/sha1-name.c
index 6dda2c16df..da0518c8e3 100644
--- a/sha1-name.c
+++ b/sha1-name.c
@@ -743,7 +743,7 @@ static int get_oid_basic(const char *str, int len, struct object_id *oid,
 	"because it will be ignored when you just specify 40-hex. These refs\n"
 	"may be created by mistake. For example,\n"
 	"\n"
-	"  git checkout -b $br $(git rev-parse ...)\n"
+	"  git switch -c $br $(git rev-parse ...)\n"
 	"\n"
 	"where \"$br\" is somehow empty and a 40-hex ref is created. Please\n"
 	"examine these refs and maybe delete them. Turn this message off by\n"
diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
index 743c7248a2..b748db9946 100755
--- a/t/t2020-checkout-detach.sh
+++ b/t/t2020-checkout-detach.sh
@@ -195,20 +195,20 @@ test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not as
 
 	# The first detach operation is more chatty than the following ones.
 	cat >1st_detach <<-EOF &&
-	Note: checking out 'HEAD^'.
+	Note: switching to 'HEAD^'.
 
 	You are in 'detached HEAD' state. You can look around, make experimental
 	changes and commit them, and you can discard any commits you make in this
-	state without impacting any branches by performing another checkout.
+	state without impacting any branches by switching back to a branch.
 
 	If you want to create a new branch to retain commits you create, you may
-	do so (now or later) by using -b with the checkout command again. Example:
+	do so (now or later) by using -c with the switch command. Example:
 
-	  git checkout -b <new-branch-name>
+	  git switch -c <new-branch-name>
 
-	Or undo this checkout with:
+	Or undo this operation with:
 
-	  git checkout -
+	  git switch -
 
 	Turn off this advice by setting config variable advice.detachedHead to false
 
@@ -277,20 +277,20 @@ test_expect_success 'describe_detached_head does print SHA-1 ellipsis when asked
 
 	# The first detach operation is more chatty than the following ones.
 	cat >1st_detach <<-EOF &&
-	Note: checking out 'HEAD^'.
+	Note: switching to 'HEAD^'.
 
 	You are in 'detached HEAD' state. You can look around, make experimental
 	changes and commit them, and you can discard any commits you make in this
-	state without impacting any branches by performing another checkout.
+	state without impacting any branches by switching back to a branch.
 
 	If you want to create a new branch to retain commits you create, you may
-	do so (now or later) by using -b with the checkout command again. Example:
+	do so (now or later) by using -c with the switch command. Example:
 
-	  git checkout -b <new-branch-name>
+	  git switch -c <new-branch-name>
 
-	Or undo this checkout with:
+	Or undo this operation with:
 
-	  git checkout -
+	  git switch -
 
 	Turn off this advice by setting config variable advice.detachedHead to false
 
diff --git a/unpack-trees.c b/unpack-trees.c
index 22c41a3ba8..5ff64a983d 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -313,7 +313,7 @@ static struct progress *get_progress(struct unpack_trees_options *o)
 			total++;
 	}
 
-	return start_delayed_progress(_("Checking out files"), total);
+	return start_delayed_progress(_("Updating files"), total);
 }
 
 static void setup_collided_checkout_detection(struct checkout *state,
-- 
2.21.0.479.g47ac719cd3
Elijah Newren· Mar 29, 2019, 15:58 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH v6 00/27] Add new command 'switch'

On Fri, Mar 29, 2019 at 3:39 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
Show 18 quoted lines
>
> This adds a new command 'git-switch' as the half-replacement for
> 'git-checkout'. Jump to 13/27 as the starting point. The other half is
> git-restore, which is dealt with separately.
>
> The sixth time must be the charm, because the seventh time may be the
> curse!
>
> The last big change since v5 is from the discussion about
> remove_branch_state() and its relation with --ignore-in-progress. By
> popular demand, --ignore-in-progress is gone. The discussion is linked
> in 23/27, which is also improved to suggest a way out.
>
> That discussion also leads to a new patch, 07/27, which improves 'git
> checkout' and tells the user when merge/revert/cherry-pick is canceled.
>
> The rest is just minor updates here and there. And I made sure the
> sign-off is back this time :D

I looked through the range-diff and it looks good to me. I think the safety we added to switch could also be added to checkout, and I think we're narrowing down on how to safely support --ignore-in-progress in certain situations, but both of those things could go in after this series lands...and both might generate more discussion that doesn't need to derail the rest of this series.

← back to recent threads