From: Jeff King <hidden> Date: 2016-06-15 22:56:24
These patches fix the issue with peel-ref noticed recently by Michael
(namely that we fail to correctly peel packed refs outside of
refs/tags). The problem has been there since we added peeling support
to pack-refs, but traditionally "show-ref -d" was the only caller that
actually triggered the issue. Between that and the fact that most people
only put annotated tags into refs/tags, nobody really noticed.
Since my 435c833 (upload-pack: use peel_ref for ref advertisements,
2012-10-04), which is in v1.8.1, upload-pack can trigger the problem,
too, but I haven't actually heard of any reports in the wild.
I split it into two patches; the first one is the minimal fix that makes
git work properly going forward. The second one helps git be more robust
when reading packed-refs files generated by older git (or other
implementations). The second one depends semantically but not textually
on the first one; if you're worried about that, they can be squashed.
[1/2]: pack-refs: write peeled entry for non-tags
[2/2]: pack-refs: add fully-peeled trait
I think Michael may be rewriting some of this code, but if we are going
to go this direction with the fix (and I think we should), this is the
change that should go to maint in the meantime.
-Peff
From: Jeff King <hidden> Date: 2016-06-15 22:56:24
When we pack an annotated tag ref, we write not only the
sha1 of the tag object along with the ref, but also the sha1
obtained by peeling the tag. This lets readers of the
pack-refs file know the peeled value without having to
actually load the object, speeding up upload-pack's ref
advertisement.
The writer marks a packed-refs file with peeled refs using
the "peeled" trait at the top of the file. When the reader
sees this trait, it knows that each ref is either followed
by its peeled value, or it is not an annotated tag.
However, there is a mismatch between the assumptions of the
reader and writer. The writer will only peel refs under
refs/tags, but the reader does not know this; it will assume
a ref without a peeled value must not be a tag object. Thus
an annotated tag object placed outside of the refs/tags
hierarchy will not have its peeled value printed by
upload-pack.
The simplest way to fix this is to start writing peel values
for all refs. This matches what the reader expects for both
new and old versions of git.
Signed-off-by: Jeff King <redacted>
---
pack-refs.c | 16 ++++++++--------
t/t3211-peel-ref.sh | 42 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 50 insertions(+), 8 deletions(-)
create mode 100755 t/t3211-peel-ref.sh
@@ -27,6 +27,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1,intflags,void*cb_data){structpack_refs_cb_data*cb=cb_data;+structobject*o;intis_tag_ref;/* Do not pack the symbolic refs */
@@ -0,0 +1,42 @@+#!/bin/sh++test_description='tests for the peel_ref optimization of packed-refs'+../test-lib.sh++test_expect_success'create annotated tag in refs/tags''+test_commitbase&&+gittag-mannotatedfoo+'++test_expect_success'create annotated tag outside of refs/tags''+gitupdate-refrefs/outside/foorefs/tags/foo+'++# This matches show-ref's output+print_ref(){+echo"`git rev-parse "$1"` $1"+}++test_expect_success'set up expected show-ref output''+{+print_ref"refs/heads/master"&&+print_ref"refs/outside/foo"&&+print_ref"refs/outside/foo^{}"&&+print_ref"refs/tags/base"&&+print_ref"refs/tags/foo"&&+print_ref"refs/tags/foo^{}"+}>expect+'++test_expect_success'refs are peeled outside of refs/tags (loose)''+gitshow-ref-d>actual&&+test_cmpexpectactual+'++test_expect_success'refs are peeled outside of refs/tags (packed)''+gitpack-refs--all&&+gitshow-ref-d>actual&&+test_cmpexpectactual+'++test_done
From: Jeff King <hidden> Date: 2016-06-15 22:56:24
Older versions of pack-refs did not write peel lines for
refs outside of refs/tags. This meant that on reading the
pack-refs file, we might set the REF_KNOWS_PEELED flag for
such a ref, even though we do not know anything about its
peeled value.
The previous commit updated the writer to always peel, no
matter what the ref is. That means that packed-refs files
written by newer versions of git are fine to be read by both
old and new versions of git. However, we still have the
problem of reading packed-refs files written by older
versions of git, or by other implementations which have not
yet learned the same trick.
The simplest fix would be to always unset the
REF_KNOWS_PEELED flag for refs outside of refs/tags that do
not have a peel line (if it has a peel line, we know it is
valid, but we cannot assume a missing peel line means
anything). But that loses an important optimization, as
upload-pack should not need to load the object pointed to by
refs/heads/foo to determine that it is not a tag.
Instead, we add a "fully-peeled" trait to the packed-refs
file. If it is set, we know that we can trust a missing peel
line to mean that a ref cannot be peeled. Otherwise, we fall
back to assuming nothing.
Signed-off-by: Jeff King <redacted>
---
pack-refs.c | 2 +-
refs.c | 16 +++++++++++++++-
t/t3211-peel-ref.sh | 22 ++++++++++++++++++++++
3 files changed, 38 insertions(+), 2 deletions(-)
@@ -128,7 +128,7 @@ int pack_refs(unsigned int flags)die_errno("unable to create ref-pack file structure");/* perhaps other traits later as well */-fprintf(cbdata.refs_file,"# pack-refs with: peeled \n");+fprintf(cbdata.refs_file,"# pack-refs with: peeled fully-peeled \n");for_each_ref(handle_one_ref,&cbdata);if(ferror(cbdata.refs_file))
@@ -39,4 +39,26 @@ test_expect_success 'refs are peeled outside of refs/tags (packed)' 'test_cmpexpectactual'+test_expect_success'create old-style pack-refs without fully-peeled''+# Git no longer writes without fully-peeled, so we just write our own+# from scratch; we could also munge the existing file to remove the+# fully-peeled bits, but that seems even more prone to failure,+# especially if the format ever changes again. At least this way we+# know we are emulating exactly what an older git would have written.+{+echo"# pack-refs with: peeled "&&+print_ref"refs/heads/master"&&+print_ref"refs/outside/foo"&&+print_ref"refs/tags/base"&&+print_ref"refs/tags/foo"&&+echo"^$(gitrev-parse"refs/tags/foo^{}")"+}>tmp&&+mvtmp.git/packed-refs+'++test_expect_success'refs are peeled outside of refs/tags (old packed)''+gitshow-ref-d>actual&&+test_cmpexpectactual+'+ test_done
From: Michael Haggerty <hidden> Date: 2016-06-15 22:56:25
Looks good aside from a couple of minor points mentioned below.
On 03/16/2013 10:01 AM, Jeff King wrote:
When we pack an annotated tag ref, we write not only the
sha1 of the tag object along with the ref, but also the sha1
obtained by peeling the tag. This lets readers of the
pack-refs file know the peeled value without having to
actually load the object, speeding up upload-pack's ref
advertisement.
The writer marks a packed-refs file with peeled refs using
the "peeled" trait at the top of the file. When the reader
sees this trait, it knows that each ref is either followed
by its peeled value, or it is not an annotated tag.
However, there is a mismatch between the assumptions of the
reader and writer. The writer will only peel refs under
refs/tags, but the reader does not know this; it will assume
a ref without a peeled value must not be a tag object. Thus
an annotated tag object placed outside of the refs/tags
hierarchy will not have its peeled value printed by
upload-pack.
The simplest way to fix this is to start writing peel values
for all refs. This matches what the reader expects for both
new and old versions of git.
Signed-off-by: Jeff King <redacted>
@@ -27,6 +27,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1,intflags,void*cb_data){structpack_refs_cb_data*cb=cb_data;+structobject*o;intis_tag_ref;/* Do not pack the symbolic refs */
You suggested that I add a test (o != NULL) at the equivalent place in
my code (which was derived from this code). Granted, my code was
explicitly intending to pass invalid SHA1 values to parse_object(). But
wouldn't it be a good defensive step to add the same check here?
quoted hunk
+ o = deref_tag(o, path, 0);
+ if (o)
+ fprintf(cb->refs_file, "^%s\n",
+ sha1_to_hex(o->sha1));
}
if ((cb->flags & PACK_REFS_PRUNE) && !do_not_prune(flags)) {
@@ -0,0 +1,42 @@+#!/bin/sh++test_description='tests for the peel_ref optimization of packed-refs'+../test-lib.sh++test_expect_success'create annotated tag in refs/tags''+test_commitbase&&+gittag-mannotatedfoo+'++test_expect_success'create annotated tag outside of refs/tags''+gitupdate-refrefs/outside/foorefs/tags/foo+'++# This matches show-ref's output+print_ref(){+echo"`git rev-parse "$1"` $1"+}+
From: Michael Haggerty <hidden> Date: 2016-06-15 22:56:25
ACK, with one ignorable comment.
Michael
On 03/16/2013 10:01 AM, Jeff King wrote:
Older versions of pack-refs did not write peel lines for
refs outside of refs/tags. This meant that on reading the
pack-refs file, we might set the REF_KNOWS_PEELED flag for
such a ref, even though we do not know anything about its
peeled value.
The previous commit updated the writer to always peel, no
matter what the ref is. That means that packed-refs files
written by newer versions of git are fine to be read by both
old and new versions of git. However, we still have the
problem of reading packed-refs files written by older
versions of git, or by other implementations which have not
yet learned the same trick.
The simplest fix would be to always unset the
REF_KNOWS_PEELED flag for refs outside of refs/tags that do
not have a peel line (if it has a peel line, we know it is
valid, but we cannot assume a missing peel line means
anything). But that loses an important optimization, as
upload-pack should not need to load the object pointed to by
refs/heads/foo to determine that it is not a tag.
Instead, we add a "fully-peeled" trait to the packed-refs
file. If it is set, we know that we can trust a missing peel
line to mean that a ref cannot be peeled. Otherwise, we fall
back to assuming nothing.
@@ -128,7 +128,7 @@ int pack_refs(unsigned int flags)die_errno("unable to create ref-pack file structure");/* perhaps other traits later as well */-fprintf(cbdata.refs_file,"# pack-refs with: peeled \n");+fprintf(cbdata.refs_file,"# pack-refs with: peeled fully-peeled \n");for_each_ref(handle_one_ref,&cbdata);if(ferror(cbdata.refs_file))
@@ -818,13 +819,26 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)constchar*traits=refline+sizeof(header)-1;if(strstr(traits," peeled "))flag|=REF_KNOWS_PEELED;+if(strstr(traits," fully-peeled "))+fully_peeled=1;/* perhaps other traits later as well */continue;}refname=parse_ref_line(refline,sha1);if(refname){-last=create_ref_entry(refname,sha1,flag,1);+/*+*Oldergitdidnotwritepeellinesforanything+*outsideofrefs/tags/;ifthefully-peeledtrait+*isnotset,wearedealingwithsuchanolder+*gitandcannotassumeanomittedpeelvalue+*meanstherefisnotatagobject.+*/+intthis_flag=flag;+if(!fully_peeled&&prefixcmp(refname,"refs/tags/"))+this_flag&=~REF_KNOWS_PEELED;++last=create_ref_entry(refname,sha1,this_flag,1);add_ref(dir,last);continue;}
I have to admit that I am partial to my variant of this code [1] because
the logic makes it clearer when the affirmative decision can be made to
set the REF_KNOWS_PEELED flag. But this version also looks correct to
me and equivalent (aside from the idea that a few lines later if a
peeled value is found then the REF_KNOWS_PEELED bit could also be set).
@@ -39,4 +39,26 @@ test_expect_success 'refs are peeled outside of refs/tags (packed)' 'test_cmpexpectactual'+test_expect_success'create old-style pack-refs without fully-peeled''+# Git no longer writes without fully-peeled, so we just write our own+# from scratch; we could also munge the existing file to remove the+# fully-peeled bits, but that seems even more prone to failure,+# especially if the format ever changes again. At least this way we+# know we are emulating exactly what an older git would have written.+{+echo"# pack-refs with: peeled "&&+print_ref"refs/heads/master"&&+print_ref"refs/outside/foo"&&+print_ref"refs/tags/base"&&+print_ref"refs/tags/foo"&&+echo"^$(gitrev-parse"refs/tags/foo^{}")"+}>tmp&&+mvtmp.git/packed-refs+'++test_expect_success'refs are peeled outside of refs/tags (old packed)''+gitshow-ref-d>actual&&+test_cmpexpectactual+'+ test_done
From: Jeff King <hidden> Date: 2016-06-15 22:56:25
On Sat, Mar 16, 2013 at 02:50:56PM +0100, Michael Haggerty wrote:
quoted
@@ -39,14 +40,13 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, return 0; fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);- if (is_tag_ref) {- struct object *o = parse_object(sha1);- if (o->type == OBJ_TAG) {- o = deref_tag(o, path, 0);- if (o)- fprintf(cb->refs_file, "^%s\n",- sha1_to_hex(o->sha1));- }++ o = parse_object(sha1);+ if (o->type == OBJ_TAG) {
You suggested that I add a test (o != NULL) at the equivalent place in
my code (which was derived from this code). Granted, my code was
explicitly intending to pass invalid SHA1 values to parse_object(). But
wouldn't it be a good defensive step to add the same check here?
Hmm, yeah. That is not new code, but rather just reindented from above
("diff -w" makes it much more obvious what is going on).
It is probably worth dying rather than segfaulting, though it should be
a separate patch (and I do not think it is sane to do anything except
die here). I almost wonder if parse_object should die by default on
bogus or missing objects, and the few callers who really want to handle
the error can call parse_object_gently. I do not relish analyzing each
caller, though. It would be simpler to add parse_object_or_die.
From: Jeff King <hidden> Date: 2016-06-15 22:56:25
On Sat, Mar 16, 2013 at 03:06:22PM +0100, Michael Haggerty wrote:
quoted
refname = parse_ref_line(refline, sha1);
if (refname) {
- last = create_ref_entry(refname, sha1, flag, 1);
+ /*
+ * Older git did not write peel lines for anything
+ * outside of refs/tags/; if the fully-peeled trait
+ * is not set, we are dealing with such an older
+ * git and cannot assume an omitted peel value
+ * means the ref is not a tag object.
+ */
+ int this_flag = flag;
+ if (!fully_peeled && prefixcmp(refname, "refs/tags/"))
+ this_flag &= ~REF_KNOWS_PEELED;
+
+ last = create_ref_entry(refname, sha1, this_flag, 1);
add_ref(dir, last);
continue;
}
I have to admit that I am partial to my variant of this code [1] because
the logic makes it clearer when the affirmative decision can be made to
set the REF_KNOWS_PEELED flag. But this version also looks correct to
me and equivalent (aside from the idea that a few lines later if a
peeled value is found then the REF_KNOWS_PEELED bit could also be set).
Yeah, I think they are equivalent, but I agree yours is a little more
readable. I'll switch it in my re-roll, and I will go ahead and set the
REF_KNOWS_PEELED bit when we see a peel line. That code should not be
triggered in general, but it is the sane thing for the reader to do, so
it makes the code more obvious and readable.
-Peff
From: Jeff King <hidden> Date: 2016-06-15 22:56:25
Here's a re-roll that takes into account the feedback from round 1:
[1/4]: avoid segfaults on parse_object failure
[2/4]: use parse_object_or_die instead of die("bad object")
These two patches are new; they are conceptually independent of the rest
of the series, but there's a textual dependency in later patches.
[3/4]: pack-refs: write peeled entry for non-tags
Same as before, but rebased on patch 1, and s/``/$()/.
[4/4]: pack-refs: add fully-peeled trait
Rewritten using Michael's approach, which is more readable.
-Peff
From: Jeff King <hidden> Date: 2016-06-15 22:56:25
Many call-sites of parse_object assume that they will get a
non-NULL return value; this is not the case if we encounter
an error while parsing the object.
This patch adds a wrapper function around parse_object that
handles dying automatically, and uses it anywhere we
immediately try to access the return value as a non-NULL
pointer (i.e., anywhere that we would currently segfault).
This wrapper may also be useful in other places. The most
obvious one is code like:
o = parse_object(sha1);
if (!o)
die(...);
However, these should not be mechanically converted to
parse_object_or_die, as the die message is sometimes
customized. Later patches can address these sites on a
case-by-case basis.
Signed-off-by: Jeff King <redacted>
---
bundle.c | 6 +++---
object.c | 10 ++++++++++
object.h | 13 ++++++++++++-
pack-refs.c | 2 +-
4 files changed, 26 insertions(+), 5 deletions(-)
@@ -54,9 +54,20 @@ struct object *parse_object(const unsigned char *sha1);externvoid*create_object(constunsignedchar*sha1,inttype,void*obj);-/** Returns the object, having parsed it to find out what it is. **/+/*+*Returnstheobject,havingparsedittofindoutwhatitis.+*+*ReturnsNULLiftheobjectismissingorcorrupt.+*/structobject*parse_object(constunsignedchar*sha1);+/*+*Likeparse_object,butwilldie()insteadofreturningNULL.Ifthe+*"name"parameterisnotNULL,itisincludedintheerrormessage+*(otherwise,thesha1hexisgiven).+*/+structobject*parse_object_or_die(constunsignedchar*sha1,constchar*name);+/* Given the result of read_sha1_file(), returns the object after*parsingit.eaten_pindicatesiftheobjecthasaborrowedcopy*ofbufferandthecallershouldnotfree()it.
From: Jeff King <hidden> Date: 2016-06-15 22:56:25
Some call-sites do:
o = parse_object(sha1);
if (!o)
die("bad object %s", some_name);
We can now handle that as a one-liner, and get more
consistent output.
In the third case of this patch, it looks like we are losing
information, as the existing message also outputs the sha1
hex; however, parse_object will already have written a more
specific complaint about the sha1, so there is no point in
repeating it here.
Signed-off-by: Jeff King <redacted>
---
builtin/grep.c | 4 +---
builtin/prune.c | 4 +---
reachable.c | 4 +---
3 files changed, 3 insertions(+), 9 deletions(-)
@@ -820,9 +820,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)unsignedcharsha1[20];/* Is it a rev? */if(!get_sha1(arg,sha1)){-structobject*object=parse_object(sha1);-if(!object)-die(_("bad object %s"),arg);+structobject*object=parse_object_or_die(sha1,arg);if(!seen_dashdash)verify_non_filename(prefix,arg);add_object_array(object,arg,&list);
From: Jeff King <hidden> Date: 2016-06-15 22:56:25
When we pack an annotated tag ref, we write not only the
sha1 of the tag object along with the ref, but also the sha1
obtained by peeling the tag. This lets readers of the
pack-refs file know the peeled value without having to
actually load the object, speeding up upload-pack's ref
advertisement.
The writer marks a packed-refs file with peeled refs using
the "peeled" trait at the top of the file. When the reader
sees this trait, it knows that each ref is either followed
by its peeled value, or it is not an annotated tag.
However, there is a mismatch between the assumptions of the
reader and writer. The writer will only peel refs under
refs/tags, but the reader does not know this; it will assume
a ref without a peeled value must not be a tag object. Thus
an annotated tag object placed outside of the refs/tags
hierarchy will not have its peeled value printed by
upload-pack.
The simplest way to fix this is to start writing peel values
for all refs. This matches what the reader expects for both
new and old versions of git.
Signed-off-by: Jeff King <redacted>
---
pack-refs.c | 16 ++++++++--------
t/t3211-peel-ref.sh | 42 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 50 insertions(+), 8 deletions(-)
create mode 100755 t/t3211-peel-ref.sh
@@ -27,6 +27,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1,intflags,void*cb_data){structpack_refs_cb_data*cb=cb_data;+structobject*o;intis_tag_ref;/* Do not pack the symbolic refs */
@@ -0,0 +1,42 @@+#!/bin/sh++test_description='tests for the peel_ref optimization of packed-refs'+../test-lib.sh++test_expect_success'create annotated tag in refs/tags''+test_commitbase&&+gittag-mannotatedfoo+'++test_expect_success'create annotated tag outside of refs/tags''+gitupdate-refrefs/outside/foorefs/tags/foo+'++# This matches show-ref's output+print_ref(){+echo"$(gitrev-parse"$1")$1"+}++test_expect_success'set up expected show-ref output''+{+print_ref"refs/heads/master"&&+print_ref"refs/outside/foo"&&+print_ref"refs/outside/foo^{}"&&+print_ref"refs/tags/base"&&+print_ref"refs/tags/foo"&&+print_ref"refs/tags/foo^{}"+}>expect+'++test_expect_success'refs are peeled outside of refs/tags (loose)''+gitshow-ref-d>actual&&+test_cmpexpectactual+'++test_expect_success'refs are peeled outside of refs/tags (packed)''+gitpack-refs--all&&+gitshow-ref-d>actual&&+test_cmpexpectactual+'++test_done
From: Jeff King <hidden> Date: 2016-06-15 22:56:25
From: Michael Haggerty <redacted>
Older versions of pack-refs did not write peel lines for
refs outside of refs/tags. This meant that on reading the
pack-refs file, we might set the REF_KNOWS_PEELED flag for
such a ref, even though we do not know anything about its
peeled value.
The previous commit updated the writer to always peel, no
matter what the ref is. That means that packed-refs files
written by newer versions of git are fine to be read by both
old and new versions of git. However, we still have the
problem of reading packed-refs files written by older
versions of git, or by other implementations which have not
yet learned the same trick.
The simplest fix would be to always unset the
REF_KNOWS_PEELED flag for refs outside of refs/tags that do
not have a peel line (if it has a peel line, we know it is
valid, but we cannot assume a missing peel line means
anything). But that loses an important optimization, as
upload-pack should not need to load the object pointed to by
refs/heads/foo to determine that it is not a tag.
Instead, we add a "fully-peeled" trait to the packed-refs
file. If it is set, we know that we can trust a missing peel
line to mean that a ref cannot be peeled. Otherwise, we fall
back to assuming nothing.
[commit message and tests by Jeff King [off-list ref]]
Signed-off-by: Jeff King <redacted>
---
This uses Michael's approach for managing the flags within
read_packed_refs, which is more readable. As I picked up his
code and comments, I realized that there was basically
nothing of mine left, so I switched the authorship. But do
note:
1. It should have Michael's signoff, which was not present
in the commit I lifted the code from.
2. I tweaked the big comment above read_packed_refs to
reduce some ambiguities. Please double-check that I am
not putting inaccurate words in your mouth. :)
pack-refs.c | 2 +-
refs.c | 43 +++++++++++++++++++++++++++++++++++++++++--
t/t3211-peel-ref.sh | 22 ++++++++++++++++++++++
3 files changed, 64 insertions(+), 3 deletions(-)
@@ -128,7 +128,7 @@ int pack_refs(unsigned int flags)die_errno("unable to create ref-pack file structure");/* perhaps other traits later as well */-fprintf(cbdata.refs_file,"# pack-refs with: peeled \n");+fprintf(cbdata.refs_file,"# pack-refs with: peeled fully-peeled \n");for_each_ref(handle_one_ref,&cbdata);if(ferror(cbdata.refs_file))
@@ -39,4 +39,26 @@ test_expect_success 'refs are peeled outside of refs/tags (packed)' 'test_cmpexpectactual'+test_expect_success'create old-style pack-refs without fully-peeled''+# Git no longer writes without fully-peeled, so we just write our own+# from scratch; we could also munge the existing file to remove the+# fully-peeled bits, but that seems even more prone to failure,+# especially if the format ever changes again. At least this way we+# know we are emulating exactly what an older git would have written.+{+echo"# pack-refs with: peeled "&&+print_ref"refs/heads/master"&&+print_ref"refs/outside/foo"&&+print_ref"refs/tags/base"&&+print_ref"refs/tags/foo"&&+echo"^$(gitrev-parse"refs/tags/foo^{}")"+}>tmp&&+mvtmp.git/packed-refs+'++test_expect_success'refs are peeled outside of refs/tags (old packed)''+gitshow-ref-d>actual&&+test_cmpexpectactual+'+ test_done
From: Michael Haggerty <hidden> Date: 2016-06-15 22:56:25
Signed-off-by: Michael Haggerty <redacted>
and ACK for the whole series, once Junio's points are addressed.
Regarding Junio's readability suggestion: I agree that his versions are
a bit more readable, albeit at the expense of having to evaluate a bit
more logic for each reference rather than just once when the header line
is handled. So I don't have a preference either way.
Michael
On 03/17/2013 09:28 AM, Jeff King wrote:
quoted hunk
From: Michael Haggerty <redacted>
Older versions of pack-refs did not write peel lines for
refs outside of refs/tags. This meant that on reading the
pack-refs file, we might set the REF_KNOWS_PEELED flag for
such a ref, even though we do not know anything about its
peeled value.
The previous commit updated the writer to always peel, no
matter what the ref is. That means that packed-refs files
written by newer versions of git are fine to be read by both
old and new versions of git. However, we still have the
problem of reading packed-refs files written by older
versions of git, or by other implementations which have not
yet learned the same trick.
The simplest fix would be to always unset the
REF_KNOWS_PEELED flag for refs outside of refs/tags that do
not have a peel line (if it has a peel line, we know it is
valid, but we cannot assume a missing peel line means
anything). But that loses an important optimization, as
upload-pack should not need to load the object pointed to by
refs/heads/foo to determine that it is not a tag.
Instead, we add a "fully-peeled" trait to the packed-refs
file. If it is set, we know that we can trust a missing peel
line to mean that a ref cannot be peeled. Otherwise, we fall
back to assuming nothing.
[commit message and tests by Jeff King [off-list ref]]
Signed-off-by: Jeff King <redacted>
---
This uses Michael's approach for managing the flags within
read_packed_refs, which is more readable. As I picked up his
code and comments, I realized that there was basically
nothing of mine left, so I switched the authorship. But do
note:
1. It should have Michael's signoff, which was not present
in the commit I lifted the code from.
2. I tweaked the big comment above read_packed_refs to
reduce some ambiguities. Please double-check that I am
not putting inaccurate words in your mouth. :)
pack-refs.c | 2 +-
refs.c | 43 +++++++++++++++++++++++++++++++++++++++++--
t/t3211-peel-ref.sh | 22 ++++++++++++++++++++++
3 files changed, 64 insertions(+), 3 deletions(-)
@@ -128,7 +128,7 @@ int pack_refs(unsigned int flags)die_errno("unable to create ref-pack file structure");/* perhaps other traits later as well */-fprintf(cbdata.refs_file,"# pack-refs with: peeled \n");+fprintf(cbdata.refs_file,"# pack-refs with: peeled fully-peeled \n");for_each_ref(handle_one_ref,&cbdata);if(ferror(cbdata.refs_file))
@@ -39,4 +39,26 @@ test_expect_success 'refs are peeled outside of refs/tags (packed)' 'test_cmpexpectactual'+test_expect_success'create old-style pack-refs without fully-peeled''+# Git no longer writes without fully-peeled, so we just write our own+# from scratch; we could also munge the existing file to remove the+# fully-peeled bits, but that seems even more prone to failure,+# especially if the format ever changes again. At least this way we+# know we are emulating exactly what an older git would have written.+{+echo"# pack-refs with: peeled "&&+print_ref"refs/heads/master"&&+print_ref"refs/outside/foo"&&+print_ref"refs/tags/base"&&+print_ref"refs/tags/foo"&&+echo"^$(gitrev-parse"refs/tags/foo^{}")"+}>tmp&&+mvtmp.git/packed-refs+'++test_expect_success'refs are peeled outside of refs/tags (old packed)''+gitshow-ref-d>actual&&+test_cmpexpectactual+'+ test_done