threads / discuss / 26510

checkout/branch needs some extra safety around the --track option

Subject: checkout/branch needs some extra safety around the --track option

## tl;dr

7 messages between Feb 15, 2011 and Feb 18, 2011.

replies: 6people: 3as markdown or json

Johan Herland· Feb 15, 2011, 17:52 UTC · lore
Hi,

I just got a problem report from a Git user a $dayjob, who was confused about the following output from 'git push' (without arguments, in a repo with push.default == 'tracking'):

Show 6 quoted lines
> ----------------------------------------------------------------
> $ git push
> Total 0 (delta 0), reused 0 (delta 0)
> To .
>     c9e7b5a..1bf9fed  mybranch -> v0.99
> ----------------------------------------------------------------

After some investigations, I could reproduce his problem with the following commands:

$ git checkout -b mybranch --track v0.99 # Do work on mybranch $ git push

In other words. The branch was erroneously created with the --track option, setting up a bogus branch.mybranch config section. When pushing, this caused the v0.99 tag to be (unexpectedly, to him) updated in the local repo.

Obviously, this is rooted in user error, but it occurs to me that Git should be more helpful in this situation. I propose:

1. When given "--track", branch/checkout should verify that the tracked 
branch is indeed a branch (preferably a remote branch, although I guess 
tracking a local branch can make sense in some situations). At least, 
it should deny tracking a _tag_. Tracking a tag simply does not make 
sense at all (unless you expect the tag to move, in which case it 
should be a branch and not a tag).
2. 'git push' should not move tags by default (not even in your local 
repo). Moving tags might be ok if given the -f option (still a remote 
repo should be able to object). With the current version of Git, the 
following commands will transform an annotated tag into a lightweight 
tag with no warning:

$ git config push.default tracking $ git checkout -b foo --track $annotated_tag $ git push

...Johan
-- 
Johan Herland, <johan@herland.net>
www.herland.net
Junio C Hamano· Feb 15, 2011, 20:04 UTC · re: Johan Herland · lore

Re: checkout/branch needs some extra safety around the --track option

Johan Herland <johan@herland.net> writes:
Show 6 quoted lines
> 1. When given "--track", branch/checkout should verify that the tracked 
> branch is indeed a branch (preferably a remote branch, although I guess 
> tracking a local branch can make sense in some situations). At least, 
> it should deny tracking a _tag_. Tracking a tag simply does not make 
> sense at all (unless you expect the tag to move, in which case it 
> should be a branch and not a tag).
Sensible, I think.
Johan Herland· Feb 16, 2011, 10:46 UTC · re: Junio C Hamano · lore

[PATCH] branch/checkout --track: Ensure that upstream branch is indeed a branch

When creating a new branch using the --track option, we must make sure that we don't try to set an upstream that does not make sense to follow (using 'git pull') or update (using 'git push'). The current code checks against using HEAD as upstream (since tracking a symref doesn't make sense). However, tracking a tag doesn't make sense either. Indeed, tracking _any_ ref that is not a (local or remote) branch doesn't make sense, and should be disallowed.

This patch achieves this by checking that the ref we're trying to --track resides within refs/heads/* or refs/remotes/*. This new check replaces the previous check against HEAD.

A couple of testcases are also added, verifying that we cannot create branches with tags as upstreams.

Finally, some selftests relying on using a non-branch as an upstream have been reworked or removed:

- t6040: Reverse the meaning of two tests that depend on the ability to
use (lightweight and annotated) tags as upstreams. These two tests were
originally added in commits 1be570f and 57ffc5f, and this patch reverts the
intention of those two commits.
- t7201: Remove part of a test (introduced in 9188ed8) relying on a
non-branch as upstream.
Signed-off-by: Johan Herland <johan@herland.net>
---
On Tuesday 15 February 2011, Junio C Hamano wrote:
Show 9 quoted lines
> Johan Herland <johan@herland.net> writes:
> > 1. When given "--track", branch/checkout should verify that the tracked
> > branch is indeed a branch (preferably a remote branch, although I guess
> > tracking a local branch can make sense in some situations). At least,
> > it should deny tracking a _tag_. Tracking a tag simply does not make
> > sense at all (unless you expect the tag to move, in which case it
> > should be a branch and not a tag).
> 
> Sensible, I think.
Make it so.
...Johan
 branch.c                 |    6 ++++--
 t/t3200-branch.sh        |    5 +++++
 t/t6040-tracking-info.sh |   16 ++++++++--------
 t/t7201-co.sh            |   16 +++++++++-------
 4 files changed, 26 insertions(+), 17 deletions(-)
diff --git a/branch.c b/branch.c
index 93dc866..f46a43a 100644
--- a/branch.c
+++ b/branch.c
@@ -175,8 +175,10 @@ void create_branch(const char *head,
 			die("Cannot setup tracking information; starting point is not a branch.");
 		break;
 	case 1:
-		/* Unique completion -- good, only if it is a real ref */
-		if (explicit_tracking && !strcmp(real_ref, "HEAD"))
+		/* Unique completion -- good, only if it is a real branch */
+		if (explicit_tracking &&
+		    prefixcmp(real_ref, "refs/heads/") &&
+		    prefixcmp(real_ref, "refs/remotes/"))
 			die("Cannot setup tracking information; starting point is not a branch.");
 		break;
 	default:
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index f308235..78da358 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -223,6 +223,11 @@ test_expect_success \
     'branch from non-branch HEAD w/--track causes failure' \
     'test_must_fail git branch --track my10 HEAD^'
 
+test_expect_success \
+    'branch from tag w/--track causes failure' \
+    'git tag foobar &&
+     test_must_fail git branch --track my11 foobar'
+
 # Keep this test last, as it changes the current branch
 cat >expect <<EOF
 0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 
1117150200 +0000	branch: Created from master
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 1e0447f..cb85132 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -74,20 +74,20 @@ test_expect_success 'status' '
 	grep "have 1 and 1 different" actual
 '
 
-test_expect_success 'status when tracking lightweight tags' '
+test_expect_success 'fail to track lightweight tags' '
 	git checkout master &&
 	git tag light &&
-	git branch --track lighttrack light >actual &&
-	grep "set up to track" actual &&
-	git checkout lighttrack
+	test_must_fail git branch --track lighttrack light >actual &&
+	test_must_fail grep "set up to track" actual &&
+	test_must_fail git checkout lighttrack
 '
 
-test_expect_success 'status when tracking annotated tags' '
+test_expect_success 'fail to track annotated tags' '
 	git checkout master &&
 	git tag -m heavy heavy &&
-	git branch --track heavytrack heavy >actual &&
-	grep "set up to track" actual &&
-	git checkout heavytrack
+	test_must_fail git branch --track heavytrack heavy >actual &&
+	test_must_fail grep "set up to track" actual &&
+	test_must_fail git checkout heavytrack
 '
 
 test_expect_success 'setup tracking with branch --set-upstream on existing branch' '
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 1337fa5..0c002ab 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -408,6 +408,15 @@ test_expect_success 'checkout w/--track from non-branch HEAD fails' '
     test "z$(git rev-parse master^0)" = "z$(git rev-parse HEAD)"
 '
 
+test_expect_success 'checkout w/--track from tag fails' '
+    git checkout master^0 &&
+    test_must_fail git symbolic-ref HEAD &&
+    test_must_fail git checkout --track -b track frotz &&
+    test_must_fail git rev-parse --verify track &&
+    test_must_fail git symbolic-ref HEAD &&
+    test "z$(git rev-parse master^0)" = "z$(git rev-parse HEAD)"
+'
+
 test_expect_success 'detach a symbolic link HEAD' '
     git checkout master &&
     git config --bool core.prefersymlinkrefs yes &&
@@ -423,7 +432,6 @@ test_expect_success 'detach a symbolic link HEAD' '
 test_expect_success \
     'checkout with --track fakes a sensible -b <name>' '
     git update-ref refs/remotes/origin/koala/bear renamer &&
-    git update-ref refs/new/koala/bear renamer &&
 
     git checkout --track origin/koala/bear &&
     test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
@@ -439,12 +447,6 @@ test_expect_success \
 
     git checkout --track remotes/origin/koala/bear &&
     test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
-    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" &&
-
-    git checkout master && git branch -D koala/bear &&
-
-    git checkout --track refs/new/koala/bear &&
-    test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
     test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)"
 '
 
-- 
1.7.4
Junio C Hamano· Feb 16, 2011, 22:00 UTC · re: Johan Herland · lore

Re: [PATCH] branch/checkout --track: Ensure that upstream branch is indeed a branch

Johan Herland <johan@herland.net> writes:
Show 30 quoted lines
> When creating a new branch using the --track option, we must make sure that
> we don't try to set an upstream that does not make sense to follow (using
> 'git pull') or update (using 'git push'). The current code checks against
> using HEAD as upstream (since tracking a symref doesn't make sense). However,
> tracking a tag doesn't make sense either. Indeed, tracking _any_ ref that is
> not a (local or remote) branch doesn't make sense, and should be disallowed.
>
> This patch achieves this by checking that the ref we're trying to --track
> resides within refs/heads/* or refs/remotes/*. This new check replaces the
> previous check against HEAD.
> ...
> Make it so.
>
> diff --git a/branch.c b/branch.c
> index 93dc866..f46a43a 100644
> --- a/branch.c
> +++ b/branch.c
> @@ -175,8 +175,10 @@ void create_branch(const char *head,
>  			die("Cannot setup tracking information; starting point is not a branch.");
>  		break;
>  	case 1:
> -		/* Unique completion -- good, only if it is a real ref */
> -		if (explicit_tracking && !strcmp(real_ref, "HEAD"))
> +		/* Unique completion -- good, only if it is a real branch */
> +		if (explicit_tracking &&
> +		    prefixcmp(real_ref, "refs/heads/") &&
> +		    prefixcmp(real_ref, "refs/remotes/"))
>  			die("Cannot setup tracking information; starting point is not a branch.");
>  		break;
>  	default:

Thomas's "HEAD" patch 84c1a89 (branch: do not attempt to track HEAD implicitly, 2010-12-14) has an extra action to reset real_ref to NULL when not asking for "explicit_tracking" in the same codepath.

<<<<<<< HEAD
		/* Unique completion -- good, only if it is a real ref */
		if (!strcmp(real_ref, "HEAD")) {
			if (explicit_tracking)
				die("Cannot setup tracking information; starting point is not a branch.");
			else
				real_ref = NULL;
		}
=======
		/* Unique completion -- good, only if it is a real branch */
		if (explicit_tracking &&
		    prefixcmp(real_ref, "refs/heads/") &&
		    prefixcmp(real_ref, "refs/remotes/"))
			die("Cannot setup tracking information; starting point is not a branch.");
>>>>>>> @{-1}
Don't we need something similar here?
Johan Herland· Feb 16, 2011, 23:12 UTC · re: Junio C Hamano · lore

[PATCH v2] branch/checkout --track: Ensure that upstream branch is indeed a branch

When creating a new branch using the --track option, we must make sure that we don't try to set an upstream that does not make sense to follow (using 'git pull') or update (using 'git push'). The current code checks against using HEAD as upstream (since tracking a symref doesn't make sense). However, tracking a tag doesn't make sense either. Indeed, tracking _any_ ref that is not a (local or remote) branch doesn't make sense, and should be disallowed.

This patch achieves this by checking that the ref we're trying to --track resides within refs/heads/* or refs/remotes/*. This new check replaces the previous check against HEAD.

A couple of testcases are also added, verifying that we cannot create branches with tags as upstreams.

Finally, some selftests relying on using a non-branch as an upstream have been reworked or removed:

- t6040: Reverse the meaning of two tests that depend on the ability to
use (lightweight and annotated) tags as upstreams. These two tests were
originally added in commits 1be570f and 57ffc5f, and this patch reverts the
intention of those two commits.
- t7201: Remove part of a test (introduced in 9188ed8) relying on a
non-branch as upstream.
Signed-off-by: Johan Herland <johan@herland.net>
---
On Wednesday 16 February 2011, Junio C Hamano wrote:
Show 7 quoted lines
> Thomas's "HEAD" patch 84c1a89 (branch: do not attempt to track HEAD
> implicitly, 2010-12-14) has an extra action to reset real_ref to NULL
> when not asking for "explicit_tracking" in the same codepath.
> 
> ...
> 
> Don't we need something similar here?
Indeed. Here is v2, rebased on top of 84c1a89.
...Johan
 branch.c                 |    5 +++--
 t/t3200-branch.sh        |    5 +++++
 t/t6040-tracking-info.sh |   16 ++++++++--------
 t/t7201-co.sh            |   16 +++++++++-------
 4 files changed, 25 insertions(+), 17 deletions(-)
diff --git a/branch.c b/branch.c
index 19310e4..da5c03e 100644
--- a/branch.c
+++ b/branch.c
@@ -175,8 +175,9 @@ void create_branch(const char *head,
 			die("Cannot setup tracking information; starting point is not a branch.");
 		break;
 	case 1:
-		/* Unique completion -- good, only if it is a real ref */
-		if (!strcmp(real_ref, "HEAD")) {
+		/* Unique completion -- good, only if it is a real branch */
+		if (prefixcmp(real_ref, "refs/heads/") &&
+		    prefixcmp(real_ref, "refs/remotes/")) {
 			if (explicit_tracking)
 				die("Cannot setup tracking information; starting point is not a branch.");
 			else
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index dce90de..55af032 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -212,6 +212,11 @@ test_expect_success \
     'branch from non-branch HEAD w/--track causes failure' \
     'test_must_fail git branch --track my10 HEAD^'
 
+test_expect_success \
+    'branch from tag w/--track causes failure' \
+    'git tag foobar &&
+     test_must_fail git branch --track my11 foobar'
+
 # Keep this test last, as it changes the current branch
 cat >expect <<EOF
 0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 
1117150200 +0000	branch: Created from master
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 1785e17..10bf3de 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -74,20 +74,20 @@ test_expect_success 'status' '
 	grep "have 1 and 1 different" actual
 '
 
-test_expect_success 'status when tracking lightweight tags' '
+test_expect_success 'fail to track lightweight tags' '
 	git checkout master &&
 	git tag light &&
-	git branch --track lighttrack light >actual &&
-	grep "set up to track" actual &&
-	git checkout lighttrack
+	test_must_fail git branch --track lighttrack light >actual &&
+	test_must_fail grep "set up to track" actual &&
+	test_must_fail git checkout lighttrack
 '
 
-test_expect_success 'status when tracking annotated tags' '
+test_expect_success 'fail to track annotated tags' '
 	git checkout master &&
 	git tag -m heavy heavy &&
-	git branch --track heavytrack heavy >actual &&
-	grep "set up to track" actual &&
-	git checkout heavytrack
+	test_must_fail git branch --track heavytrack heavy >actual &&
+	test_must_fail grep "set up to track" actual &&
+	test_must_fail git checkout heavytrack
 '
 
 test_expect_success 'setup tracking with branch --set-upstream on existing branch' '
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 1337fa5..0c002ab 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -408,6 +408,15 @@ test_expect_success 'checkout w/--track from non-branch HEAD fails' '
     test "z$(git rev-parse master^0)" = "z$(git rev-parse HEAD)"
 '
 
+test_expect_success 'checkout w/--track from tag fails' '
+    git checkout master^0 &&
+    test_must_fail git symbolic-ref HEAD &&
+    test_must_fail git checkout --track -b track frotz &&
+    test_must_fail git rev-parse --verify track &&
+    test_must_fail git symbolic-ref HEAD &&
+    test "z$(git rev-parse master^0)" = "z$(git rev-parse HEAD)"
+'
+
 test_expect_success 'detach a symbolic link HEAD' '
     git checkout master &&
     git config --bool core.prefersymlinkrefs yes &&
@@ -423,7 +432,6 @@ test_expect_success 'detach a symbolic link HEAD' '
 test_expect_success \
     'checkout with --track fakes a sensible -b <name>' '
     git update-ref refs/remotes/origin/koala/bear renamer &&
-    git update-ref refs/new/koala/bear renamer &&
 
     git checkout --track origin/koala/bear &&
     test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
@@ -439,12 +447,6 @@ test_expect_success \
 
     git checkout --track remotes/origin/koala/bear &&
     test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
-    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" &&
-
-    git checkout master && git branch -D koala/bear &&
-
-    git checkout --track refs/new/koala/bear &&
-    test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
     test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)"
 '
 
-- 
1.7.4
Martin von Zweigbergk· Feb 18, 2011, 00:45 UTC · re: Johan Herland · lore

Re: [PATCH v2] branch/checkout --track: Ensure that upstream branch is indeed a branch

On Thu, 17 Feb 2011, Johan Herland wrote:
Show 10 quoted lines
> When creating a new branch using the --track option, we must make sure that
> we don't try to set an upstream that does not make sense to follow (using
> 'git pull') or update (using 'git push'). The current code checks against
> using HEAD as upstream (since tracking a symref doesn't make sense). However,
> tracking a tag doesn't make sense either. Indeed, tracking _any_ ref that is
> not a (local or remote) branch doesn't make sense, and should be disallowed.
> 
> This patch achieves this by checking that the ref we're trying to --track
> resides within refs/heads/* or refs/remotes/*. This new check replaces the
> previous check against HEAD.

In some workflows (e.g. Linux kernel, IIRC), it is recommended to base your work on a tag. Is it worth considering that people might use a tag as upstream for such cases or would that be considered abuse of the "upstream" concept? It could make sense to set an upstream to point to a tag for reference and to be able to use e.g. 'rebase -i @{u}', 'git log @{u}..' and similar.

/Martin
Junio C Hamano· Feb 18, 2011, 04:32 UTC · re: Martin von Zweigbergk · lore

Re: [PATCH v2] branch/checkout --track: Ensure that upstream branch is indeed a branch

Martin von Zweigbergk <martin.von.zweigbergk@gmail.com> writes:
> In some workflows (e.g. Linux kernel, IIRC), it is recommended to base
> your work on a tag.

But the thing is, "base your work on some known point" is a rule invented exactly to discourage casual and frequent rebasing.

If you started your work on v2.6.36 and need to rebase to a more recent version, you won't be using "rebase @{upstream}" notation _anyway_, as the old base you forked your work from that you would refer to as @{upstream} will not be moving. You will instead rebasing on top of a _different_ commit, perhaps v2.6.38 or something.

So the workflow would look more like: 
	$ git checkout -b frotz-2.6.36 v2.6.36
        ... develop "frotz" feature on top of 2.6.36
        ... time passes ...
	$ git checkout -b frotz-2.6.38 frotz-2.6.36
        $ git rebase --onto v2.6.38 v2.6.36
	... inspect the result and it looks good.
        ... if  you don't want frotz in 2.6.36 then...
        $ git branch -D frotz-2.6.36

There is not much point in using @{upstream} when your branch management is as disciplined as that recommended practice you cited.

← back to recent threads