[PATCH] Detached HEAD (experimental)

Subsystems: the rest

STALE3707d

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

[PATCH] Detached HEAD (experimental)

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:42:47

This allows "git checkout -d v1.4.3" to detach the HEAD from any
branch but point directly at the named commit.  After this, "git
branch" starts reporting that you are not on any branch.  You
can merge into "current branch" although there is not even such
a thing.

You can go back the normal state by switching to an existing
branch, say, "git checkout master" for example.  Another way to
get out of this is "git checkout -b newbranch".

This is still experimental.  While I think it makes sense to
allow commits on top of detached HEAD, it is rather dangerous
unless you are careful and know what you are doing.  Next "git
checkout master" will obviously lose what you have done, so we
might want to require "git checkout -f" out of a detached HEAD
if we find that the HEAD commit is not an ancestor of any other
branches.

On the other hand, the reason the user did not start the ad-hoc
work on a new branch with "git checkout -b" was probably because
the work was of a throw-away nature, so the convenience of not
having that safety valve might be even better.  We'll see.

Signed-off-by: Junio C Hamano <redacted>
---
 builtin-branch.c |   36 ++++++++++++++++++++++++++----------
 cache.h          |    2 +-
 git-checkout.sh  |   22 +++++++++++++++++++---
 path.c           |   26 ++++++++++++++++++--------
 setup.c          |    5 +++--
 5 files changed, 67 insertions(+), 24 deletions(-)
diff --git a/builtin-branch.c b/builtin-branch.c
index 745ee04..71f88f2 100644
--- a/builtin-branch.c
+++ b/builtin-branch.c
@@ -299,7 +299,8 @@ static void print_ref_list(int kinds, int verbose, int abbrev)
 	free_ref_list(&ref_list);
 }
 
-static void create_branch(const char *name, const char *start,
+static void create_branch(const char *name, const char *start_name,
+			  unsigned char *start_sha1,
 			  int force, int reflog)
 {
 	struct ref_lock *lock;
@@ -318,9 +319,14 @@ static void create_branch(const char *name, const char *start,
 			die("Cannot force update the current branch.");
 	}
 
-	if (get_sha1(start, sha1) ||
-	    (commit = lookup_commit_reference(sha1)) == NULL)
-		die("Not a valid branch point: '%s'.", start);
+	if (start_sha1)
+		/* detached HEAD */
+		hashcpy(sha1, start_sha1);
+	else if (get_sha1(start_name, sha1))
+		die("Not a valid object name: '%s'.", start_name);
+
+	if ((commit = lookup_commit_reference(sha1)) == NULL)
+		die("Not a valid branch point: '%s'.", start_name);
 	hashcpy(sha1, commit->object.sha1);
 
 	lock = lock_any_ref_for_update(ref, NULL);
@@ -329,7 +335,8 @@ static void create_branch(const char *name, const char *start,
 
 	if (reflog) {
 		log_all_ref_updates = 1;
-		snprintf(msg, sizeof msg, "branch: Created from %s", start);
+		snprintf(msg, sizeof msg, "branch: Created from %s",
+			 start_name);
 	}
 
 	if (write_ref_sha1(lock, sha1, msg) < 0)
@@ -341,6 +348,9 @@ static void rename_branch(const char *oldname, const char *newname, int force)
 	char oldref[PATH_MAX], newref[PATH_MAX], logmsg[PATH_MAX*2 + 100];
 	unsigned char sha1[20];
 
+	if (!oldname)
+		die("cannot rename the curren branch while not on any.");
+
 	if (snprintf(oldref, sizeof(oldref), "refs/heads/%s", oldname) > sizeof(oldref))
 		die("Old branchname too long");
 
@@ -447,9 +457,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 	head = xstrdup(resolve_ref("HEAD", head_sha1, 0, NULL));
 	if (!head)
 		die("Failed to resolve HEAD as a valid ref.");
-	if (strncmp(head, "refs/heads/", 11))
-		die("HEAD not found below refs/heads!");
-	head += 11;
+	if (!strcmp(head, "HEAD")) {
+		/* detached HEAD */
+		;
+	}
+	else {
+		if (strncmp(head, "refs/heads/", 11))
+			die("HEAD not found below refs/heads!");
+		head += 11;
+	}
 
 	if (delete)
 		return delete_branches(argc - i, argv + i, force_delete, kinds);
@@ -460,9 +476,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 	else if (rename && (i == argc - 2))
 		rename_branch(argv[i], argv[i + 1], force_rename);
 	else if (i == argc - 1)
-		create_branch(argv[i], head, force_create, reflog);
+		create_branch(argv[i], head, head_sha1, force_create, reflog);
 	else if (i == argc - 2)
-		create_branch(argv[i], argv[i + 1], force_create, reflog);
+		create_branch(argv[i], argv[i+1], NULL, force_create, reflog);
 	else
 		usage(builtin_branch_usage);
 
diff --git a/cache.h b/cache.h
index 29dd290..891045c 100644
--- a/cache.h
+++ b/cache.h
@@ -296,7 +296,7 @@ extern char *sha1_to_hex(const unsigned char *sha1);	/* static buffer result! */
 extern int read_ref(const char *filename, unsigned char *sha1);
 extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
 extern int create_symref(const char *ref, const char *refs_heads_master);
-extern int validate_symref(const char *ref);
+extern int validate_headref(const char *ref);
 
 extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
 extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2);
diff --git a/git-checkout.sh b/git-checkout.sh
index 92ec069..c50df28 100755
--- a/git-checkout.sh
+++ b/git-checkout.sh
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-USAGE='[-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]'
+USAGE='[-f] [-b <new_branch>] [-d] [-m] [<branch>] [<paths>...]'
 SUBDIRECTORY_OK=Sometimes
 . git-sh-setup
 
@@ -12,6 +12,7 @@ force=
 branch=
 newbranch=
 newbranch_log=
+detached=
 merge=
 while [ "$#" != "0" ]; do
     arg="$1"
@@ -27,6 +28,9 @@ while [ "$#" != "0" ]; do
 		git-check-ref-format "heads/$newbranch" ||
 			die "git checkout: we do not like '$newbranch' as a branch name."
 		;;
+	-d)
+		detached=1
+		;;
 	"-l")
 		newbranch_log=1
 		;;
@@ -144,13 +148,25 @@ fi
 # are switching to, then we'd better just be checking out
 # what we already had
 
-[ -z "$branch$newbranch" ] &&
-	[ "$new" != "$old" ] &&
+if test -z "$branch$newbranch" && test "$new" != "$old"
+then
+	case "$detached" in
+	'')
 	die "git checkout: provided reference cannot be checked out directly
 
   You need -b to associate a new branch with the wanted checkout. Example:
   git checkout -b <new_branch_name> $arg
 "
+		;;
+	1)
+		# NEEDSWORK: we would want to have this command here
+		# that allows us to detach the HEAD atomically.
+		# git update-ref --detach HEAD "$new"
+		rm -f "$GIT_DIR/HEAD"
+		echo "$new" >"$GIT_DIR/HEAD"
+		;;
+	esac
+fi
 
 if [ "X$old" = X ]
 then
diff --git a/path.c b/path.c
index 066f621..94ddd7e 100644
--- a/path.c
+++ b/path.c
@@ -90,10 +90,11 @@ int git_mkstemp(char *path, size_t len, const char *template)
 }
 
 
-int validate_symref(const char *path)
+int validate_headref(const char *path)
 {
 	struct stat st;
 	char *buf, buffer[256];
+	unsigned char sha1[20];
 	int len, fd;
 
 	if (lstat(path, &st) < 0)
@@ -119,14 +120,23 @@ int validate_symref(const char *path)
 	/*
 	 * Is it a symbolic ref?
 	 */
-	if (len < 4 || memcmp("ref:", buffer, 4))
+	if (len < 4)
 		return -1;
-	buf = buffer + 4;
-	len -= 4;
-	while (len && isspace(*buf))
-		buf++, len--;
-	if (len >= 5 && !memcmp("refs/", buf, 5))
+	if (!memcmp("ref:", buffer, 4)) {
+		buf = buffer + 4;
+		len -= 4;
+		while (len && isspace(*buf))
+			buf++, len--;
+		if (len >= 5 && !memcmp("refs/", buf, 5))
+			return 0;
+	}
+
+	/*
+	 * Is this a detached HEAD?
+	 */
+	if (!get_sha1_hex(buffer, sha1))
 		return 0;
+
 	return -1;
 }
 
@@ -241,7 +251,7 @@ char *enter_repo(char *path, int strict)
 		return NULL;
 
 	if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
-	    validate_symref("HEAD") == 0) {
+	    validate_headref("HEAD") == 0) {
 		putenv("GIT_DIR=.");
 		check_repository_format();
 		return path;
diff --git a/setup.c b/setup.c
index 2ae57f7..cc97f9f 100644
--- a/setup.c
+++ b/setup.c
@@ -138,7 +138,8 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
  *    GIT_OBJECT_DIRECTORY environment variable
  *  - a refs/ directory
  *  - either a HEAD symlink or a HEAD file that is formatted as
- *    a proper "ref:".
+ *    a proper "ref:", or a regular file HEAD that has a properly
+ *    formatted sha1 object name.
  */
 static int is_git_directory(const char *suspect)
 {
@@ -161,7 +162,7 @@ static int is_git_directory(const char *suspect)
 		return 0;
 
 	strcpy(path + len, "/HEAD");
-	if (validate_symref(path))
+	if (validate_headref(path))
 		return 0;
 
 	return 1;
-- 
1.5.0.rc0.gab5a

Re: [PATCH] Detached HEAD (experimental)

From: Edgar Toernig <hidden>
Date: 2016-06-15 22:42:47

[Note: casual git-user speaking]

Junio C Hamano wrote:
This allows "git checkout -d v1.4.3" to detach the HEAD from any
branch but point directly at the named commit.  After this, "git
branch" starts reporting that you are not on any branch.
Nice.  But why -d?  Create more confusion? [1]
You can go back the normal state by switching to an existing
branch, say, "git checkout master" for example.
This is fine.  Often you want to test a couple of tags, one
after another, so "git checkout v1", "git checkout v2", ...
should work.
Another way to get out of this is "git checkout -b newbranch".
Wth should this do?  I already noticed this line in your posting
from 29.Dec: [slightly edited]

	$ git checkout v1.5.0
	Checking out a tag -- you are not on any branch now...
	$ <modify>
	$ git commit -m 'fix' -a
	You cannot commit without a current branch.
	$ git checkout -b maint-1.5.0
	$ git commit -m 'fix' -a

I assume it will create a new branch and modify HEAD so that
the current working dir/index gets committed into that branch.
(Basically "git branch main-1.5.0 &&
            echo 'ref: refs/head/main-1.5.0' >.git/HEAD")
If that's the case, I was looking for that incantation for
a long time and couldn't find it.  I'm using the git-branch
and echo as shown above to get that.  The man-page isn't
very helpful for that special case of git-checkout.

Anyway, if I want to commit and git tells me that I can't
because I'm not on a branch, the _most unintuitive_ thing
would be calling 'git-checkout'.  I want to checkin!  No
way that I call checkout and risk losing all my changes.

What's wrong with a -b option to commit, similar to -b on
checkout?

	$ git checkout v1.5.0
	Checking out a tag -- you are not on any branch now...
	$ <modify>
	$ git commit -m 'fix' -a
	You cannot commit without a current branch.
	Give '-b <newbranch>' to commit into a new branch. 
	$ git commit -b maint-1.5.0 -m 'fix' -a

Another variant (the one I prefer): commit just updates HEAD and
git-branch can be used to give it a name (and switch to it!).
So these workflows would be possible:

Name after commit:

	$ git checkout v1.5.0
	Checking out a tag -- you are not on any branch now...
	$ git branch
	  master
	* (unnamed) c8ff51290518949225c832bae1e22b1bba6ab2cd
	$ <modify>
	$ git commit -m 'fix' -a
	Warning: data committed into unnamed branch.
	Give it a name now with "git branch <newname>"
	$ git branch
	  master
	* (unnamed) 13482f25863e5380cdd41065338e1709d469a605
	$ git branch maint-1.5.0
	$ git branch
	  master
	* main-1.5.0

or name before commit:

	$ git checkout v1.5.0
	Checking out a tag -- you are not on any branch now...
	$ <modify>
	$ git branch
	  master
	* (unnamed) c8ff51290518949225c832bae1e22b1bba6ab2cd
	$ git branch maint-1.5.0
	$ git branch
	  master
	* maint-1.5.0
	$ git commit -m 'fix' -a

Yes, 'git branch <newname>' would get a new semantic:
if no start-point is given the newname will become the
new current branch.

[Btw, I would even do that when we are on some branch. How
often did I do a checkout of a regular branch and only later
noticed, that I want to commit changes into a temp-branch:

	$ git checkout master
	$ <play around, fix compile issues, add debug stuff>
	$ git branch debug
	$ git branch
	  master
	* debug
	$ git commit -a -m "Add foo debugging code"
]

But that special case is IMHO (M = my, a casual user's) much
better than some weird checkout-incantations.

Ciao, ET.


[1] My pet-peeve:

	$ git checkout foo
	fatal: Entry 'bar' not uptodate. Cannot merge.

    What the heck?  Nobody asked for a merge!?!?!  What
    is it trying to do?  Does it actually mean:

	fatal: Working dir is dirty.  Either give '-f'
	to force the checkout and lose your changes, or
	give '-m' to merge 'foo' and your changes.

   ?  But then, why does an 'rm bar' fixes that?  Now 'bar'
   definitely isn't "uptodate".

Re: [PATCH] Detached HEAD (experimental)

From: Carl Worth <hidden>
Date: 2016-06-15 22:42:47

On Mon, 01 Jan 2007 23:45:08 -0800, Junio C Hamano wrote:
This allows "git checkout -d v1.4.3" to detach the HEAD from any
branch but point directly at the named commit.
Being able to perform "checkout" with a tag like this, (and no
specific branch), is something I've been wanting git to acquire for
some time. So, thanks for coding this up!
This is still experimental.  While I think it makes sense to
allow commits on top of detached HEAD, it is rather dangerous
unless you are careful and know what you are doing.
This part I don't understand. I don't see why it's useful to introduce
new danger to "git checkout" in that after this change it could cause
commits to become dangling. Currently, "git checkout" is entirely
safe, as are most git commands. The few commands that create dangling
commits require fairly explicit actions from the user, (such as
"--hard" for git-reset or -D instead of -d for git-branch).

So I'd vote against this aspect. I'd rather see commits to a detached
head be disallowed with a message instructing the user to do "git
checkout -b new-branch" in order to do the commit.

And with that new safer behavior, I think it would be a good idea to
just drop the "-d" option from git-checkout.

I want this new behavior not for people who know what they are doing,
but people who are using git only incidentally, (say they just want to
acquire and build the latest version of some software). So I'd like
the sequence to work along the lines of your earlier post, (as quoted
by another reply). Specifically, I wouldn't want to see any warning
about a "missing branch" until a commit was attempted.

This would allow a sequence like this to proceed without git ever
telling the user they were doing something "wrong":

	$ git clone git://git.kernel.org/pub/scm/git/git.git
	$ cd git
	$ git checkout v1.4.3
	$ make

With the recent improvements to the git-checkout error message
(thanks!) this sequence is at least successful eventually after the
user reads and responds to the following:

	git checkout: provided reference cannot be checked out directly

	  You need -b to associate a new branch with the wanted
	  checkout. Example:
	  git checkout -b <new_branch_name> v1.4.3

But the user is required to invent a name and deal with its existence
later. For example, after some time, imagine the same user wanting
to update to the latest and build again:

	git pull origin
	git checkout v1.5.0

Now the user has to invent _another_ unique branch name, (or learn
"git branch -d" or "git reset --hard" or ...), while this branch
concept and these other commands aren't actually helping the user with
the task at hand, (just tracking the code and building the most recent
version).

Similarly, I think this use case of "just tracking" should support
branches disappearing from the remote repository without the user
having to edit any config file. If there are entries that are
automatically added by git-clone that should be removed later, that
should happen automatically. A recent thread suggested adding an error
message instructing the user to delete the entries. That's again
unkind to a user who doesn't really want to learn git, but just wants
to get at the most recent version of some code that happens to be
available through git.

That disappearing branches cause problems requiring manual cleanup of
configuration files is one of the reasons that we are not using any
feature branches in the "central" cairo repository, for example, (we
do have branches for release maintenance). I'd really like to be able
to put some feature branches there for shared work, (rather than
forcing that work out to separate personal repositories as we do
know).

Maybe the configuration file entries added by git-clone need to be
marked in some way to distinguish them from manually added entries, so
that we would feel more comfortable automatically removing them when a
remote branch has disappeared.

-Carl

Re: [PATCH] Detached HEAD (experimental)

From: Jakub Narebski <hidden>
Date: 2016-06-15 22:42:47

Carl Worth wrote:
Similarly, I think this use case of "just tracking" should support
branches disappearing from the remote repository without the user
having to edit any config file. If there are entries that are
automatically added by git-clone that should be removed later, that
should happen automatically. A recent thread suggested adding an error
message instructing the user to delete the entries. That's again
unkind to a user who doesn't really want to learn git, but just wants
to get at the most recent version of some code that happens to be
available through git.

That disappearing branches cause problems requiring manual cleanup of
configuration files is one of the reasons that we are not using any
feature branches in the "central" cairo repository, for example, (we
do have branches for release maintenance). I'd really like to be able
to put some feature branches there for shared work, (rather than
forcing that work out to separate personal repositories as we do
know).

Maybe the configuration file entries added by git-clone need to be
marked in some way to distinguish them from manually added entries, so
that we would feel more comfortable automatically removing them when a
remote branch has disappeared.
Is it still problem (the dissapearing remote branches) with the new
wildcard remote.<name>.fetch generated by new git-clone? I think it
should not complain that some branches vanished, but it would not I think
it would remove no longer needed tracking branches (local branches)
for us...

-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git

[PATCH] git-branch: show detached HEAD

From: Lars Hjemli <hidden>
Date: 2016-06-15 22:42:47

This makes git-branch show a detached HEAD as '* (no branch)'.

Signed-off-by: Lars Hjemli <redacted>
---

This might be a premature patch. But if/when we allow HEAD to be detached, 
git-branch should tell us that HEAD is the current 'branch'.

 builtin-branch.c |  103 +++++++++++++++++++++++++++++-------------------------
 1 files changed, 55 insertions(+), 48 deletions(-)
diff --git a/builtin-branch.c b/builtin-branch.c
index 71f88f2..16f86cc 100644
--- a/builtin-branch.c
+++ b/builtin-branch.c
@@ -231,29 +231,54 @@ static int ref_cmp(const void *r1, const void *r2)
 	return strcmp(c1->name, c2->name);
 }
 
-static void print_ref_info(const unsigned char *sha1, int abbrev)
+static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, 
+			   int abbrev, int current)
 {
+	char c;
+	int color;
 	struct commit *commit;
 	char subject[256];
 
+	switch (item->kind) {
+	case REF_LOCAL_BRANCH:
+		color = COLOR_BRANCH_LOCAL;
+		break;
+	case REF_REMOTE_BRANCH:
+		color = COLOR_BRANCH_REMOTE;
+		break;
+	default:
+		color = COLOR_BRANCH_PLAIN;
+		break;
+	}
 
-	commit = lookup_commit(sha1);
-	if (commit && !parse_commit(commit))
-		pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
-				    subject, sizeof(subject), 0,
-				    NULL, NULL, 0);
-	else
-		strcpy(subject, " **** invalid ref ****");
+	c = ' ';
+	if (current) {
+		c = '*';
+		color = COLOR_BRANCH_CURRENT;
+	}
 
-	printf(" %s %s\n", find_unique_abbrev(sha1, abbrev), subject);
+	if (verbose) {
+		commit = lookup_commit(item->sha1);
+		if (commit && !parse_commit(commit))
+			pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
+					    subject, sizeof(subject), 0,
+					    NULL, NULL, 0);
+		else
+			strcpy(subject, " **** invalid ref ****");
+		printf("%c %s%-*s%s %s %s\n", c, branch_get_color(color),
+		       maxwidth, item->name,
+		       branch_get_color(COLOR_BRANCH_RESET),
+		       find_unique_abbrev(item->sha1, abbrev), subject);
+	} else {
+		printf("%c %s%s%s\n", c, branch_get_color(color), item->name,
+		       branch_get_color(COLOR_BRANCH_RESET));
+	}
 }
 
-static void print_ref_list(int kinds, int verbose, int abbrev)
+static void print_ref_list(int kinds, int verbose, int abbrev, int detached)
 {
 	int i;
-	char c;
 	struct ref_list ref_list;
-	int color;
 
 	memset(&ref_list, 0, sizeof(ref_list));
 	ref_list.kinds = kinds;
@@ -261,39 +286,22 @@ static void print_ref_list(int kinds, int verbose, int abbrev)
 
 	qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
 
-	for (i = 0; i < ref_list.index; i++) {
-		switch( ref_list.list[i].kind ) {
-			case REF_LOCAL_BRANCH:
-				color = COLOR_BRANCH_LOCAL;
-				break;
-			case REF_REMOTE_BRANCH:
-				color = COLOR_BRANCH_REMOTE;
-				break;
-			default:
-				color = COLOR_BRANCH_PLAIN;
-				break;
-		}
-
-		c = ' ';
-		if (ref_list.list[i].kind == REF_LOCAL_BRANCH &&
-				!strcmp(ref_list.list[i].name, head)) {
-			c = '*';
-			color = COLOR_BRANCH_CURRENT;
-		}
+	if (detached && (kinds & REF_LOCAL_BRANCH)) {
+		struct ref_item item;
+		item.name = "(no branch)";
+		item.kind = REF_LOCAL_BRANCH;
+		hashcpy(item.sha1, head_sha1);
+		if (strlen(item.name) > ref_list.maxwidth)
+			      ref_list.maxwidth = strlen(item.name);
+		print_ref_item(&item, ref_list.maxwidth, verbose, abbrev, 1);
+	}
 
-		if (verbose) {
-			printf("%c %s%-*s%s", c,
-					branch_get_color(color),
-					ref_list.maxwidth,
-					ref_list.list[i].name,
-					branch_get_color(COLOR_BRANCH_RESET));
-			print_ref_info(ref_list.list[i].sha1, abbrev);
-		}
-		else
-			printf("%c %s%s%s\n", c,
-					branch_get_color(color),
-					ref_list.list[i].name,
-					branch_get_color(COLOR_BRANCH_RESET));
+	for (i = 0; i < ref_list.index; i++) {
+		int current = !(detached && (kinds & REF_LOCAL_BRANCH)) &&
+			(ref_list.list[i].kind == REF_LOCAL_BRANCH) &&
+			!strcmp(ref_list.list[i].name, head);
+		print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose, 
+			       abbrev, current);
 	}
 
 	free_ref_list(&ref_list);
@@ -380,7 +388,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 {
 	int delete = 0, force_delete = 0, force_create = 0;
 	int rename = 0, force_rename = 0;
-	int verbose = 0, abbrev = DEFAULT_ABBREV;
+	int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
 	int reflog = 0;
 	int kinds = REF_LOCAL_BRANCH;
 	int i;
@@ -458,8 +466,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 	if (!head)
 		die("Failed to resolve HEAD as a valid ref.");
 	if (!strcmp(head, "HEAD")) {
-		/* detached HEAD */
-		;
+		detached = 1;
 	}
 	else {
 		if (strncmp(head, "refs/heads/", 11))
@@ -470,7 +477,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 	if (delete)
 		return delete_branches(argc - i, argv + i, force_delete, kinds);
 	else if (i == argc)
-		print_ref_list(kinds, verbose, abbrev);
+		print_ref_list(kinds, verbose, abbrev, detached);
 	else if (rename && (i == argc - 1))
 		rename_branch(head, argv[i], force_rename);
 	else if (rename && (i == argc - 2))
-- 
1.5.0.rc0.g76033

Re: [PATCH] git-branch: show detached HEAD

From: Shawn O. Pearce <hidden>
Date: 2016-06-15 22:42:47

Lars Hjemli [off-list ref] wrote:
This makes git-branch show a detached HEAD as '* (no branch)'.
It would be nicer if when you are on a remote tracking branch or
on a tag that the name of the tag or the remote tracking branch is
shown rather than '* (no branch)'.

-- 
Shawn.

Re: [PATCH] git-branch: show detached HEAD

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:42:47

"Shawn O. Pearce" [off-list ref] writes:
Lars Hjemli [off-list ref] wrote:
quoted
This makes git-branch show a detached HEAD as '* (no branch)'.
It would be nicer if when you are on a remote tracking branch or
on a tag that the name of the tag or the remote tracking branch is
shown rather than '* (no branch)'.
That would be utterly confusing if you are talking about the
same detached HEAD semantics as I and Carl discussed today.  I
like what Lars's patch does (although I felt that it was too big
for only doing this which made it harder to judge), but I would
even make it stronger to say something like:

	* You are not on ANY branch right now.
          master
          next
          pu
          ...

You will never be _on_ a remote tracking branch.  So far we did
not allow HEAD to point at outside refs/heads/ and we still
don't.  When HEAD is detached from any branch, however, it can
store a bare 40-hex (plus LF) commit object name instead of
being a symref.  You are not on any branch at that point.

Most importantly, if we allow commits to be built on top of HEAD
while it is detached from any branch, the commit will _not_
advance any branch.  So showing the remote tracking branch the
way you suggest will be misleading.

If we do not allow commits to be built on top, we would still
allow something to be done other than switching out of "detached
mode" to be useful.  For example, reset to move around which
commit to look at would be a useful thing.  Another of my
unstated desire is to get rid of the use of special "bisect"
branch during bisection using detached HEAD.  Again, if we
highlight remote tracking branch whose tip happens to be the
same commit as the current HEAD as you suggest, that would lead
to quite confusing behaviour.  Sometimes it would say the same
thing as you are _on_ that branch (which confuses you because
you are _not_ on that branch in reality), sometimes it would
highlight nothing to show you are not on any branch.

Re: [PATCH] git-branch: show detached HEAD

From: Lars Hjemli <hidden>
Date: 2016-06-15 22:42:47

On 1/3/07, Junio C Hamano [off-list ref] wrote:
I would even make it stronger to say something like:

        * You are not on ANY branch right now.
          master
          next
          pu
          ...
Hmm, that wouldn't be very nice for 'git-branch -v' (which suddenly
got extra useful with detached head).

-- 
larsh

Re: [PATCH] Detached HEAD (experimental)

From: Carl Worth <hidden>
Date: 2016-06-15 22:42:48

On Tue, 02 Jan 2007 23:18:13 +0100, Jakub Narebski wrote:
Is it still problem (the dissapearing remote branches) with the new
wildcard remote.<name>.fetch generated by new git-clone?
Ah, you're right. Sorry I missed that, (looks like it was in next but
not in master). I had said I was going to start running from next to
test this stuff.

So now I tried switching to next and hit a couple minor surprises,
(not big issues---mostly just me really trying out separate-remotes
for the first time, and pretending to some extent that I don't know
anything about it):

	$ git checkout next
	error: pathspec 'next' did not match any file(s) known to git.
	Did you forget to 'git add'?

Here I'm using a branch name that cannot be resolved but I'm getting
an error message based on a path name that cannot be resolved. It's
tricky to know how to construct the correct error message here since
"git checkout" can accept either a branch name or a path name. But I
would argue that the branch name is the primary thing for "git
checkout" to act on so the error message should talk about that if the
argument cannot be resolved as either a branch or a path. (Regardless,
"git add" would not be a helpful suggestion if someone were actually
trying to do "git checkout file").

So I know that "git checkout next" used to work, and I know that we're
now in a "separate remotes" world. But I don't know how everything
about how they work yet. Clearly just using "next" doesn't resolve to
anything anymore. So let's see what we have to work with:

	$ git branch
	* master

Hmm... nothing to see here (though the fact that the remote-tracking
branches don't show up here is generally quite nice---I love the
reduced noise). But maybe we want at least an indication of what's
not being shown? Maybe something like:

	$ git branch
	* master
	[and 8 remote branches: use -r to see them as well]

That might avoid some confusion for upgraders anyway.

Moving on, I can see the "missing" branches with:

	$ git branch -r
	  origin/HEAD
	  origin/html
	  origin/maint
	  origin/man
	  origin/master
	  origin/next
	  origin/pu
	  origin/todo

And now I try to check one out:

	$ git checkout origin/next
	git checkout: provided reference cannot be checked out directly

	  You need -b to associate a new branch with the wanted checkout. Example:
	  git checkout -b <new_branch_name> origin/next

And now I start getting confused. If git-checkout wants a branch, and
git-branch says that "origin/next" is a branch, then why won't this
work? OK, I know that something's special about origin/next, (it's a
"remote-tracking branch" and I needed a -r option to get git-branch to
list it for me), but nothing in the git-checkout documentation would
lead me to expect that "git checkout origin/next" wouldn't work.

But at least I'm given a very clear error message here, (a great
improvement, thanks!), and even a sample command. So I can do:

	$ git checkout -b next origin/next

And I'm happy that works and I can build things.

But say in a couple of days I want to build the latest in Junio's
"next". What's the easiest recipe for that now? If I'm understanding
things correctly, I can update my remote-tracking origin/next with
just:

	$ git pull origin

And that's thanks to this entry in .git/config:

	[remote "origin"]
	        url = git://git.kernel.org/pub/scm/git/git.git
	        fetch = +refs/heads/*:refs/remotes/origin/*

I _think_ there's also a way for me to configure my local "next" to
automatically fast-forward to track what's happening in "origin/next"
on any invocation of "git pull origin", right? That is, configure
origin/next as the thing to get merged into my local next when I
pull. How do I do that again?  Where's that documented?

Ah, if I look in .git/config I can see that I should be able to just
copy the block for the "master" branch and come up with:

	[branch "next"]
	        remote = origin
	        merge = refs/heads/next

is that right? If so, it's really nice that what used to be hard-coded
magic, (first remote branch getting merged into current branch), is
now self-documented magic that can easily be applied to other branch
combinations. Another great improvement, well done!

The remaining question is whether it wouldn't make sense to just
create that block when I did "git checkout -b next origin/next". I
think this has been proposed before and Junio said "Maybe, if
everything can be resolved unambiguously, but even then, not
always". I'd be interested in hearing more about when that would be
the wrong thing to do. It seems it would be awfully convenient here,
(and not doing it means it's easy to end up with a local "next" branch
without realizing how stale it is).

Now, if I'm only tracking/building what's in next and not actually
planning on committing anything, then I wouldn't even need the local
branch at all if I could just checkout the remote tracking-branch
directly:

	$ git checkout origin/next

In fact, I'd greatly prefer this, since a lot of the advantage of
separate remotes is that "git branch" lists only stuff I'm actually
working on and not other noise from remote branches that I consider
uninteresting. Forcing me to clutter up that list just to examine the
state of some remote branch is not helpful.

Of course, this feature depends on the pending "detached HEAD" work
that started this thread, (wow, look at that, I wandered back on
topic!).

And a further question from there is whether it makes sense to have
"git checkout" look around in .git/refs/remotes/* so that I could
checkout origin/next by just using the name "next":

	$ git checkout next

which could complain if that couldn't be resolved without ambiguity.
Would that be a bad idea?

-Carl

Re: [PATCH] Detached HEAD (experimental)

From: J. Bruce Fields <hidden>
Date: 2016-06-15 22:42:48

On Tue, Jan 02, 2007 at 04:34:45PM -0800, Carl Worth wrote:
And now I start getting confused. If git-checkout wants a branch, and
git-branch says that "origin/next" is a branch, then why won't this
work? OK, I know that something's special about origin/next, (it's a
"remote-tracking branch" and I needed a -r option to get git-branch to
list it for me), but nothing in the git-checkout documentation would
lead me to expect that "git checkout origin/next" wouldn't work.
If we use the word "branches" for things that you can check out and
commit to, then "remote-tracking branches" are not actually branches.
Argh!

What would be better terminology here?

--b.

Re: [PATCH] Detached HEAD (experimental)

From: Alan Chandler <hidden>
Date: 2016-06-15 22:42:48

On Saturday 06 January 2007 18:58, J. Bruce Fields wrote:
If we use the word "branches" for things that you can check out and
commit to, then "remote-tracking branches" are not actually branches.
Argh!

What would be better terminology here?
Why can't we use the terms 'local branch' and 'remote branch'.  We can 
only commit to local branches - you need to push to remote ones.
-- 
Alan Chandler
http://www.chandlerfamily.org.uk

Re: [PATCH] Detached HEAD (experimental)

From: J. Bruce Fields <hidden>
Date: 2016-06-15 22:42:48

On Sat, Jan 06, 2007 at 08:48:15PM +0000, Alan Chandler wrote:
On Saturday 06 January 2007 18:58, J. Bruce Fields wrote:
quoted
If we use the word "branches" for things that you can check out and
commit to, then "remote-tracking branches" are not actually branches.
Argh!

What would be better terminology here?
Why can't we use the terms 'local branch' and 'remote branch'.  We can 
only commit to local branches - you need to push to remote ones.
We'd have to replace "branch" by "local branch" in a lot of
documentation, but that could work.

Though what do you call a branch in a remote repository then, if not a
remote branch?  I suppose it doesn't matter.

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