Re: [PATCH] fsck: return non-zero status on missing ref tips

2014-09-15 Thread Michael Haggerty
On 09/12/2014 06:58 AM, Junio C Hamano wrote:
 On Thu, Sep 11, 2014 at 9:29 PM, Jeff King p...@peff.net wrote:
 [+cc mhagger for packed-refs wisdom]

 If we only have a packed copy of refs/heads/master and it is broken,
 then deleting any _other_ unrelated ref will cause refs/heads/master to
 be dropped from the packed-refs file entirely. We get an error message,
 but that's easy to miss, and the pointer to master's sha1 is lost
 forever.
 
 Hmph, and the significance of losing a random 20-byte object name that
 is useless in your repository is? You could of course ask around other
 repositories (i.e. your origin, others that fork from the same origin,
 etc.), and having the name might make it easier to locate the exact
 object.
 
 But in such a case, either they have it at the tip (in which case you
 can just fetch the branch you lost), or they have it reachable from
 one of their tips of branches you had shown interest in (that is why
 you had that lost object in the first place). Either way, you would be
 running git fetch or asking them to send git bundle output to be
 unbundled at your end, and the way you ask would be by refname, not
 the object name, so I am not sure if the loss is that grave.
 
 Perhaps I am missing something, of course, though.

I don't understand your argument.

First, you would not just lose the SHA-1 of the object. You would also
lose the name of the reference that was previously pointing at it.

Second, the discarded information *is* useful. The more information you
have, the more likely you can restore it and/or diagnose the original
cause of the corruption.

Third, even if the discarded information were not useful, the fact that
*information has gone missing* is of overwhelming importance, and that
fact would be forgotten as soon as the warning message scrolls off of
your terminal. The reference deletion that triggered the warning might
even have been done in the background by some other process (e.g., a
GUI) and the output discarded or shunted into some debug window that
the user would have no reason to look at.

So I agree with Peff that it would be prudent to preserve the corrupt
reference at least until the next git fsck, which (a) is run by the
user specifically to look for corruption, and (b) can return an error
result to make the failure obvious.

The only thing that is unclear to me is whether the user would be able
to get rid of the broken reference once it is discovered (short of
opening packed-refs in an editor).

Michael

-- 
Michael Haggerty
mhag...@alum.mit.edu

--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH] fsck: return non-zero status on missing ref tips

2014-09-15 Thread Michael Haggerty
On 09/12/2014 06:29 AM, Jeff King wrote:
 [+cc mhagger for packed-refs wisdom]
 
 On Thu, Sep 11, 2014 at 11:38:30PM -0400, Jeff King wrote:
 
 Fsck tries hard to detect missing objects, and will complain
 (and exit non-zero) about any inter-object links that are
 missing. However, it will not exit non-zero for any missing
 ref tips, meaning that a severely broken repository may
 still pass git fsck  echo ok.

 The problem is that we use for_each_ref to iterate over the
 ref tips, which hides broken tips. It does at least print an
 error from the refs.c code, but fsck does not ever see the
 ref and cannot note the problem in its exit code. We can solve
 this by using for_each_rawref and noting the error ourselves.
 
 There's a possibly related problem with packed-refs that I noticed while
 looking at this.
 
 When we call pack-refs, it will refuse to pack any broken loose refs,
 and leave them loose. Which is sane. But when we delete a ref, we need
 to rewrite the packed-refs file, and we omit any broken packed refs. We
 wouldn't have written a broken entry, but we may get broken later (i.e.,
 the tip object may go missing after the packed-refs file is written).
 
 If we only have a packed copy of refs/heads/master and it is broken,
 then deleting any _other_ unrelated ref will cause refs/heads/master to
 be dropped from the packed-refs file entirely. We get an error message,
 but that's easy to miss, and the pointer to master's sha1 is lost
 forever.

I was confused for a while by your observation, because the curate
function has

if (read_ref_full(entry-name, sha1, 0, flags))
/* We should at least have found the packed ref. */
die(Internal error);

, which looks like more than emit an error message and continue. But
in fact the flow never gets this far, because iterating without
DO_FOR_EACH_INCLUDE_BROKEN doesn't just skip references for which
REF_ISBROKEN is set, but also (do to a test in do_one_ref()) references
for which ref_resolves_to_object() fails. The ultimate source of my
confusion is that the word BROKEN has two different meanings in the two
constants' names.

 [...]
 I am tempted to say that we do not need to do curate_each_ref_fn at all.
 Any entry with a broken sha1 is either:
 
   1. A truly broken ref, in which case we should make sure to keep it
  (i.e., it is not cruft at all).
 
   2. A crufty entry that has been replaced by a loose reference that has
  not yet been packed. Such a crufty entry may point to broken
  objects, and that is OK.
 
 In case 2, we _could_ delete the cruft. But I do not think we need to.
 The loose ref will take precedence to anybody who actually does a ref
 lookup, so the cruft is not hurting anybody.
 
 Dropping curate_packed_ref_fn (as below) fixes the test above. And
 miraculously does not even seem to conflict with ref patches in pu. :)
 
 Am I missing any case that it is actually helping?

Something inside me screams out in horror that we would pass up an
opportunity to delete unneeded cruft from the packed-refs file. But I
can't think of a rational reason to disagree with you, so as far as I'm
concerned your suggestion seems OK.

Michael

-- 
Michael Haggerty
mhag...@alum.mit.edu

--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH] fsck: return non-zero status on missing ref tips

2014-09-11 Thread Jeff King
 On Tue, Sep 09, 2014 at 03:03:33PM -0700, Junio C Hamano wrote:
 
  Upon finding a corrupt loose object, we forgot to note the error to
  signal it with the exit status of the entire process.
  
  [jc: adjusted t1450 and added another test]
  
  Signed-off-by: Junio C Hamano gits...@pobox.com
  ---
  
   * I think your fix is a right one that catches all the we can
 parse minimally for the purpose of 'struct object' class system,
 but the object is semantically broken cases, as fsck_obj() is
 where such a validation should all happen.

Here's another structural case that we should catch but do not:

-- 8 --
Subject: fsck: return non-zero status on missing ref tips

Fsck tries hard to detect missing objects, and will complain
(and exit non-zero) about any inter-object links that are
missing. However, it will not exit non-zero for any missing
ref tips, meaning that a severely broken repository may
still pass git fsck  echo ok.

The problem is that we use for_each_ref to iterate over the
ref tips, which hides broken tips. It does at least print an
error from the refs.c code, but fsck does not ever see the
ref and cannot note the problem in its exit code. We can solve
this by using for_each_rawref and noting the error ourselves.

In addition to adding tests for this case, we add tests for
all types of missing-object links (all of which worked, but
which we were not testing).

Signed-off-by: Jeff King p...@peff.net
---
Just below here we check that refs/heads/* points only to commit
objects. That's also sort-of-structural, but is pretty easy to recover
from without data loss, so I don't think it is as obvious a candidate
for a non-zero exit.

 builtin/fsck.c  |  3 ++-
 t/t1450-fsck.sh | 56 
 2 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/builtin/fsck.c b/builtin/fsck.c
index 29de901..0928a98 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -489,6 +489,7 @@ static int fsck_handle_ref(const char *refname, const 
unsigned char *sha1, int f
obj = parse_object(sha1);
if (!obj) {
error(%s: invalid sha1 pointer %s, refname, 
sha1_to_hex(sha1));
+   errors_found |= ERROR_REACHABLE;
/* We'll continue with the rest despite the error.. */
return 0;
}
@@ -505,7 +506,7 @@ static void get_default_heads(void)
 {
if (head_points_at  !is_null_sha1(head_sha1))
fsck_handle_ref(HEAD, head_sha1, 0, NULL);
-   for_each_ref(fsck_handle_ref, NULL);
+   for_each_rawref(fsck_handle_ref, NULL);
if (include_reflogs)
for_each_reflog(fsck_handle_reflog, NULL);
 
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
index 0de755c..b52397a 100755
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
@@ -302,4 +302,60 @@ test_expect_success 'fsck notices .git in trees' '
)
 '
 
+# create a static test repo which is broken by omitting
+# one particular object ($1, which is looked up via rev-parse
+# in the new repository).
+create_repo_missing () {
+   rm -rf missing 
+   git init missing 
+   (
+   cd missing 
+   git commit -m one --allow-empty 
+   mkdir subdir 
+   echo content subdir/file 
+   git add subdir/file 
+   git commit -m two 
+   unrelated=$(echo unrelated | git hash-object --stdin -w) 
+   git tag -m foo tag $unrelated 
+   sha1=$(git rev-parse --verify $1) 
+   path=$(echo $sha1 | sed 's|..|/|') 
+   rm .git/objects/$path
+   )
+}
+
+test_expect_success 'fsck notices missing blob' '
+   create_repo_missing HEAD:subdir/file 
+   test_must_fail git -C missing fsck
+'
+
+test_expect_success 'fsck notices missing subtree' '
+   create_repo_missing HEAD:subdir 
+   test_must_fail git -C missing fsck
+'
+
+test_expect_success 'fsck notices missing root tree' '
+   create_repo_missing HEAD^{tree} 
+   test_must_fail git -C missing fsck
+'
+
+test_expect_success 'fsck notices missing parent' '
+   create_repo_missing HEAD^ 
+   test_must_fail git -C missing fsck
+'
+
+test_expect_success 'fsck notices missing tagged object' '
+   create_repo_missing tag^{blob} 
+   test_must_fail git -C missing fsck
+'
+
+test_expect_success 'fsck notices ref pointing to missing commit' '
+   create_repo_missing HEAD 
+   test_must_fail git -C missing fsck
+'
+
+test_expect_success 'fsck notices ref pointing to missing tag' '
+   create_repo_missing tag 
+   test_must_fail git -C missing fsck
+'
+
 test_done
-- 
2.1.0.373.g91ca799

--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH] fsck: return non-zero status on missing ref tips

2014-09-11 Thread Jeff King
[+cc mhagger for packed-refs wisdom]

On Thu, Sep 11, 2014 at 11:38:30PM -0400, Jeff King wrote:

 Fsck tries hard to detect missing objects, and will complain
 (and exit non-zero) about any inter-object links that are
 missing. However, it will not exit non-zero for any missing
 ref tips, meaning that a severely broken repository may
 still pass git fsck  echo ok.
 
 The problem is that we use for_each_ref to iterate over the
 ref tips, which hides broken tips. It does at least print an
 error from the refs.c code, but fsck does not ever see the
 ref and cannot note the problem in its exit code. We can solve
 this by using for_each_rawref and noting the error ourselves.

There's a possibly related problem with packed-refs that I noticed while
looking at this.

When we call pack-refs, it will refuse to pack any broken loose refs,
and leave them loose. Which is sane. But when we delete a ref, we need
to rewrite the packed-refs file, and we omit any broken packed refs. We
wouldn't have written a broken entry, but we may get broken later (i.e.,
the tip object may go missing after the packed-refs file is written).

If we only have a packed copy of refs/heads/master and it is broken,
then deleting any _other_ unrelated ref will cause refs/heads/master to
be dropped from the packed-refs file entirely. We get an error message,
but that's easy to miss, and the pointer to master's sha1 is lost
forever.

This test (on top of the patch I just sent) demonstrates:

diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
index b52397a..b0f4545 100755
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
@@ -317,6 +317,7 @@ create_repo_missing () {
git commit -m two 
unrelated=$(echo unrelated | git hash-object --stdin -w) 
git tag -m foo tag $unrelated 
+   git pack-refs --all --prune 
sha1=$(git rev-parse --verify $1) 
path=$(echo $sha1 | sed 's|..|/|') 
rm .git/objects/$path
@@ -358,4 +359,10 @@ test_expect_success 'fsck notices ref pointing to missing 
tag' '
test_must_fail git -C missing fsck
 '
 
+test_expect_success 'ref deletion does not eat broken refs' '
+   create_repo_missing HEAD 
+   git -C missing update-ref -d refs/tags/tag 
+   test_must_fail git -C missing fsck
+'
+
 test_done


The fsck will succeed even though master should be broken, because
we appear to have no refs at all.

The fault is in curate_packed_ref_fn, which drops crufty from
packed-refs as we repack. That in turn is representing an older:

 if (!ref_resolves_to_object(entry))
 return 0; /* Skip broken refs */

which comes from 624cac3 (refs: change the internal reference-iteration
API, 2013-04-22). But that was just maintaining the existing behavior,
which was using a do_for_each_ref iteration without INCLUDE_BROKEN. So I
think this problem has a similar root, though the fix is now slightly
different.

I am tempted to say that we do not need to do curate_each_ref_fn at all.
Any entry with a broken sha1 is either:

  1. A truly broken ref, in which case we should make sure to keep it
 (i.e., it is not cruft at all).

  2. A crufty entry that has been replaced by a loose reference that has
 not yet been packed. Such a crufty entry may point to broken
 objects, and that is OK.

In case 2, we _could_ delete the cruft. But I do not think we need to.
The loose ref will take precedence to anybody who actually does a ref
lookup, so the cruft is not hurting anybody.

Dropping curate_packed_ref_fn (as below) fixes the test above. And
miraculously does not even seem to conflict with ref patches in pu. :)

Am I missing any case that it is actually helping?

diff --git a/refs.c b/refs.c
index a7853cc..83c2bf7 100644
--- a/refs.c
+++ b/refs.c
@@ -2484,70 +2484,11 @@ int pack_refs(unsigned int flags)
 }
 
 /*
- * If entry is no longer needed in packed-refs, add it to the string
- * list pointed to by cb_data.  Reasons for deleting entries:
- *
- * - Entry is broken.
- * - Entry is overridden by a loose ref.
- * - Entry does not point at a valid object.
- *
- * In the first and third cases, also emit an error message because these
- * are indications of repository corruption.
- */
-static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)
-{
-   struct string_list *refs_to_delete = cb_data;
-
-   if (entry-flag  REF_ISBROKEN) {
-   /* This shouldn't happen to packed refs. */
-   error(%s is broken!, entry-name);
-   string_list_append(refs_to_delete, entry-name);
-   return 0;
-   }
-   if (!has_sha1_file(entry-u.value.sha1)) {
-   unsigned char sha1[20];
-   int flags;
-
-   if (read_ref_full(entry-name, sha1, 0, flags))
-   /* We should at least have found the packed ref. */
-   die(Internal error);
-   if ((flags  REF_ISSYMREF) || !(flags  

Re: [PATCH] fsck: return non-zero status on missing ref tips

2014-09-11 Thread Jeff King
On Fri, Sep 12, 2014 at 12:29:39AM -0400, Jeff King wrote:

 Dropping curate_packed_ref_fn (as below) fixes the test above. And
 miraculously does not even seem to conflict with ref patches in pu. :)

Of course I spoke too soon. The patch I sent is actually based on pu. It
is easy to make the equivalent change in either master or pu (they
are both just deletions of the same blocks), but the code mutated a
little in between, and there are purely textual conflicts going from one
to the other.

-Peff
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH] fsck: return non-zero status on missing ref tips

2014-09-11 Thread Junio C Hamano
On Thu, Sep 11, 2014 at 9:29 PM, Jeff King p...@peff.net wrote:
 [+cc mhagger for packed-refs wisdom]

 If we only have a packed copy of refs/heads/master and it is broken,
 then deleting any _other_ unrelated ref will cause refs/heads/master to
 be dropped from the packed-refs file entirely. We get an error message,
 but that's easy to miss, and the pointer to master's sha1 is lost
 forever.

Hmph, and the significance of losing a random 20-byte object name that
is useless in your repository is? You could of course ask around other
repositories (i.e. your origin, others that fork from the same origin,
etc.), and having the name might make it easier to locate the exact
object.

But in such a case, either they have it at the tip (in which case you
can just fetch the branch you lost), or they have it reachable from
one of their tips of branches you had shown interest in (that is why
you had that lost object in the first place). Either way, you would be
running git fetch or asking them to send git bundle output to be
unbundled at your end, and the way you ask would be by refname, not
the object name, so I am not sure if the loss is that grave.

Perhaps I am missing something, of course, though.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH] fsck: return non-zero status on missing ref tips

2014-09-11 Thread Jeff King
On Thu, Sep 11, 2014 at 09:58:45PM -0700, Junio C Hamano wrote:

 On Thu, Sep 11, 2014 at 9:29 PM, Jeff King p...@peff.net wrote:
  [+cc mhagger for packed-refs wisdom]
 
  If we only have a packed copy of refs/heads/master and it is broken,
  then deleting any _other_ unrelated ref will cause refs/heads/master to
  be dropped from the packed-refs file entirely. We get an error message,
  but that's easy to miss, and the pointer to master's sha1 is lost
  forever.
 
 Hmph, and the significance of losing a random 20-byte object name that
 is useless in your repository is? You could of course ask around other
 repositories (i.e. your origin, others that fork from the same origin,
 etc.), and having the name might make it easier to locate the exact
 object.

Because your repository is corrupted and broken, and we then forget that
fact. I.e., it is not a random 20-byte object name. It is the thing that
your branch is pointing at, and that is valuable in itself. If you can
restore the object (e.g., from another repository), you need to know
which object to restore.

But I also think corrupting a repository and not noticing is quite bad
in itself.

 But in such a case, either they have it at the tip (in which case you
 can just fetch the branch you lost), or they have it reachable from
 one of their tips of branches you had shown interest in (that is why
 you had that lost object in the first place). Either way, you would be
 running git fetch or asking them to send git bundle output to be
 unbundled at your end, and the way you ask would be by refname, not
 the object name, so I am not sure if the loss is that grave.

Yes, but after you get the objects from the other person, what do you
set your ref to? If I know my tip was at commit X, I can get any set of
objects from another untrusted person that includes X, verify what they
sent me cryptographically, and restore my tip to X.

If I do not know X, they can send me any random set of objects. I can
verify that the sha1s are OK and the graph is complete, but I cannot
verify that the contents are sane. I am effectively just picking their
master as my new starting point, and trusting that it has not been
tampered with.

-Peff
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html