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

[PATCH] refspec: allow @ on the left-hand side of refspecs

From
brian m. carlson <sandals@crustytoothpaste.net>
Date
Jul 29, 2018, 19:28 UTC
Message-ID
<20180729192803.1047050-1-sandals@crustytoothpaste.net>

The object ID parsing machinery is aware of "@" as a synonym for "HEAD" and this is documented accordingly in gitrevisions(7). The push documentation describes the source portion of a refspec as "any arbitrary 'SHA-1 expression'"; however, "@" is not allowed on the left-hand side of a refspec, since we attempt to check for it being a valid ref name and fail (since it is not).

Teach the refspec machinery about this alias and silently substitute "HEAD" when we see "@". This handles the fact that HEAD is a symref and preserves its special behavior. We need not handle other arbitrary object ID expressions (such as "@^") when pushing because the revision machinery already handles that for us.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
---
I probably type "git push upstream HEAD" from five to thirty times a
day, many of those where I typo "HEAD", so I decided to implement the
shorter form.  This design handles @ as HEAD in both fetch and push,
whereas alternate solutions would not.

check_refname_format explicitly rejects "@"; I tried at first to simply ignore that with a flag, but we end up calling that from several other places in the codebase and rejecting it and all of those places would have needed updating.

I thought about putting the if/else logic in a function, but since it's just four lines, I decided not to. However, if people think it would be tidier, I can do so.

Note that the test portion of the patch is best read with git diff -w; the current version is very noisy.

 refspec.c             |   6 ++-
 t/t5516-fetch-push.sh | 104 +++++++++++++++++++++---------------------
 2 files changed, 58 insertions(+), 52 deletions(-)
diff --git a/refspec.c b/refspec.c
index e8010dce0c..57c2f65104 100644
--- a/refspec.c
+++ b/refspec.c
@@ -62,8 +62,12 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
 		return 0;
 	}
 
+	if (llen == 1 && lhs[0] == '@')
+		item->src = xstrdup("HEAD");
+	else
+		item->src = xstrndup(lhs, llen);
+
 	item->pattern = is_glob;
-	item->src = xstrndup(lhs, llen);
 	flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0);
 
 	if (fetch) {
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index a5077d8b7c..cbccbd2f8d 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -436,70 +436,72 @@ test_expect_success 'push ref expression with non-existent, incomplete dest' '
 
 '
 
-test_expect_success 'push with HEAD' '
+for ref in HEAD @
+do
+	test_expect_success "push with $ref" '
 
-	mk_test testrepo heads/master &&
-	git checkout master &&
-	git push testrepo HEAD &&
-	check_push_result testrepo $the_commit heads/master
+		mk_test testrepo heads/master &&
+		git checkout master &&
+		git push testrepo $ref &&
+		check_push_result testrepo $the_commit heads/master
 
-'
+	'
 
-test_expect_success 'push with HEAD nonexisting at remote' '
+	test_expect_success "push with $ref nonexisting at remote" '
 
-	mk_test testrepo heads/master &&
-	git checkout -b local master &&
-	git push testrepo HEAD &&
-	check_push_result testrepo $the_commit heads/local
-'
+		mk_test testrepo heads/master &&
+		git checkout -B local master &&
+		git push testrepo $ref &&
+		check_push_result testrepo $the_commit heads/local
+	'
 
-test_expect_success 'push with +HEAD' '
+	test_expect_success "push with +$ref" '
 
-	mk_test testrepo heads/master &&
-	git checkout master &&
-	git branch -D local &&
-	git checkout -b local &&
-	git push testrepo master local &&
-	check_push_result testrepo $the_commit heads/master &&
-	check_push_result testrepo $the_commit heads/local &&
+		mk_test testrepo heads/master &&
+		git checkout master &&
+		git branch -D local &&
+		git checkout -b local &&
+		git push testrepo master local &&
+		check_push_result testrepo $the_commit heads/master &&
+		check_push_result testrepo $the_commit heads/local &&
 
-	# Without force rewinding should fail
-	git reset --hard HEAD^ &&
-	test_must_fail git push testrepo HEAD &&
-	check_push_result testrepo $the_commit heads/local &&
+		# Without force rewinding should fail
+		git reset --hard HEAD^ &&
+		test_must_fail git push testrepo $ref &&
+		check_push_result testrepo $the_commit heads/local &&
 
-	# With force rewinding should succeed
-	git push testrepo +HEAD &&
-	check_push_result testrepo $the_first_commit heads/local
+		# With force rewinding should succeed
+		git push testrepo +$ref &&
+		check_push_result testrepo $the_first_commit heads/local
 
-'
+	'
 
-test_expect_success 'push HEAD with non-existent, incomplete dest' '
+	test_expect_success "push $ref with non-existent, incomplete dest" '
 
-	mk_test testrepo &&
-	git checkout master &&
-	git push testrepo HEAD:branch &&
-	check_push_result testrepo $the_commit heads/branch
+		mk_test testrepo &&
+		git checkout master &&
+		git push testrepo $ref:branch &&
+		check_push_result testrepo $the_commit heads/branch
 
-'
+	'
 
-test_expect_success 'push with config remote.*.push = HEAD' '
-
-	mk_test testrepo heads/local &&
-	git checkout master &&
-	git branch -f local $the_commit &&
-	(
-		cd testrepo &&
-		git checkout local &&
-		git reset --hard $the_first_commit
-	) &&
-	test_config remote.there.url testrepo &&
-	test_config remote.there.push HEAD &&
-	test_config branch.master.remote there &&
-	git push &&
-	check_push_result testrepo $the_commit heads/master &&
-	check_push_result testrepo $the_first_commit heads/local
-'
+	test_expect_success "push with config remote.*.push = $ref" '
+		mk_test testrepo heads/local &&
+		git checkout master &&
+		git branch -f local $the_commit &&
+		(
+			cd testrepo &&
+			git checkout local &&
+			git reset --hard $the_first_commit
+		) &&
+		test_config remote.there.url testrepo &&
+		test_config remote.there.push $ref &&
+		test_config branch.master.remote there &&
+		git push &&
+		check_push_result testrepo $the_commit heads/master &&
+		check_push_result testrepo $the_first_commit heads/local
+	'
+done
 
 test_expect_success 'push with remote.pushdefault' '
 	mk_test up_repo heads/master &&
Next: Brandon Williams
Message 1 of 4 in “refspec: allow @ on the left-hand side of refspecs”
  1. refspec: allow @ on the left-hand side of refspecsbrian m. carlson, Jul 29, 2018
  2. Brandon WilliamsJul 30, 2018
  3. brian m. carlsonJul 30, 2018
  4. Brandon WilliamsJul 31, 2018

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.