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

[PATCH v2 3/3] builtin/checkout: learn -B

From
Tay Ray Chuan <rctay89@gmail.com>
Date
Jun 23, 2010, 19:29 UTC
Message-ID
<1277321340-4000-4-git-send-email-rctay89@gmail.com>
In-Reply-To
<1277321340-4000-3-git-send-email-rctay89@gmail.com>
Internally, --track and --orphan still use the 'safe' -b, not -B.
Signed-off-by: Tay Ray Chuan <rctay89@gmail.com>
---

Junio, I didn't wait for your reply to my suggestion about using "git reset" instead, as I hope to put this out for review before I creep to bed - I'll put it in the next iteration, if need be.

 Documentation/git-checkout.txt |   21 ++++++++++++++++-
 builtin/checkout.c             |   29 +++++++++++++++++++++----
 t/t2018-checkout-branch.sh     |   45 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 88 insertions(+), 7 deletions(-)
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 261dd90..97c5144 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git checkout' [-q] [-f] [-m] [<branch>]
-'git checkout' [-q] [-f] [-m] [[-b|--orphan] <new_branch>] [<start_point>]
+'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
 'git checkout' --patch [<tree-ish>] [--] [<paths>...]

@@ -21,7 +21,7 @@ also update `HEAD` to set the specified branch as the current
 branch.

 'git checkout' [<branch>]::
-'git checkout' -b <new branch> [<start point>]::
+'git checkout' -b|-B <new_branch> [<start point>]::

 	This form switches branches by updating the index, working
 	tree, and HEAD to reflect the specified branch.
@@ -31,6 +31,17 @@ were called and then checked out; in this case you can
 use the `--track` or `--no-track` options, which will be passed to
 'git branch'.  As a convenience, `--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
+is reset. This is the transactional equivalent of
++
+------------
+$ git branch -f <branch> [<start point>]
+$ git checkout <branch>
+------------
++
+that is to say, the branch is not reset/created unless "git checkout" is
+successful.

 'git checkout' [--patch] [<tree-ish>] [--] <pathspec>...::

@@ -75,6 +86,12 @@ entries; instead, unmerged entries are ignored.
 	Create a new branch named <new_branch> and start it at
 	<start_point>; see linkgit:git-branch[1] for details.

+-B::
+	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.
+
 -t::
 --track::
 	When creating a new branch, set up "upstream" configuration. See
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 3969683..ec2ecfa 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -32,7 +32,11 @@ struct checkout_opts {
 	int writeout_stage;
 	int writeout_error;

+	/* not set by parse_options */
+	int branch_exists;
+
 	const char *new_branch;
+	const char *new_branch_force;
 	const char *new_orphan_branch;
 	int new_branch_log;
 	enum branch_track track;
@@ -511,7 +515,8 @@ static void update_refs_for_switch(struct checkout_opts *opts,
 			}
 		}
 		else
-			create_branch(old->name, opts->new_branch, new->name, 0,
+			create_branch(old->name, opts->new_branch, new->name,
+				      opts->new_branch_force ? 1 : 0,
 				      opts->new_branch_log, opts->track);
 		new->name = opts->new_branch;
 		setup_branch_path(new);
@@ -531,7 +536,7 @@ static void update_refs_for_switch(struct checkout_opts *opts,
 					new->name);
 			else
 				fprintf(stderr, "Switched to%s branch '%s'\n",
-					opts->new_branch ? " a new" : "",
+					opts->branch_exists ? " and reset" : " a new",
 					new->name);
 		}
 		if (old->path && old->name) {
@@ -659,6 +664,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT__QUIET(&opts.quiet),
 		OPT_STRING('b', NULL, &opts.new_branch, "branch",
 		           "create and checkout a new branch"),
+		OPT_STRING('B', NULL, &opts.new_branch_force, "branch",
+		           "create/reset and checkout a branch"),
 		OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
 		OPT_SET_INT('t', "track",  &opts.track, "track",
 			BRANCH_TRACK_EXPLICIT),
@@ -689,6 +696,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);

+	/* we can assume from now on new_branch = !new_branch_force */
+	if (opts.new_branch && opts.new_branch_force)
+		die("-B cannot be used with -b");
+
+	/* copy -B over to -b, so that we can just check the latter */
+	if (opts.new_branch_force)
+		opts.new_branch = opts.new_branch_force;
+
 	if (patch_mode && (opts.track > 0 || opts.new_branch
 			   || opts.new_branch_log || opts.merge || opts.force))
 		die ("--patch is incompatible with all other options");
@@ -710,7 +725,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)

 	if (opts.new_orphan_branch) {
 		if (opts.new_branch)
-			die("--orphan and -b are mutually exclusive");
+			die("--orphan and -b|-B are mutually exclusive");
 		if (opts.track > 0)
 			die("--orphan cannot be used with -t");
 		opts.new_branch = opts.new_orphan_branch;
@@ -859,8 +874,12 @@ no_reference:
 		if (strbuf_check_branch_ref(&buf, opts.new_branch))
 			die("git checkout: we do not like '%s' as a branch name.",
 			    opts.new_branch);
-		if (!get_sha1(buf.buf, rev))
-			die("git checkout: branch %s already exists", opts.new_branch);
+		if (!get_sha1(buf.buf, rev)) {
+			opts.branch_exists = 1;
+			if (!opts.new_branch_force)
+				die("git checkout: branch %s already exists",
+				    opts.new_branch);
+		}
 		strbuf_release(&buf);
 	}

diff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh
index 3c13065..1caffea 100755
--- a/t/t2018-checkout-branch.sh
+++ b/t/t2018-checkout-branch.sh
@@ -118,4 +118,49 @@ test_expect_success 'checkout -b to an existing branch fails' '
 	test_must_fail do_checkout branch2 $HEAD2
 '

+test_expect_success 'checkout -B to an existing branch resets branch to HEAD' '
+	git checkout branch1 &&
+
+	do_checkout branch2 "" -B
+'
+
+test_expect_success 'checkout -B to an existing branch with an explicit ref resets branch to that ref' '
+	git checkout branch1 &&
+
+	do_checkout branch2 $HEAD1 -B
+'
+
+test_expect_success 'checkout -B to an existing branch with unmergeable changes fails' '
+	git checkout branch1 &&
+
+	setup_dirty_unmergeable &&
+	test_must_fail do_checkout branch2 $HEAD1 -B &&
+	test_dirty_unmergeable
+'
+
+test_expect_success 'checkout -f -B to an existing branch with unmergeable changes discards changes' '
+	# still dirty and on branch1
+	do_checkout branch2 $HEAD1 "-f -B" &&
+	test_must_fail test_dirty_unmergeable
+'
+
+test_expect_success 'checkout -B to an existing branch preserves mergeable changes' '
+	git checkout branch1 &&
+
+	setup_dirty_mergeable &&
+	do_checkout branch2 $HEAD1 -B &&
+	test_dirty_mergeable
+'
+
+test_expect_success 'checkout -f -B to an existing branch with mergeable changes discards changes' '
+	# clean up from previous test
+	git reset --hard &&
+
+	git checkout branch1 &&
+
+	setup_dirty_mergeable &&
+	do_checkout branch2 $HEAD1 "-f -B" &&
+	test_must_fail test_dirty_mergeable
+'
+
 test_done
--
1.7.1.513.g4f18
Previous: Tay Ray ChuanNext: Michel Lespinasse
Message 27 of 29 in “DWIM with "git checkout -f -b"”
  1. 0/3 DWIM with "git checkout -f -b"Tay Ray Chuan, Jun 20, 2010
  2. 1/3 add tests for checkout -bTay Ray Chuan, Jun 20, 2010
  3. 2/3 t2018-checkout-branch.sh: show that checkout -f -b doesn't DWIMTay Ray Chuan, Jun 20, 2010
  4. 3/3 builtin/checkout: DWIM with -f -bTay Ray Chuan, Jun 20, 2010
  5. Junio C HamanoJun 20, 2010
  6. Jeff KingJun 20, 2010
  7. Clemens BuchacherJun 20, 2010
  8. Junio C HamanoJun 20, 2010
  9. Jeff KingJun 20, 2010
  10. Junio C HamanoJun 21, 2010
  11. Tay Ray ChuanJun 21, 2010
  12. Junio C HamanoJun 21, 2010
  13. 0/3 teach git-checkout -B WAS builtin/checkout: DWIM with -f -bTay Ray Chuan, Jun 21, 2010
  14. 1/3 add tests for checkout -bTay Ray Chuan, Jun 21, 2010
  15. 2/3 builtin/checkout: change -b from an OPTION_STRING to a OPTION_SET_INTTay Ray Chuan, Jun 21, 2010
  16. 3/3 builtin/checkout: learn -BTay Ray Chuan, Jun 21, 2010
  17. Junio C HamanoJun 23, 2010
  18. Tay Ray ChuanJun 23, 2010
  19. Erick MattosJun 21, 2010
  20. Tay Ray ChuanJun 23, 2010
  21. Erick MattosJun 23, 2010
  22. Tay Ray ChuanJun 21, 2010
  23. Erick MattosJun 21, 2010
  24. 0/3 teach git-checkout -B WAS builtin/checkout: DWIM with -f -bTay Ray Chuan, Jun 23, 2010
  25. 1/3 add tests for checkout -bTay Ray Chuan, Jun 23, 2010
  26. 2/3 builtin/checkout: reword hint for -bTay Ray Chuan, Jun 23, 2010
  27. 3/3 builtin/checkout: learn -BTay Ray Chuan, Jun 23, 2010
  28. Michel LespinasseJun 21, 2010
  29. Michel LespinasseJun 21, 2010

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

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