Use ObjectInserter for loose objects in WalkFetchConnection

Rather than relying on the repository's ability to give us the
local file path for a loose object, just pass its inflated form to
the ObjectInserter for the repository.  We have to recompress it,
which may slow down fetches, but this is the slow dumb protocol.
The extra cost to do the compression locally isn't going to be a
major bottleneck.

This nicely removes the nasty part about computing the object
identity by hand, allowing us to instead rely upon the inserter's
internal computation.  Unfortunately it means we might store a loose
object whose SHA-1 doesn't match the expected SHA-1, such as if the
remote repository was corrupted.  This is fairly harmless, as the
incorrectly named object will now be stored under its proper name,
and will eventually be garbage collected, as its not referenced by
the local repository.

We have to flush the inserter after the object is stored because
we aren't sure if we need to read the object later, or not.

Change-Id: Idb1e2b1af1433a23f8c3fd55aeb20575e6047ef0
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
This commit is contained in:
Shawn O. Pearce 2010-06-23 18:26:40 -07:00
parent 5cfc29b491
commit 41c04bbb28
1 changed files with 18 additions and 56 deletions

View File

@ -48,7 +48,6 @@
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.security.MessageDigest;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -63,7 +62,6 @@
import org.eclipse.jgit.errors.CompoundException; import org.eclipse.jgit.errors.CompoundException;
import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.ObjectWritingException;
import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
@ -72,6 +70,7 @@
import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectChecker;
import org.eclipse.jgit.lib.ObjectDirectory; import org.eclipse.jgit.lib.ObjectDirectory;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PackIndex; import org.eclipse.jgit.lib.PackIndex;
import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.PackLock;
import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.ProgressMonitor;
@ -166,8 +165,6 @@ class WalkFetchConnection extends BaseFetchConnection {
private final MutableObjectId idBuffer = new MutableObjectId(); private final MutableObjectId idBuffer = new MutableObjectId();
private final MessageDigest objectDigest = Constants.newMessageDigest();
/** /**
* Errors received while trying to obtain an object. * Errors received while trying to obtain an object.
* <p> * <p>
@ -181,10 +178,14 @@ class WalkFetchConnection extends BaseFetchConnection {
private final List<PackLock> packLocks; private final List<PackLock> packLocks;
/** Inserter to write objects onto {@link #local}. */
private final ObjectInserter inserter;
WalkFetchConnection(final WalkTransport t, final WalkRemoteObjectDatabase w) { WalkFetchConnection(final WalkTransport t, final WalkRemoteObjectDatabase w) {
Transport wt = (Transport)t; Transport wt = (Transport)t;
local = wt.local; local = wt.local;
objCheck = wt.isCheckFetchedObjects() ? new ObjectChecker() : null; objCheck = wt.isCheckFetchedObjects() ? new ObjectChecker() : null;
inserter = local.newObjectInserter();
remotes = new ArrayList<WalkRemoteObjectDatabase>(); remotes = new ArrayList<WalkRemoteObjectDatabase>();
remotes.add(w); remotes.add(w);
@ -241,6 +242,7 @@ public void setPackLockMessage(final String message) {
@Override @Override
public void close() { public void close() {
inserter.release();
for (final RemotePack p : unfetchedPacks) { for (final RemotePack p : unfetchedPacks) {
if (p.tmpIdx != null) if (p.tmpIdx != null)
p.tmpIdx.delete(); p.tmpIdx.delete();
@ -559,8 +561,7 @@ private boolean downloadLooseObject(final AnyObjectId id,
throws TransportException { throws TransportException {
try { try {
final byte[] compressed = remote.open(looseName).toArray(); final byte[] compressed = remote.open(looseName).toArray();
verifyLooseObject(id, compressed); verifyAndInsertLooseObject(id, compressed);
saveLooseObject(id, compressed);
return true; return true;
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
// Not available in a loose format from this alternate? // Not available in a loose format from this alternate?
@ -573,8 +574,8 @@ private boolean downloadLooseObject(final AnyObjectId id,
} }
} }
private void verifyLooseObject(final AnyObjectId id, final byte[] compressed) private void verifyAndInsertLooseObject(final AnyObjectId id,
throws IOException { final byte[] compressed) throws IOException {
final UnpackedObjectLoader uol; final UnpackedObjectLoader uol;
try { try {
uol = new UnpackedObjectLoader(compressed); uol = new UnpackedObjectLoader(compressed);
@ -596,62 +597,23 @@ private void verifyLooseObject(final AnyObjectId id, final byte[] compressed)
throw e; throw e;
} }
objectDigest.reset(); final int type = uol.getType();
objectDigest.update(Constants.encodedTypeString(uol.getType())); final byte[] raw = uol.getCachedBytes();
objectDigest.update((byte) ' ');
objectDigest.update(Constants.encodeASCII(uol.getSize()));
objectDigest.update((byte) 0);
objectDigest.update(uol.getCachedBytes());
idBuffer.fromRaw(objectDigest.digest(), 0);
if (!AnyObjectId.equals(id, idBuffer)) {
throw new TransportException(MessageFormat.format(JGitText.get().incorrectHashFor
, id.name(), idBuffer.name(), Constants.typeString(uol.getType()), compressed.length));
}
if (objCheck != null) { if (objCheck != null) {
try { try {
objCheck.check(uol.getType(), uol.getCachedBytes()); objCheck.check(type, raw);
} catch (CorruptObjectException e) { } catch (CorruptObjectException e) {
throw new TransportException(MessageFormat.format(JGitText.get().transportExceptionInvalid throw new TransportException(MessageFormat.format(JGitText.get().transportExceptionInvalid
, Constants.typeString(uol.getType()), id.name(), e.getMessage())); , Constants.typeString(type), id.name(), e.getMessage()));
} }
} }
}
private void saveLooseObject(final AnyObjectId id, final byte[] compressed) ObjectId act = inserter.insert(type, raw);
throws IOException, ObjectWritingException { if (!AnyObjectId.equals(id, act)) {
final File tmp; throw new TransportException(MessageFormat.format(JGitText.get().incorrectHashFor
, id.name(), act.name(), Constants.typeString(type), compressed.length));
tmp = File.createTempFile("noz", null, local.getObjectsDirectory());
try {
final FileOutputStream out = new FileOutputStream(tmp);
try {
out.write(compressed);
} finally {
out.close();
}
tmp.setReadOnly();
} catch (IOException e) {
tmp.delete();
throw e;
} }
inserter.flush();
final File o = local.toFile(id);
if (tmp.renameTo(o))
return;
// Maybe the directory doesn't exist yet as the object
// directories are always lazily created. Note that we
// try the rename first as the directory likely does exist.
//
o.getParentFile().mkdir();
if (tmp.renameTo(o))
return;
tmp.delete();
if (local.hasObject(id))
return;
throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToStore, id.name()));
} }
private Collection<WalkRemoteObjectDatabase> expandOneAlternate( private Collection<WalkRemoteObjectDatabase> expandOneAlternate(