threads / patch / 13659

patch, 3 partsAdd some tests for git update-ref -d

Subject: [PATCH 1/3] Add some tests for git update-ref -d

## tl;dr

8 messages between May 25, 2008 and May 26, 2008. Diffs are folded; open one to read it.

replies: 7people: 3as markdown or json

Karl Hasselström· May 25, 2008, 16:14 UTC · lore

[PATCH 0/3] Make old sha1 optional with git update-ref -d

This series makes the old ref value optional for update-ref -d (it's already optional for update-ref without -d). But first it adds some test and refactors a bit.

---
Karl Hasselström (3):
      Make old sha1 optional with git update-ref -d
      Clean up builtin-update-ref's option parsing
      Add some tests for git update-ref -d
 Documentation/git-update-ref.txt |    2 +-
 builtin-update-ref.c             |   32 ++++++++++++++++++--------------
 t/t1400-update-ref.sh            |   24 ++++++++++++++++++++++++
 3 files changed, 43 insertions(+), 15 deletions(-)
-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle
Karl Hasselström· May 25, 2008, 16:14 UTC · re: Karl Hasselström · lore
Signed-off-by: Karl Hasselström <kha@treskal.com>
---
 t/t1400-update-ref.sh |   16 ++++++++++++++++
 1 files changed, 16 insertions(+), 0 deletions(-)
Show changes to t/t1400-update-ref.sh +16 −0
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index 78cd412..b88e767 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -32,6 +32,14 @@ test_expect_success \
 	"create $m" \
 	"git update-ref $m $B $A &&
 	 test $B"' = $(cat .git/'"$m"')'
+test_expect_success "fail to delete $m with stale ref" "
+	test_must_fail git update-ref -d $m $A &&
+	test $B = \$(cat .git/$m)
+"
+test_expect_success "delete $m" "
+	git update-ref -d $m $B &&
+	! test -f .git/$m
+"
 rm -f .git/$m
 
 test_expect_success \
@@ -49,6 +57,14 @@ test_expect_success \
 	"create $m (by HEAD)" \
 	"git update-ref HEAD $B $A &&
 	 test $B"' = $(cat .git/'"$m"')'
+test_expect_success "fail to delete $m (by HEAD) with stale ref" "
+	test_must_fail git update-ref -d HEAD $A &&
+	test $B = \$(cat .git/$m)
+"
+test_expect_success "delete $m (by HEAD)" "
+	git update-ref -d HEAD $B &&
+	! test -f .git/$m
+"
 rm -f .git/$m
 
 test_expect_success '(not) create HEAD with old sha1' "
Karl Hasselström· May 25, 2008, 16:14 UTC · re: Karl Hasselström · lore

[PATCH 2/3] Clean up builtin-update-ref's option parsing

builtin-update-ref's option parsing was somewhat tricky to follow, especially if the -d option was given. This patch cleans it upp a bit (including fixing an out-of-bounds argv access), at the expense of making it a bit longer.

Signed-off-by: Karl Hasselström <kha@treskal.com>
---
 builtin-update-ref.c |   30 +++++++++++++++++-------------
 1 files changed, 17 insertions(+), 13 deletions(-)
Show changes to builtin-update-ref.c +17 −13
diff --git a/builtin-update-ref.c b/builtin-update-ref.c
index e90737c..0d3eb8e 100644
--- a/builtin-update-ref.c
+++ b/builtin-update-ref.c
@@ -27,25 +27,29 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
 	if (msg && !*msg)
 		die("Refusing to perform update with empty message.");
 
-	if (argc < 2 || argc > 3)
-		usage_with_options(git_update_ref_usage, options);
-	refname = argv[0];
-	value   = argv[1];
-	oldval  = argv[2];
-
-	if (get_sha1(value, sha1))
-		die("%s: not a valid SHA1", value);
-
 	if (delete) {
-		if (oldval)
+		if (argc != 2)
 			usage_with_options(git_update_ref_usage, options);
-		return delete_ref(refname, sha1);
+		refname = argv[0];
+		value = NULL;
+		oldval = argv[1];
+	} else {
+		if (argc < 2 || argc > 3)
+			usage_with_options(git_update_ref_usage, options);
+		refname = argv[0];
+		value = argv[1];
+		oldval = argc > 2 ? argv[2] : NULL;
 	}
 
+	if (value && *value && get_sha1(value, sha1))
+		die("%s: not a valid SHA1", value);
 	hashclr(oldsha1);
 	if (oldval && *oldval && get_sha1(oldval, oldsha1))
 		die("%s: not a valid old SHA1", oldval);
 
-	return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
-			  no_deref ? REF_NODEREF : 0, DIE_ON_ERR);
+	if (delete)
+		return delete_ref(refname, oldsha1);
+	else
+		return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
+				  no_deref ? REF_NODEREF : 0, DIE_ON_ERR);
 }
Junio C Hamano· May 25, 2008, 18:40 UTC · re: Karl Hasselström · lore

Re: [PATCH 2/3] Clean up builtin-update-ref's option parsing

Karl Hasselström <kha@treskal.com> writes:
Show 6 quoted lines
> builtin-update-ref's option parsing was somewhat tricky to follow,
> especially if the -d option was given. This patch cleans it upp a bit
> (including fixing an out-of-bounds argv access), at the expense of
> making it a bit longer.
>
> Signed-off-by: Karl Hasselström <kha@treskal.com>

Longer is fine but I am afraid that this patch is not much easier to follow than the original, does not fix anything, and introduces a new bug.

Show 31 quoted lines
> diff --git a/builtin-update-ref.c b/builtin-update-ref.c
> index e90737c..0d3eb8e 100644
> --- a/builtin-update-ref.c
> +++ b/builtin-update-ref.c
> @@ -27,25 +27,29 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
>  	if (msg && !*msg)
>  		die("Refusing to perform update with empty message.");
>  
> -	if (argc < 2 || argc > 3)
> -		usage_with_options(git_update_ref_usage, options);
> -	refname = argv[0];
> -	value   = argv[1];
> -	oldval  = argv[2];
> -
> -	if (get_sha1(value, sha1))
> -		die("%s: not a valid SHA1", value);
> -
>  	if (delete) {
> -		if (oldval)
> +		if (argc != 2)
>  			usage_with_options(git_update_ref_usage, options);
> -		return delete_ref(refname, sha1);
> +		refname = argv[0];
> +		value = NULL;
> +		oldval = argv[1];
> +	} else {
> +		if (argc < 2 || argc > 3)
> +			usage_with_options(git_update_ref_usage, options);
> +		refname = argv[0];
> +		value = argv[1];
> +		oldval = argc > 2 ? argv[2] : NULL;

When (argc == 3), argv[2] has the old value string given on the command line. When (argc == 2), argv[2] has the terminating NULL pointer. So either case you can safely use argv[2]. You do not allow other cases upfront, so I do not understand why you need this conditional expression?

IOW, I do not see "an out-of-bounds argv access" in the original, and you are making this assignment harder to follow.

>  	}
>  
> +	if (value && *value && get_sha1(value, sha1))
> +		die("%s: not a valid SHA1", value);
Dropping *value in the sequence may fix it but I think this is wrong.

We used to barf if you said "git update-ref refs/heads/master '' master" because it would be nonsense to give an empty string as the new value of the ref, didn't we? Doesn't this change break it? Does your set of additional tests in [1/3] catch it?

Show 12 quoted lines
>  	hashclr(oldsha1);
>  	if (oldval && *oldval && get_sha1(oldval, oldsha1))
>  		die("%s: not a valid old SHA1", oldval);
>  
> -	return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
> -			  no_deref ? REF_NODEREF : 0, DIE_ON_ERR);
> +	if (delete)
> +		return delete_ref(refname, oldsha1);
> +	else
> +		return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
> +				  no_deref ? REF_NODEREF : 0, DIE_ON_ERR);
>  }
Karl Hasselström· May 25, 2008, 19:32 UTC · re: Junio C Hamano · lore

Re: [PATCH 2/3] Clean up builtin-update-ref's option parsing

On 2008-05-25 11:40:24 -0700, Junio C Hamano wrote:
Show 23 quoted lines
> >  	if (delete) {
> > -		if (oldval)
> > +		if (argc != 2)
> >  			usage_with_options(git_update_ref_usage, options);
> > -		return delete_ref(refname, sha1);
> > +		refname = argv[0];
> > +		value = NULL;
> > +		oldval = argv[1];
> > +	} else {
> > +		if (argc < 2 || argc > 3)
> > +			usage_with_options(git_update_ref_usage, options);
> > +		refname = argv[0];
> > +		value = argv[1];
> > +		oldval = argc > 2 ? argv[2] : NULL;
>
> When (argc == 3), argv[2] has the old value string given on the
> command line. When (argc == 2), argv[2] has the terminating NULL
> pointer. So either case you can safely use argv[2]. You do not allow
> other cases upfront, so I do not understand why you need this
> conditional expression?
>
> IOW, I do not see "an out-of-bounds argv access" in the original,
> and you are making this assignment harder to follow.

Mmmph. The problem here was that I didn't know that argv is NULL-terminated. I just assumed it was bad to access anything beyond the given array length as is usually the case in C. But I really should have taken a hint from the existing code. Sorry for wasting your time with this.

Show 11 quoted lines
> >  	}
> >  
> > +	if (value && *value && get_sha1(value, sha1))
> > +		die("%s: not a valid SHA1", value);
> 
> Dropping *value in the sequence may fix it but I think this is wrong.
> 
> We used to barf if you said "git update-ref refs/heads/master '' master"
> because it would be nonsense to give an empty string as the new value of
> the ref, didn't we?  Doesn't this change break it?  Does your set of
> additional tests in [1/3] catch it?

Aaah, I see what you mean. Good point. The empty string should obviously not be allowed as the new ref. In the original code it _is_ allowed as the _old_ value, and interpreted as 0{40}.

I'll update the series. With, among other things, a comment that explains this.

                                 -+-
Many thanks for the review. I'll be back.
-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle
Karl Hasselström· May 25, 2008, 16:14 UTC · re: Karl Hasselström · lore

[PATCH 3/3] Make old sha1 optional with git update-ref -d

Giving the old sha1 is already optional when changing a ref, and it's quite handy when running update-ref manually. So make it optional for deleting a ref too.

Signed-off-by: Karl Hasselström <kha@treskal.com>
---
 Documentation/git-update-ref.txt |    2 +-
 builtin-update-ref.c             |    8 ++++----
 t/t1400-update-ref.sh            |    8 ++++++++
 3 files changed, 13 insertions(+), 5 deletions(-)
Show changes to 3 files +13 −5

Documentation/git-update-ref.txt, builtin-update-ref.c, t/t1400-update-ref.sh

diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index 4dc4759..80b94c3 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -7,7 +7,7 @@ git-update-ref - Update the object name stored in a ref safely
 
 SYNOPSIS
 --------
-'git-update-ref' [-m <reason>] (-d <ref> <oldvalue> | [--no-deref] <ref> <newvalue> [<oldvalue>])
+'git-update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>])
 
 DESCRIPTION
 -----------
diff --git a/builtin-update-ref.c b/builtin-update-ref.c
index 0d3eb8e..054e64d 100644
--- a/builtin-update-ref.c
+++ b/builtin-update-ref.c
@@ -4,7 +4,7 @@
 #include "parse-options.h"
 
 static const char * const git_update_ref_usage[] = {
-	"git-update-ref [options] -d <refname> <oldval>",
+	"git-update-ref [options] -d <refname> [<oldval>]",
 	"git-update-ref [options]    <refname> <newval> [<oldval>]",
 	NULL
 };
@@ -28,11 +28,11 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
 		die("Refusing to perform update with empty message.");
 
 	if (delete) {
-		if (argc != 2)
+		if (argc < 1 || argc > 2)
 			usage_with_options(git_update_ref_usage, options);
 		refname = argv[0];
 		value = NULL;
-		oldval = argv[1];
+		oldval = argc > 1 ? argv[1] : NULL;
 	} else {
 		if (argc < 2 || argc > 3)
 			usage_with_options(git_update_ref_usage, options);
@@ -48,7 +48,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
 		die("%s: not a valid old SHA1", oldval);
 
 	if (delete)
-		return delete_ref(refname, oldsha1);
+		return delete_ref(refname, oldval ? oldsha1 : NULL);
 	else
 		return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
 				  no_deref ? REF_NODEREF : 0, DIE_ON_ERR);
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index b88e767..da82645 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -42,6 +42,14 @@ test_expect_success "delete $m" "
 "
 rm -f .git/$m
 
+test_expect_success "delete $m without oldvalue verification" "
+	git update-ref $m $A &&
+	test $A = \$(cat .git/$m) &&
+	git update-ref -d $m &&
+	! test -f .git/$m
+"
+rm -f .git/$m
+
 test_expect_success \
 	"fail to create $n" \
 	"touch .git/$n_dir
Johannes Schindelin· May 26, 2008, 10:51 UTC · re: Karl Hasselström · lore

Re: [PATCH 3/3] Make old sha1 optional with git update-ref -d

Hi,
On Sun, 25 May 2008, Karl Hasselström wrote:
> Giving the old sha1 is already optional when changing a ref, and it's
> quite handy when running update-ref manually. So make it optional for
> deleting a ref too.

Isn't this a bit dangerous, especially given that there is potentially _no_ reflog as a safeguard?

So no, it is not the same as when changing a ref.

I am mildly negative on this (having lost some remote branches, because they were deleted _together with the reflogs_ by "git push bla :x"), Dscho

Karl Hasselström· May 26, 2008, 14:09 UTC · re: Johannes Schindelin · lore

Re: [PATCH 3/3] Make old sha1 optional with git update-ref -d

On 2008-05-26 11:51:12 +0100, Johannes Schindelin wrote:
Show 14 quoted lines
> On Sun, 25 May 2008, Karl Hasselström wrote:
>
> > Giving the old sha1 is already optional when changing a ref, and
> > it's quite handy when running update-ref manually. So make it
> > optional for deleting a ref too.
>
> Isn't this a bit dangerous, especially given that there is
> potentially _no_ reflog as a safeguard?
>
> So no, it is not the same as when changing a ref.
>
> I am mildly negative on this (having lost some remote branches,
> because they were deleted _together with the reflogs_ by "git push
> bla :x"),

Deleting the wrong thing is never a fun experience. The thing is, this patch doesn't really make it any easier to delete the wrong thing. Before, you'd

  git update-ref -d <ref> <some-other-way-to-point-to-the-same-commit>
if you wanted to play it safe, and
  git update-ref -d <ref> <ref>

if you thought you knew what you were doing. All this patch does is make the second usage mode easier to type.

(One might argue that a user who doesn't know what she's doing will have an easier time shooting herself in the foot when the oldvalue is optional, but I was under the impression that ease of use without consulting the man page shouldn't be a factor when designing the plumbing UI.)

I do agree with you that branch deletion is one of the more "dangerous" operations in git. But I don't think making update-ref hard to use is the answer there; what we'd really need is for old reflogs to stay around for a while before being deleted.

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

← back to recent threads