[PATCH] Proof-of-concept patch to remember what the detached HEAD was

Subsystems: the rest

STALE3735d

10 messages, 6 authors, 2016-06-15 · open the first message on its own page

[PATCH] Proof-of-concept patch to remember what the detached HEAD was

From: Daniel Barkalow <hidden>
Date: 2016-06-15 22:47:33

When detaching HEAD (or "browsing history"), the user has specified
the commit with some "extended SHA1" which is not a local
branch. Exactly what that string was is likely to be useful to the
user later. Also, we can detect the user putting work into the history
for the first time (such that it is no longer going to be protected as
uncommitted changes in the working tree) without a branch to hold it
by seeing that there is such a description for the current state
before the commit. (Afterwards, the description should be dropped; it
doesn't make sense to tell the user they checked out "origin/master"
or "d199fb7" when they've now diverged from that remote branch with
local changes or made a different commit.)

The upshot of the messages should be:

 $ git checkout origin/master
 Since you can't actually change "origin/master" yourself, you'll just
 be sightseeing unless you create a local branch to hold new local work.

 $ git branch
 * (not a local branch, but "origin/master")

 $ git commit
 You've been sightseeing "origin/master". The commit can't change that
 value, so your commit isn't held in any branch. If you want to create
 a branch to hold it, here's how.

"git checkout origin/master" should be similar in complexity to
"svn checkout -r 8655"; the difference is that svn won't let you
commit then and git will but you'll need to understand the
implications if you do so. If you don't commit (because you don't want
to make any changes, because you don't think it would be possible, or
because you don't want to worry about what would happen), there's no
meaningful difference, and you don't need to be told.

The messages have to be improved and made more useful.

The effects of "git checkout HEAD", "git checkout origin/master; git 
checkout HEAD^", and "git checkout origin/master; git reset --hard 
origin/next" aren't handled quite right; none of them keep a description, 
but there should always be some description of a detached HEAD unless the 
user has made a commit (and therefore gotten the message about making a 
local branch to put it on).
---
 branch.c                 |   13 +++++++++++++
 branch.h                 |    6 ++++++
 builtin-branch.c         |   13 ++++++++++++-
 builtin-checkout.c       |    8 +++++++-
 builtin-commit.c         |   10 +++++++++-
 t/t3203-branch-output.sh |    2 +-
 t/t7201-co.sh            |    6 ++----
 7 files changed, 50 insertions(+), 8 deletions(-)
diff --git a/branch.c b/branch.c
index 05ef3f5..2c5b6d3 100644
--- a/branch.c
+++ b/branch.c
@@ -194,6 +194,18 @@ void create_branch(const char *head,
 	free(real_ref);
 }
 
+char *get_detached_head_string(void)
+{
+	char *filename = git_path("DETACH_NAME");
+	struct stat st;
+	if (stat(filename, &st) || !S_ISREG(st.st_mode))
+		return NULL;
+	struct strbuf buf = STRBUF_INIT;
+	strbuf_read_file(&buf, filename, st.st_size);
+	strbuf_trim(&buf);
+	return strbuf_detach(&buf, 0);
+}
+
 void remove_branch_state(void)
 {
 	unlink(git_path("MERGE_HEAD"));
@@ -201,4 +213,5 @@ void remove_branch_state(void)
 	unlink(git_path("MERGE_MSG"));
 	unlink(git_path("MERGE_MODE"));
 	unlink(git_path("SQUASH_MSG"));
+	unlink(git_path("DETACH_NAME"));
 }
diff --git a/branch.h b/branch.h
index eed817a..0a30c3a 100644
--- a/branch.h
+++ b/branch.h
@@ -22,6 +22,12 @@ void create_branch(const char *head, const char *name, const char *start_name,
 void remove_branch_state(void);
 
 /*
+ * Returns the string used when detaching HEAD, or NULL if HEAD is not
+ * detached.
+ */
+char *get_detached_head_string(void);
+
+/*
  * Configure local branch "local" to merge remote branch "remote"
  * taken from origin "origin".
  */
diff --git a/builtin-branch.c b/builtin-branch.c
index 9f57992..9ce4127 100644
--- a/builtin-branch.c
+++ b/builtin-branch.c
@@ -425,7 +425,18 @@ static void show_detached(struct ref_list *ref_list)
 
 	if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) {
 		struct ref_item item;
-		item.name = xstrdup("(no branch)");
+		char *literal = get_detached_head_string();
+		struct stat st;
+		if (literal) {
+			struct strbuf buf = STRBUF_INIT;
+			strbuf_addstr(&buf, "(no branch, as \"");
+			strbuf_addstr(&buf, literal);
+			strbuf_addstr(&buf, "\")");
+			free(literal);
+			item.name = strbuf_detach(&buf, 0);
+		} else {
+			item.name = xstrdup("(no branch)");
+		}
 		item.len = strlen(item.name);
 		item.kind = REF_LOCAL_BRANCH;
 		item.dest = NULL;
diff --git a/builtin-checkout.c b/builtin-checkout.c
index d050c37..448397d 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -510,11 +510,17 @@ static void update_refs_for_switch(struct checkout_opts *opts,
 			   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);
+				fprintf(stderr, "Note: moving to '%s' which isn't a local branch.\nAny commits you may make will not affect the commit with this name.\n", new->name);
 			describe_detached_head("HEAD is now at", new->commit);
 		}
 	}
 	remove_branch_state();
+	if (!new->path && strcmp(new->name, "HEAD")) {
+		FILE *detach_name;
+		detach_name = fopen(git_path("DETACH_NAME"), "w");
+		fprintf(detach_name, "%s\n", new->name);
+		fclose(detach_name);
+	}
 	strbuf_release(&msg);
 	if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
 		report_tracking(new);
diff --git a/builtin-commit.c b/builtin-commit.c
index 200ffda..2ceb951 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -24,6 +24,7 @@
 #include "string-list.h"
 #include "rerere.h"
 #include "unpack-trees.h"
+#include "branch.h"
 
 static const char * const builtin_commit_usage[] = {
 	"git commit [options] [--] <filepattern>...",
@@ -968,6 +969,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	struct ref_lock *ref_lock;
 	struct commit_list *parents = NULL, **pptr = &parents;
 	struct stat statbuf;
+	char *detached_string;
 	int allow_fast_forward = 1;
 	struct wt_status s;
 
@@ -1089,10 +1091,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		die("cannot update HEAD ref");
 	}
 
+	detached_string = get_detached_head_string();
+
 	unlink(git_path("MERGE_HEAD"));
 	unlink(git_path("MERGE_MSG"));
 	unlink(git_path("MERGE_MODE"));
 	unlink(git_path("SQUASH_MSG"));
+	unlink(git_path("DETACH_NAME"));
 
 	if (commit_index_files())
 		die ("Repository has been updated, but unable to write\n"
@@ -1101,8 +1106,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 
 	rerere();
 	run_hook(get_index_file(), "post-commit", NULL);
-	if (!quiet)
+	if (!quiet) {
+		if (detached_string)
+			fprintf(stderr, "\nNote: you had checked out '%s' which isn't a local branch.\nIf you want to create a new branch with this commit, you may do so\n(now or later) by using -b with the checkout command. Example:\n  git checkout -b <new_branch_name>\n\n", detached_string);
 		print_summary(prefix, commit_sha1);
+	}
 
 	return 0;
 }
diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh
index 809d1c4..08409cd 100755
--- a/t/t3203-branch-output.sh
+++ b/t/t3203-branch-output.sh
@@ -67,7 +67,7 @@ test_expect_success 'git branch -v shows branch summaries' '
 '
 
 cat >expect <<'EOF'
-* (no branch)
+* (no branch, as "HEAD^0")
   branch-one
   branch-two
   master
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index ebfd34d..0f40589 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -171,10 +171,8 @@ test_expect_success 'checkout to detach HEAD' '
 	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>
+Note: moving to '\''renamer^'\'' which isn'\''t a local branch.
+Any commits you may make will not affect the commit with this name.
 HEAD is now at 7329388... Initial A one, A two
 EOF
 ) &&
-- 
1.6.5.9.ge994f.dirty

Re: [PATCH] Proof-of-concept patch to remember what the detached HEAD was

From: Jeff King <hidden>
Date: 2016-06-15 22:47:33

On Wed, Oct 14, 2009 at 12:44:34AM -0400, Daniel Barkalow wrote:
+char *get_detached_head_string(void)
+{
+	char *filename = git_path("DETACH_NAME");
+	struct stat st;
+	if (stat(filename, &st) || !S_ISREG(st.st_mode))
+		return NULL;
+	struct strbuf buf = STRBUF_INIT;
+	strbuf_read_file(&buf, filename, st.st_size);
+	strbuf_trim(&buf);
+	return strbuf_detach(&buf, 0);
+}
Would it hurt to tuck this information into HEAD itself, as we already
put arbitrary text into FETCH_HEAD?

-Peff

Re: [PATCH] Proof-of-concept patch to remember what the detached HEAD was

From: Johannes Schindelin <hidden>
Date: 2016-06-15 22:47:33

Hi,

On Wed, 14 Oct 2009, Jeff King wrote:
On Wed, Oct 14, 2009 at 12:44:34AM -0400, Daniel Barkalow wrote:
quoted
+char *get_detached_head_string(void)
+{
+	char *filename = git_path("DETACH_NAME");
+	struct stat st;
+	if (stat(filename, &st) || !S_ISREG(st.st_mode))
+		return NULL;
+	struct strbuf buf = STRBUF_INIT;
+	strbuf_read_file(&buf, filename, st.st_size);
+	strbuf_trim(&buf);
+	return strbuf_detach(&buf, 0);
+}
Would it hurt to tuck this information into HEAD itself, as we already
put arbitrary text into FETCH_HEAD?
AFAIR we still remember HEAD to be a symlink.

Ciao,
Dscho

Re: [PATCH] Proof-of-concept patch to remember what the detached HEAD was

From: Jeff King <hidden>
Date: 2016-06-15 22:47:33

On Wed, Oct 14, 2009 at 12:33:22PM +0200, Johannes Schindelin wrote:
quoted
quoted
+char *get_detached_head_string(void)
+{
+	char *filename = git_path("DETACH_NAME");
+	struct stat st;
+	if (stat(filename, &st) || !S_ISREG(st.st_mode))
+		return NULL;
+	struct strbuf buf = STRBUF_INIT;
+	strbuf_read_file(&buf, filename, st.st_size);
+	strbuf_trim(&buf);
+	return strbuf_detach(&buf, 0);
+}
Would it hurt to tuck this information into HEAD itself, as we already
put arbitrary text into FETCH_HEAD?
AFAIR we still remember HEAD to be a symlink.
I think that has been abandoned for detached HEAD (that is, if you
support only symlinked HEAD, then you cannot detach at all). But I might
be wrong. It has been a while since I looked at that code.

-Peff

Re: [PATCH] Proof-of-concept patch to remember what the detached HEAD was

From: Daniel Barkalow <hidden>
Date: 2016-06-15 22:47:33

On Wed, 14 Oct 2009, Jeff King wrote:
On Wed, Oct 14, 2009 at 12:44:34AM -0400, Daniel Barkalow wrote:
quoted
+char *get_detached_head_string(void)
+{
+	char *filename = git_path("DETACH_NAME");
+	struct stat st;
+	if (stat(filename, &st) || !S_ISREG(st.st_mode))
+		return NULL;
+	struct strbuf buf = STRBUF_INIT;
+	strbuf_read_file(&buf, filename, st.st_size);
+	strbuf_trim(&buf);
+	return strbuf_detach(&buf, 0);
+}
Would it hurt to tuck this information into HEAD itself, as we already
put arbitrary text into FETCH_HEAD?
I don't know; I'll have to try that and see if the tools that handle HEAD 
are happy with extra text there. If it works, it's a good solution.

I think I tried it at some point and things failed all over the place, but 
that may have been before symrefs, when you could get the actual sha1 
hash out of HEAD with "$(cat .git/HEAD)".

	-Daniel
*This .sig left intentionally blank*

Re: [PATCH] Proof-of-concept patch to remember what the detached HEAD was

From: Jay Soffian <hidden>
Date: 2016-06-15 22:47:33

On Wed, Oct 14, 2009 at 12:44 AM, Daniel Barkalow [off-list ref] wrote:
 $ git commit
 You've been sightseeing "origin/master". The commit can't change that
 value, so your commit isn't held in any branch. If you want to create
 a branch to hold it, here's how.

"git checkout origin/master" should be similar in complexity to
"svn checkout -r 8655"; the difference is that svn won't let you
commit then and git will but you'll need to understand the
implications if you do so. If you don't commit (because you don't want
to make any changes, because you don't think it would be possible, or
because you don't want to worry about what would happen), there's no
meaningful difference, and you don't need to be told.
Huh, I hadn't seen this message before I wrote in a reply to
"builtin-checkout: suggest creating local branch" that we do the
following at commit, which I think is what you're suggesting:

$ git commit -m "blah"
Cannot commit while not on any branch. Please use git commit -b <branch> to
specify the name of a new branch to commit to, or use git commit -f to
force a detached commit.

I'm not sure that requires the complexity of remembering how the user
got detached though?

j.

Re: [PATCH] Proof-of-concept patch to remember what the detached HEAD was

From: Daniel Barkalow <hidden>
Date: 2016-06-15 22:47:33

On Wed, 14 Oct 2009, Jay Soffian wrote:
On Wed, Oct 14, 2009 at 12:44 AM, Daniel Barkalow [off-list ref] wrote:
quoted
 $ git commit
 You've been sightseeing "origin/master". The commit can't change that
 value, so your commit isn't held in any branch. If you want to create
 a branch to hold it, here's how.

"git checkout origin/master" should be similar in complexity to
"svn checkout -r 8655"; the difference is that svn won't let you
commit then and git will but you'll need to understand the
implications if you do so. If you don't commit (because you don't want
to make any changes, because you don't think it would be possible, or
because you don't want to worry about what would happen), there's no
meaningful difference, and you don't need to be told.
Huh, I hadn't seen this message before I wrote in a reply to
"builtin-checkout: suggest creating local branch" that we do the
following at commit, which I think is what you're suggesting:

$ git commit -m "blah"
Cannot commit while not on any branch. Please use git commit -b <branch> to
specify the name of a new branch to commit to, or use git commit -f to
force a detached commit.
The difference is that some experienced users depend on being able to 
commit while not on a branch, and want to not get a warning for every 
commit while not on a branch.
I'm not sure that requires the complexity of remembering how the user
got detached though?
What matters there is actually whether we got to the present state by 
committing or not. It's also relevant to telling the user what they've got 
checked out that isn't a branch.

	-Daniel
*This .sif left intentionally blank*

Re: [PATCH] Proof-of-concept patch to remember what the detached HEAD was

From: Nicolas Pitre <nico@fluxnic.net>
Date: 2016-06-15 22:47:33

On Wed, 14 Oct 2009, Daniel Barkalow wrote:
On Wed, 14 Oct 2009, Jay Soffian wrote:
quoted
$ git commit -m "blah"
Cannot commit while not on any branch. Please use git commit -b <branch> to
specify the name of a new branch to commit to, or use git commit -f to
force a detached commit.
The difference is that some experienced users depend on being able to 
commit while not on a branch, and want to not get a warning for every 
commit while not on a branch.
I assume that the -f would silence any warning?


Nicolas

Re: [PATCH] Proof-of-concept patch to remember what the detached HEAD was

From: Daniel Barkalow <hidden>
Date: 2016-06-15 22:47:33

On Wed, 14 Oct 2009, Nicolas Pitre wrote:
On Wed, 14 Oct 2009, Daniel Barkalow wrote:
quoted
On Wed, 14 Oct 2009, Jay Soffian wrote:
quoted
$ git commit -m "blah"
Cannot commit while not on any branch. Please use git commit -b <branch> to
specify the name of a new branch to commit to, or use git commit -f to
force a detached commit.
The difference is that some experienced users depend on being able to 
commit while not on a branch, and want to not get a warning for every 
commit while not on a branch.
I assume that the -f would silence any warning?
I suppose; I don't know if that would be acceptable to the relevant users. 
It would certainly require script changes, but that's not an issue for 
1.7.0, presumably.

I personally normally use the order:

$ git checkout origin/master
(change stuff, test)
$ git checkout -b my-topic
$ git commit

So I only care about detaching, not committing while detached, and I'm not 
the right person to ask.

	-Daniel
*This .sig left intentionally blank*

Re: [PATCH] Proof-of-concept patch to remember what the detached HEAD was

From: Christoph Bartoschek <hidden>
Date: 2016-06-15 22:47:34

Daniel Barkalow wrote:
The upshot of the messages should be:

 $ git checkout origin/master
 Since you can't actually change "origin/master" yourself, you'll just
 be sightseeing unless you create a local branch to hold new local work.

 $ git branch
 * (not a local branch, but "origin/master")

 $ git commit
 You've been sightseeing "origin/master". The commit can't change that
 value, so your commit isn't held in any branch. If you want to create
 a branch to hold it, here's how.

I appreciate such a message for git branch as a user. Today I had the 
following problem:

I wanted to compile Qt Creator 1.3 from their repository. I cloned it with 
git clone and then issued git checkout origin/1.3.0-beta. First there came a 
the warning and I was not sure whether the checkout succeeded at all. I had 
to ask in the IRC channel whether the checkout was ok.

But then I was not able to verify that the checkout indeed matched the 
1.3.0-beta.  "git status" and "git branch" did not help here. 

Having an improved "git branch" would help a lot, especially if one returns 
some weeks later to the directory and wants to know what the checkout is.

Christoph
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help