From: Junio C Hamano <hidden> Date: 2017-08-01 17:11:56
Jonathan Tan [off-list ref] writes:
Well, the fsck can still detect issues like corrupt objects (as you
mention above) and dangling heads, which might be real issues. But it is
true that it does not give you the guarantee you describe.
Which makes it pretty much useless. The whole point of running
"fsck" is to make sure that we won't waste work by not finding
a corruption long after it was introduced and spent a lot of effort
building on top of a state that nobody can reproduce.
From a user standpoint, this might be able to be worked around by
providing a network-requiring object connectivity checking tool or by
just having the user running a build to ensure that all necessary files
are present.
I actually was hoping that you do not have to go to the network for
the checking. And I have to say that "only the tip matters" is a
horrible cop-out that is not even a workaround. Your users would be
served better if you honestly admit that your fsck will not be
useful when this feature is used---at least they won't be harmed by
a false expectation that "fsck" would give them some assurance,
which is not the case.
Let's step back a bit and think what already happens in the pre-
lazy-object world. We record cut-off commits when a depth limited
clone is created in "shallow". These essentially are promises,
saying something like:
Rest assured that everything in the history behind these commits
are on the other side and you can retrieve them by unshallowing.
If you traverse from your local tips and find no missing objects
before reaching one of these commits, then you do not have any
local corruption you need to worry about.
the other end made to us, when the shallow clone was made. And we
take this promise and build more commits on top, and then we adjust
these cut-off commits incrementally as we deepen our clone or make
it even shallower. For this assurance to work, we of course need to
assume a bit more than what we assume for a complete clone, namely,
the "other side" will hold onto the history behind these, i.e. does
not remind the tips it already has shown to us, or even if it does,
the objects that are reachable from these cut-off points will
somehow always be available to us on demand.
Can we do something similar, i.e. maintain minimum set of cut-off
points and adjust that set incrementally, just sufficient to ensure
the integrity of objects locally created and not yet safely stored
away by pushing them the "other side"?
I haven't thought things through (and I know you, Ben and others
have thought much longer and harder), but I would imagine if we have
a commit object [*1*], some of whose parent commits, trees and blobs
are locally missing, and know that the commit exists on the "other
side", we know that all of these "missing" objects that are
referenced by the commit are also available from the "other side".
IOW, I suspect that the same principle "shallow" uses to give us the
integrity guarantee can be naturally extended to allow us to see if
a broken connectivity is OK.
[Footnote]
*1* The same can be said for a tag or a tree object that we know
exist on the "other side"; they may refer, directly or
indirectly through objects we locally have, to objects that that
are missing locally, and as long as the starting point object
are known to be available on the "other side", it is OK for them
to be missing locally.
From: Jonathan Nieder <hidden> Date: 2017-08-01 17:45:11
Hi,
Junio C Hamano wrote:
Can we do something similar, i.e. maintain minimum set of cut-off
points and adjust that set incrementally, just sufficient to ensure
the integrity of objects locally created and not yet safely stored
away by pushing them the "other side"?
This sounds like a variant on the "promises" idea (maintaining a list
of objects at the frontier) described before. Instead of listing
blobs that the server promised, you are proposing listing trees that
the server has promised to handle all references from.
I haven't thought things through (and I know you, Ben and others
have thought much longer and harder), but I would imagine if we have
a commit object [*1*], some of whose parent commits, trees and blobs
are locally missing, and know that the commit exists on the "other
side", we know that all of these "missing" objects that are
referenced by the commit are also available from the "other side".
IOW, I suspect that the same principle "shallow" uses to give us the
integrity guarantee can be naturally extended to allow us to see if
a broken connectivity is OK.
If we are deeply worried about this kind of broken connectivity, there
is another case to care about: the server can "promise" to serve
requests for some object (e.g., the tree pointed to by the server's
"master") and then decide it does not want to fulfill that promise
(e.g., that tree pointed to private key material and "master" was
rewound to avoid it). In the promises model, how we do we get a fresh
understanding of what the server wants to promise now?
Earlier in this discussion of fsck, I thought you were proposing a
slightly different idea. The idea I heard is that you want to check
connectivity for whatever you have built locally, while accepting a
relaxed guarantee for objects from upstream. If an object is missing,
the idea would be that at least this way you know whose fault it is.
:) (Not that there's much to do with that knowledge.) Implementing
that by treating everything reachable from a remote-tracking branch as
"from upstream" seems natural. But that implementation suffers from
the same problems: not all objects from upstream need be reachable
from a remote-tracking branch (e.g. after a fetch-by-object-id, or
because a remote branch can be rewound).
Both variants proposed of the promises idea also hold some promise,
but my understanding was that the cost of maintaining the promises
file (getting data to fill it, locking on update, merging in new
objects into it on update), for little benefit wasn't palatable to
Microsoft, who has been coping fine without such a file.
Jonathan
From: Jonathan Tan <hidden> Date: 2017-08-02 00:19:57
On Tue, 01 Aug 2017 10:11:38 -0700
Junio C Hamano [off-list ref] wrote:
Let's step back a bit and think what already happens in the pre-
lazy-object world. We record cut-off commits when a depth limited
clone is created in "shallow". These essentially are promises,
saying something like:
Rest assured that everything in the history behind these commits
are on the other side and you can retrieve them by unshallowing.
If you traverse from your local tips and find no missing objects
before reaching one of these commits, then you do not have any
local corruption you need to worry about.
the other end made to us, when the shallow clone was made. And we
take this promise and build more commits on top, and then we adjust
these cut-off commits incrementally as we deepen our clone or make
it even shallower. For this assurance to work, we of course need to
assume a bit more than what we assume for a complete clone, namely,
the "other side" will hold onto the history behind these, i.e. does
not remind the tips it already has shown to us, or even if it does,
the objects that are reachable from these cut-off points will
somehow always be available to us on demand.
Can we do something similar, i.e. maintain minimum set of cut-off
points and adjust that set incrementally, just sufficient to ensure
the integrity of objects locally created and not yet safely stored
away by pushing them the "other side"?
This suggestion (the "frontier" of what we have) does seem to incur less
overhead than the original promise suggestion (the "frontier" of what we
don't have), but after some in-office discussion, I'm convinced that it
might not be the case - for example, one tree (that we have) might
reference many blobs (that we don't have), but at the same time, many
trees (that we have) might have the same blob (that we don't have). And
the promise overhead was already decided to be too much - which is why
we moved away from it.
One possibility to conceptually have the same thing without the overhead
of the list is to put the obtained-from-elsewhere objects into its own
alternate object store, so that we can distinguish the two. I mentioned
this in my e-mail but rejected it, but after some more thought, this
might be sufficient - we might still need to iterate through every
object to know exactly what we can assume the remote to have, but the
"frontier" solution also needs this iteration, so we are no worse off.
Going back to the original use cases that motivated this (the monorepo
like Microsoft's repo and the large-blob repo like Android's repo), it
might be better just to disable the connectivity check when
extensions.lazyObject is set (as you mentioned). This does change the
meaning of fsck, but it may be fine since the "meaning" of the repo (a
view of another repo, and no longer a full repo) has changed too. Then
this patch set will be more about ensuring that the lazy object loader
is not inadvertently run. As future work, we could add diagnostics that,
for example, attempt a walk anyway and print a list of missing SHA-1s.
(I suspect that we will also need to disable the connectivity check for
things like "git fetch", which means that we won't be able to tell
locally if the server sent us all the objects that we requested for.
This might not be a problem, though, since the local repo already has
some measure of trust for the server.)