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

Re: master^ is not a local branch -- huh?!?

From
Junio C Hamano <gitster@pobox.com>
Date
Jan 30, 2010, 06:03 UTC
Message-ID
<7v8wbg2kpf.fsf@alter.siamese.dyndns.org>
In-Reply-To
<alpine.LFD.2.00.1001292305500.1681@xanadu.home>
Nicolas Pitre <nico@fluxnic.net> writes:
Show 11 quoted lines
> First, I'm afraid that "Checking out commit 'foobar'" might be confusing 
> as this may happen through either a remote branch, a tag, or any random 
> commit.  It seems to me that "Checking out 'v2.5'" is less confusing 
> than "Checking out commit 'v2.5'".  But that's a minor detail and 
> probably a personal preference.
> ...
> To the contrary: this "detached HEAD" is exactly what you need if you 
> want to relate to any documentation or perform a search for more 
> information.  Like it or not, this detached HEAD term is exactly what 
> this Git concept is all about and how it is designated everywhere.  The 
> sooner Git users see and learn about it the better.

As I am not good at keeping track of different proposals to change this word here and that word there, I expect this will probably need at least few rotations of earth to get input from people in different timezones, and I think this is post 1.7.0 item anyway, I'll queue the attached draft in 'pu' and keep it there, to make it easier for others to tweak the message.

-- >8 --
Subject: [PATCH] Reword "detached HEAD" notification

The old "advice" message explained how to create a branch after going into a detached HEAD state but didn't make it clear why the user may want to do so. Also "moving to ... which isn't a local branch" was unclear if it is complaining, if it is describing the new state, or if it is explaining why the HEAD is detached (the true reason is the last one).

Give the established phrase 'detached HEAD' first to make it easy for users to look up the concept in documentation, and briefly describe what can be done in the state (i.e. play around without having to clean up) before telling the user how to keep what was done during the temporary state.

Allow the long description to be hidden by setting advice.detachedHead configuration to false.

We might want to customize the advice depending on how the commit to check out was spelled (e.g. instead of "new-branch-name", we way want to say "topic" when "git checkout origin/topic" triggered this message) in later updates, but this encapsulates that into a separate function and it should be a good first step.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/config.txt |    5 +++++
 advice.c                 |    2 ++
 advice.h                 |    1 +
 builtin-checkout.c       |   18 ++++++++++++++++--
 t/t7201-co.sh            |   32 ++++++++++++++++++++++----------
 5 files changed, 46 insertions(+), 12 deletions(-)
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 17901e2..fee44d8 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -138,6 +138,11 @@ advice.*::
 		Advice on how to set your identity configuration when
 		your information is guessed from the system username and
 		domain name. Default: true.
+
+	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.  Default: true.
 --
 
 core.fileMode::
diff --git a/advice.c b/advice.c
index 936d98b..0be4b5f 100644
--- a/advice.c
+++ b/advice.c
@@ -5,6 +5,7 @@ int advice_status_hints = 1;
 int advice_commit_before_merge = 1;
 int advice_resolve_conflict = 1;
 int advice_implicit_identity = 1;
+int advice_detached_head = 1;
 
 static struct {
 	const char *name;
@@ -15,6 +16,7 @@ static struct {
 	{ "commitbeforemerge", &advice_commit_before_merge },
 	{ "resolveconflict", &advice_resolve_conflict },
 	{ "implicitidentity", &advice_implicit_identity },
+	{ "detachedhead", &advice_detached_head },
 };
 
 int git_default_advice_config(const char *var, const char *value)
diff --git a/advice.h b/advice.h
index 9b7a3ad..3244ebb 100644
--- a/advice.h
+++ b/advice.h
@@ -8,6 +8,7 @@ extern int advice_status_hints;
 extern int advice_commit_before_merge;
 extern int advice_resolve_conflict;
 extern int advice_implicit_identity;
+extern int advice_detached_head;
 
 int git_default_advice_config(const char *var, const char *value);
 
diff --git a/builtin-checkout.c b/builtin-checkout.c
index 5277817..c5ab783 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -488,6 +488,20 @@ static void report_tracking(struct branch_info *new)
 	strbuf_release(&sb);
 }
 
+static void detach_advice(const char *old_path, const char *new_name)
+{
+	const char fmt[] =
+	"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"
+	"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";
+
+	fprintf(stderr, fmt, new_name);
+}
+
 static void update_refs_for_switch(struct checkout_opts *opts,
 				   struct branch_info *old,
 				   struct branch_info *new)
@@ -522,8 +536,8 @@ static void update_refs_for_switch(struct checkout_opts *opts,
 		update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
 			   REF_NODEREF, DIE_ON_ERR);
 		if (!opts->quiet) {
-			if (old->path)
-				fprintf(stderr, "Note: moving to '%s' which isn't a local branch\nIf you want to create a new branch from this checkout, you may do so\n(now or later) by using -b with the checkout command again. Example:\n  git checkout -b <new_branch_name>\n", new->name);
+			if (old->path && advice_detached_head)
+				detach_advice(old->path, new->name);
 			describe_detached_head("HEAD is now at", new->commit);
 		}
 	}
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 6442f71..d20ed61 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -166,19 +166,31 @@ test_expect_success 'checkout -m with merge conflict' '
 	! test -s current
 '
 
-test_expect_success 'checkout to detach HEAD' '
+test_expect_success 'checkout to detach HEAD (with advice declined)' '
 
+	git config advice.detachedHead false &&
 	git checkout -f renamer && git clean -f &&
 	git checkout renamer^ 2>messages &&
-	(cat >messages.expect <<EOF
-Note: moving to '\''renamer^'\'' which isn'\''t a local branch
-If you want to create a new branch from this checkout, you may do so
-(now or later) by using -b with the checkout command again. Example:
-  git checkout -b <new_branch_name>
-HEAD is now at 7329388... Initial A one, A two
-EOF
-) &&
-	test_cmp messages.expect messages &&
+	grep "HEAD is now at 7329388" messages &&
+	test 1 -eq $(wc -l <messages) &&
+	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' '
+	git config advice.detachedHead true &&
+	git checkout -f renamer && git clean -f &&
+	git checkout renamer^ 2>messages &&
+	grep "HEAD is now at 7329388" messages &&
+	test 1 -lt $(wc -l <messages) &&
 	H=$(git rev-parse --verify HEAD) &&
 	M=$(git show-ref -s --verify refs/heads/master) &&
 	test "z$H" = "z$M" &&
-- 
1.7.0.rc0.187.g226c
Previous: Mark LodatoNext: Jeff King
Message 56 of 94 in “master^ is not a local branch -- huh?!?”
  1. Ron1Jan 29, 2010
  2. Jacob HelwigJan 29, 2010
  3. Sverre RabbelierJan 29, 2010
  4. Jacob HelwigJan 29, 2010
  5. Junio C HamanoJan 29, 2010
  6. Sverre RabbelierJan 29, 2010
  7. checkout: warn about 'branch name' rather than 'local branch'Sverre Rabbelier, Jan 29, 2010
  8. checkout: Fix test for s/local branch/branch name/ change.Jacob Helwig, Jan 29, 2010
  9. Sverre RabbelierJan 29, 2010
  10. Nicolas PitreJan 29, 2010
  11. Ron GarretJan 29, 2010
  12. Junio C HamanoJan 29, 2010
  13. Sverre RabbelierJan 29, 2010
  14. Junio C HamanoJan 29, 2010
  15. Nicolas PitreJan 29, 2010
  16. Sverre RabbelierJan 29, 2010
  17. Nicolas PitreJan 29, 2010
  18. Sverre RabbelierJan 29, 2010
  19. Nicolas PitreJan 29, 2010
  20. Junio C HamanoJan 29, 2010
  21. Sverre RabbelierJan 29, 2010
  22. Nicolas PitreJan 29, 2010
  23. Junio C HamanoJan 29, 2010
  24. Jacob HelwigJan 29, 2010
  25. Nicolas PitreJan 29, 2010
  26. Junio C HamanoJan 30, 2010
  27. Sverre RabbelierJan 30, 2010
  28. Junio C HamanoJan 30, 2010
  29. Sverre RabbelierJan 30, 2010
  30. Michael WittenJan 30, 2010
  31. Nicolas PitreJan 30, 2010
  32. Mark LodatoJan 30, 2010
  33. Nicolas PitreJan 30, 2010
  34. Ron GarretJan 30, 2010
  35. Junio C HamanoJan 30, 2010
  36. Ron GarretJan 30, 2010
  37. Nicolas PitreJan 30, 2010
  38. Jay SoffianJan 30, 2010
  39. Junio C HamanoJan 30, 2010
  40. Jay SoffianJan 30, 2010
  41. Nicolas PitreJan 30, 2010
  42. Junio C HamanoJan 30, 2010
  43. Ron GarretJan 30, 2010
  44. Junio C HamanoJan 30, 2010
  45. Jay SoffianJan 30, 2010
  46. Junio C HamanoJan 30, 2010
  47. Ron GarretJan 30, 2010
  48. Mark LodatoJan 30, 2010
  49. Nicolas PitreJan 30, 2010
  50. Mark LodatoJan 30, 2010
  51. Nicolas PitreJan 30, 2010
  52. Nicolas PitreJan 30, 2010
  53. Jay SoffianJan 30, 2010
  54. Nicolas PitreJan 30, 2010
  55. Mark LodatoJan 30, 2010
  56. Junio C HamanoJan 30, 2010
  57. Jeff KingJan 30, 2010
  58. Ron GarretJan 30, 2010
  59. A Large Angry SCMJan 29, 2010
  60. Junio C HamanoJan 29, 2010
  61. Johannes SchindelinJan 30, 2010
  62. Nicolas PitreJan 30, 2010
  63. Johannes SchindelinJan 29, 2010
  64. Ron1Jan 29, 2010
  65. Jacob HelwigJan 29, 2010
  66. Ron GarretJan 29, 2010
  67. Junio C HamanoJan 29, 2010
  68. Ron GarretJan 29, 2010
  69. Junio C HamanoJan 31, 2010
  70. Octavio AlvarezJan 29, 2010
  71. Ron GarretJan 29, 2010
  72. Octavio AlvarezJan 29, 2010
  73. Ron GarretJan 29, 2010
  74. Octavio AlvarezJan 29, 2010
  75. Junio C HamanoJan 29, 2010
  76. Ron GarretJan 29, 2010
  77. Julian PhillipsJan 29, 2010
  78. Ron GarretJan 30, 2010
  79. Ron GarretJan 30, 2010
  80. Junio C HamanoJan 30, 2010
  81. Ron GarretJan 30, 2010
  82. Scott R. GodinJan 29, 2010
  83. Ron GarretJan 29, 2010
  84. Sverre RabbelierJan 29, 2010
  85. Ron GarretJan 29, 2010
  86. Junio C HamanoJan 29, 2010
  87. Ron GarretJan 29, 2010
  88. Michael WittenJan 30, 2010
  89. Junio C HamanoJan 30, 2010
  90. Ron GarretJan 30, 2010
  91. Junio C HamanoJan 30, 2010
  92. Ron GarretJan 30, 2010
  93. Junio C HamanoJan 30, 2010
  94. Ron GarretJan 30, 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.