threads / patch / 7467

patchcheckout -d: explicitly detach HEAD even when switching to the tip of a branch

Subject: [PATCH] checkout -d: explicitly detach HEAD even when switching to the tip of a branch

## tl;dr

8 messages between Mar 29, 2007 and Apr 4, 2007. Diffs are folded; open one to read it.

replies: 7people: 5as markdown or json

Junio C Hamano· Mar 29, 2007, 08:02 UTC · lore

You cannot currently checkout the tip of an existing branch without moving to the branch.

This allows you to detach your HEAD and place it at such a commit, with:

    $ git checkout -d master
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 Documentation/git-checkout.txt |    6 +++-
 git-checkout.sh                |   18 +++++++++--
 t/t7201-co.sh                  |   63 +++++++++++++++++++++++++++++++++++++++-
 3 files changed, 81 insertions(+), 6 deletions(-)
Show changes to 3 files +81 −6

Documentation/git-checkout.txt, git-checkout.sh, t/t7201-co.sh

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index f5b2d50..d00eeaa 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -8,7 +8,7 @@ git-checkout - Checkout and switch to a branch
 SYNOPSIS
 --------
 [verse]
-'git-checkout' [-q] [-f] [-b [--track | --no-track] <new_branch> [-l]] [-m] [<branch>]
+'git-checkout' [-q] [-f] [-b [--track | --no-track] <new_branch> [-l] | -d] [-m] [<branch>]
 'git-checkout' [<tree-ish>] <paths>...
 
 DESCRIPTION
@@ -61,6 +61,10 @@ OPTIONS
 	all changes to made the branch ref, enabling use of date
 	based sha1 expressions such as "<branchname>@{yesterday}".
 
+-d::
+	Explicitly ask to detach HEAD, even when named revision
+	to switch to is at the tip of a branch.
+
 -m::
 	If you have local modifications to one or more files that
 	are different between the current branch and the branch to
diff --git a/git-checkout.sh b/git-checkout.sh
index a7390e8..e551443 100755
--- a/git-checkout.sh
+++ b/git-checkout.sh
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-USAGE='[-q] [-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]'
+USAGE='[-q] [-f] [-b <new_branch> | -d] [-m] [<branch>] [<paths>...]'
 SUBDIRECTORY_OK=Sometimes
 . git-sh-setup
 require_work_tree
@@ -17,6 +17,7 @@ newbranch=
 newbranch_log=
 merge=
 quiet=
+explicit_detach=
 LF='
 '
 while [ "$#" != "0" ]; do
@@ -39,6 +40,9 @@ while [ "$#" != "0" ]; do
 	"--track"|"--no-track")
 		track="$arg"
 		;;
+	-d)
+		explicit_detach=1
+		;;
 	"-f")
 		force=1
 		;;
@@ -94,6 +98,11 @@ case "$newbranch,$track" in
 	die "git checkout: --track and --no-track require -b"
 esac
 
+case "$newbranch$explicit_detach" in
+11)
+	die "git checkout: -d and -b are incompatible"
+esac
+
 case "$force$merge" in
 11)
 	die "git checkout: -f and -m are incompatible"
@@ -117,7 +126,7 @@ then
 		hint="
 Did you intend to checkout '$@' which can not be resolved as commit?"
 	fi
-	if test '' != "$newbranch$force$merge"
+	if test '' != "$newbranch$force$merge$explicit_detach"
 	then
 		die "git checkout: updating paths is incompatible with switching branches/forcing$hint"
 	fi
@@ -170,7 +179,8 @@ describe_detached_head () {
 	}
 }
 
-if test -z "$branch$newbranch" && test "$new" != "$old"
+if test -z "$branch$newbranch" && test "$new" != "$old" ||
+   test -n "$explicit_detach"
 then
 	detached="$new"
 	if test -n "$oldbranch" && test -z "$quiet"
@@ -254,7 +264,7 @@ if [ "$?" -eq 0 ]; then
 		git-branch $track $newbranch_log "$newbranch" "$new_name" || exit
 		branch="$newbranch"
 	fi
-	if test -n "$branch"
+	if test -n "$branch" && test -z "$explicit_detach"
 	then
 		GIT_DIR="$GIT_DIR" git-symbolic-ref -m "checkout: moving to $branch" HEAD "refs/heads/$branch"
 		if test -n "$quiet"
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 867bbd2..caa04f9 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -3,7 +3,20 @@
 # Copyright (c) 2006 Junio C Hamano
 #
 
-test_description='git-checkout tests.'
+test_description='git-checkout tests.
+
+Creates master, forks renamer and side branches from it.
+Test switching across them.
+
+  ! [master] Initial A one, A two
+   * [renamer] Renamer R one->uno, M two
+    ! [side] Side M one, D two, A three
+  ---
+    + [side] Side M one, D two, A three
+   *  [renamer] Renamer R one->uno, M two
+  +*+ [master] Initial A one, A two
+
+'
 
 . ./test-lib.sh
 
@@ -129,4 +142,52 @@ test_expect_success 'checkout -m with merge conflict' '
 	! test -s current
 '
 
+test_expect_success 'checkout to detach HEAD' '
+
+	git checkout -f renamer && git clean &&
+	git checkout renamer^ &&
+	H=$(git rev-parse --verify HEAD) &&
+	M=$(git show-ref -s --verify refs/heads/master) &&
+	test "z$H" = "z$M" &&
+	if git symbolic-ref HEAD >/dev/null 2>&1
+	then
+		echo "OOPS, HEAD is still symbolic???"
+		false
+	else
+		: happy
+	fi
+'
+
+test_expect_success 'checkout to detach HEAD with explicit -d' '
+
+	git checkout -f master && git clean &&
+	git checkout -d renamer^ &&
+	H=$(git rev-parse --verify HEAD) &&
+	M=$(git show-ref -s --verify refs/heads/master) &&
+	test "z$H" = "z$M" &&
+	if git symbolic-ref HEAD >/dev/null 2>&1
+	then
+		echo "OOPS, HEAD is still symbolic???"
+		false
+	else
+		: happy
+	fi
+'
+
+test_expect_success 'checkout to detach HEAD with explicit -d' '
+
+	git checkout -f master && git clean &&
+	git checkout -d &&
+	H=$(git rev-parse --verify HEAD) &&
+	M=$(git show-ref -s --verify refs/heads/master) &&
+	test "z$H" = "z$M" &&
+	if git symbolic-ref HEAD >/dev/null 2>&1
+	then
+		echo "OOPS, HEAD is still symbolic???"
+		false
+	else
+		: happy
+	fi
+'
+
 test_done
-- 
1.5.1.rc3.1.ga429d
Xavier Maillard· Mar 29, 2007, 10:44 UTC · re: Junio C Hamano · lore

Re: [PATCH] checkout -d: explicitly detach HEAD even when switching to the tip of a branch

   This allows you to detach your HEAD and place it at such a
       $ git checkout -d master

Hum, excuse my ignorance, but can you explain what exactly could be a use case of this ?

Xavier
Alex Riesen· Mar 29, 2007, 17:10 UTC · re: Xavier Maillard · lore

Re: [PATCH] checkout -d: explicitly detach HEAD even when switching to the tip of a branch

Xavier Maillard, Thu, Mar 29, 2007 12:44:42 +0200:
Show 6 quoted lines
>    This allows you to detach your HEAD and place it at such a
> 
>        $ git checkout -d master
> 
> Hum, excuse my ignorance, but can you explain what exactly could
> be a use case of this ?

A throw-away or an experimental work. A commit importer which cares about the branch it commits on: to update the branch reference only when it has reached a stable state.

Xavier Maillard· Mar 29, 2007, 20:54 UTC · re: Alex Riesen · lore

Re: [PATCH] checkout -d: explicitly detach HEAD even when switching to the tip of a branch

   A throw-away or an experimental work. A commit importer which cares
   about the branch it commits on: to update the branch reference only
   when it has reached a stable state.
Hum, yes, I see. Thank you.
Xavier
Jakub Narebski· Mar 31, 2007, 00:11 UTC · re: Junio C Hamano · lore

Re: [PATCH] checkout -d: explicitly detach HEAD even when switching to the tip of a branch

Junio C Hamano wrote:
Show 7 quoted lines
> You cannot currently checkout the tip of an existing branch
> without moving to the branch.
> 
> This allows you to detach your HEAD and place it at such a
> commit, with:
> 
>     $ git checkout -d master
What about
      $ git checkout master^0
trick to force detaching?
-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
Xavier Maillard· Mar 31, 2007, 14:55 UTC · re: Jakub Narebski · lore

Re: [PATCH] checkout -d: explicitly detach HEAD even when switching to the tip of a branch

   > You cannot currently checkout the tip of an existing branch
   > without moving to the branch.
   > 
   > This allows you to detach your HEAD and place it at such a
   > commit, with:
   > 
   >     $ git checkout -d master
   What about
	 $ git checkout master^0
   trick to force detaching?
I love this idea.
Xavier
Junio C Hamano· Apr 4, 2007, 00:11 UTC · re: Xavier Maillard · lore

Re: [PATCH] checkout -d: explicitly detach HEAD even when switching to the tip of a branch

Xavier Maillard <zedek@gnu.org> writes:
Show 15 quoted lines
>    > You cannot currently checkout the tip of an existing branch
>    > without moving to the branch.
>    > 
>    > This allows you to detach your HEAD and place it at such a
>    > commit, with:
>    > 
>    >     $ git checkout -d master
>
>    What about
>
> 	 $ git checkout master^0
>
>    trick to force detaching?
>
> I love this idea.
Could anybody remind me why we have the "new != old" check here?
Show changes to 2 files +11 −1

git-checkout.sh, git-checkout-script

diff --git a/git-checkout.sh b/git-checkout.sh
index a7390e8..573a3c0 100755
--- a/git-checkout.sh
+++ b/git-checkout.sh
@@ -170,7 +170,7 @@ describe_detached_head () {
 	}
 }
 
-if test -z "$branch$newbranch" && test "$new" != "$old"
+if test -z "$branch$newbranch"
 then
 	detached="$new"
 	if test -n "$oldbranch" && test -z "$quiet"

I think I do not need an explicit -d option if we just remove
that second test.  It is coming from an ancient commit 91dcdfd3,
and I _think_ it was to prevent something like:

	git-checkout-script v2.6.12^0

from succeeding, while allowing

	git-checkout-script HEAD^0

to be a no-op (as it happens to be naming the same commit).

commit 91dcdfd3b5331d955cfb60edf8930f1b5c142905
Author: Linus Torvalds <torvalds@g5.osdl.org>
Date:   Mon Jul 11 20:44:20 2005 -0700

    Make "git checkout" create new branches on demand

diff --git a/git-checkout-script b/git-checkout-script
index 48e1da9..7e70338 100755
--- a/git-checkout-script
+++ b/git-checkout-script
@@ -5,10 +5,19 @@ old=$(git-rev-parse HEAD)
 ...
@@ -32,6 +41,16 @@ while [ "$#" != "0" ]; do
 done
 [ -z "$new" ] && new=$old
 
+#
+# If we don't have an old branch that we're switching to,
+# and we don't have a new branch name for the target we
+# are switching to, then we'd better just be checking out
+# what we already had
+#
+[ -z "$branch$newbranch" ] &&
+	[ "$new" != "$old" ] &&
+	die "git checkout: you need to specify a new branch name"
+
 if [ "$force" ]
 then
     git-read-tree --reset $new &&
@@ -47,6 +66,10 @@ fi
 ...
Linus Torvalds· Apr 4, 2007, 01:55 UTC · re: Junio C Hamano · lore

Re: [PATCH] checkout -d: explicitly detach HEAD even when switching to the tip of a branch

On Tue, 3 Apr 2007, Junio C Hamano wrote:
> 
> Could anybody remind me why we have the "new != old" check here?
Afaik, it's just so that you can do
	git checkout -f
without having it complain. And I think it just comes from the earlier:
	[ -z "$new" ] && new=$old && new_name="$old_name"

that we did - ie without that it would incorrectly just overwrite the current working tree with the new commit, but not actually do the "git reset" part.

So without that check, the way things used to work (*before* detached heads), if you were to have done

	git checkout <some-random-head>
it would have screwed up the current branch horribly.
With detached heads, I don't think it's needed.
		Linus

← back to recent threads