Browse Source

Refactor IndexPack to not require local filesystem

By moving the logic that parses a pack stream from the network (or
a bundle) into a type that can be constructed by an ObjectInserter,
repository implementations have a chance to inject their own logic
for storing object data received into the destination repository.

The API isn't completely generic yet, there are still quite a few
assumptions that the PackParser subclass is storing the data onto
the local filesystem as a single file.  But its about the simplest
split of IndexPack I can come up with without completely ripping
the code apart.

Change-Id: I5b167c9cc6d7a7c56d0197c62c0fd0036a83ec6c
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
Signed-off-by: Chris Aniszczyk <caniszczyk@gmail.com>
tags/v0.11.1
Shawn O. Pearce 13 years ago
parent
commit
1bf0c3cdb1
19 changed files with 2224 additions and 1506 deletions
  1. 18
    17
      org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/IndexPack.java
  2. 21
    11
      org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackFileTest.java
  3. 41
    22
      org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java
  4. 32
    18
      org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java
  5. 15
    4
      org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java
  6. 0
    1
      org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
  7. 20
    0
      org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
  8. 11
    0
      org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java
  9. 5
    0
      org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java
  10. 11
    2
      org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java
  11. 6
    0
      org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java
  12. 480
    0
      org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryPackParser.java
  13. 12
    7
      org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
  14. 14
    11
      org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
  15. 0
    1384
      org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java
  16. 1
    1
      org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java
  17. 1503
    0
      org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
  18. 24
    17
      org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
  19. 10
    11
      org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java

+ 18
- 17
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/IndexPack.java View File

@@ -45,12 +45,12 @@
package org.eclipse.jgit.pgm;

import java.io.BufferedInputStream;
import java.io.File;

import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
import org.eclipse.jgit.lib.CoreConfig;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.storage.file.ObjectDirectoryPackParser;
import org.eclipse.jgit.transport.PackParser;
import org.kohsuke.args4j.Option;

class IndexPack extends TextBuiltin {
@Option(name = "--fix-thin", usage = "usage_fixAThinPackToBeComplete")
@@ -59,20 +59,21 @@ class IndexPack extends TextBuiltin {
@Option(name = "--index-version", usage = "usage_indexFileFormatToCreate")
private int indexVersion = -1;

@Argument(index = 0, required = true, metaVar = "metaVar_base")
private File base;

@Override
protected void run() throws Exception {
if (indexVersion == -1)
indexVersion = db.getConfig().get(CoreConfig.KEY)
.getPackIndexVersion();
final BufferedInputStream in;
final org.eclipse.jgit.transport.IndexPack ip;
in = new BufferedInputStream(System.in);
ip = new org.eclipse.jgit.transport.IndexPack(db, in, base);
ip.setFixThin(fixThin);
ip.setIndexVersion(indexVersion);
ip.index(new TextProgressMonitor());
BufferedInputStream in = new BufferedInputStream(System.in);
ObjectInserter inserter = db.newObjectInserter();
try {
PackParser p = inserter.newPackParser(in);
p.setAllowThin(fixThin);
if (indexVersion != -1 && p instanceof ObjectDirectoryPackParser) {
ObjectDirectoryPackParser imp = (ObjectDirectoryPackParser) p;
imp.setIndexVersion(indexVersion);
}
p.parse(new TextProgressMonitor());
inserter.flush();
} finally {
inserter.release();
}
}
}

+ 21
- 11
org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackFileTest.java View File

@@ -71,7 +71,7 @@ import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectStream;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.storage.pack.DeltaEncoder;
import org.eclipse.jgit.transport.IndexPack;
import org.eclipse.jgit.transport.PackParser;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.NB;
import org.eclipse.jgit.util.TemporaryBuffer;
@@ -212,11 +212,9 @@ public class PackFileTest extends LocalDiskRepositoryTestCase {
deflate(pack, delta3);

digest(pack);
final byte[] raw = pack.toByteArray();
IndexPack ip = IndexPack.create(repo, new ByteArrayInputStream(raw));
ip.setFixThin(true);
ip.index(NullProgressMonitor.INSTANCE);
ip.renameAndOpenPack();
PackParser ip = index(pack.toByteArray());
ip.setAllowThin(true);
ip.parse(NullProgressMonitor.INSTANCE);

assertTrue("has blob", wc.has(id3));

@@ -273,11 +271,9 @@ public class PackFileTest extends LocalDiskRepositoryTestCase {
deflate(pack, delta3);

digest(pack);
final byte[] raw = pack.toByteArray();
IndexPack ip = IndexPack.create(repo, new ByteArrayInputStream(raw));
ip.setFixThin(true);
ip.index(NullProgressMonitor.INSTANCE);
ip.renameAndOpenPack();
PackParser ip = index(pack.toByteArray());
ip.setAllowThin(true);
ip.parse(NullProgressMonitor.INSTANCE);

assertTrue("has blob", wc.has(id3));

@@ -364,4 +360,18 @@ public class PackFileTest extends LocalDiskRepositoryTestCase {
md.update(buf.toByteArray());
buf.write(md.digest());
}

private ObjectInserter inserter;

@After
public void release() {
if (inserter != null)
inserter.release();
}

private PackParser index(byte[] raw) throws IOException {
if (inserter == null)
inserter = repo.newObjectInserter();
return inserter.newPackParser(new ByteArrayInputStream(raw));
}
}

+ 41
- 22
org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java View File

@@ -44,6 +44,7 @@
package org.eclipse.jgit.storage.file;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

@@ -52,7 +53,6 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -66,14 +66,14 @@ import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.PackIndex.MutableEntry;
import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.storage.pack.PackWriter;
import org.eclipse.jgit.transport.IndexPack;
import org.eclipse.jgit.transport.PackParser;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -92,28 +92,34 @@ public class PackWriterTest extends SampleDataRepositoryTestCase {

private ByteArrayOutputStream os;

private File packBase;

private File packFile;
private PackFile pack;

private File indexFile;
private ObjectInserter inserter;

private PackFile pack;
private FileRepository dst;

@Before
public void setUp() throws Exception {
super.setUp();
os = new ByteArrayOutputStream();
packBase = new File(trash, "tmp_pack");
packFile = new File(trash, "tmp_pack.pack");
indexFile = new File(trash, "tmp_pack.idx");
config = new PackConfig(db);

dst = createBareRepository();
File alt = new File(dst.getObjectDatabase().getDirectory(), "info/alternates");
alt.getParentFile().mkdirs();
write(alt, db.getObjectDatabase().getDirectory().getAbsolutePath() + "\n");
}

@After
public void tearDown() throws Exception {
if (writer != null)
if (writer != null) {
writer.release();
writer = null;
}
if (inserter != null) {
inserter.release();
inserter = null;
}
super.tearDown();
}

@@ -408,6 +414,11 @@ public class PackWriterTest extends SampleDataRepositoryTestCase {
config.setIndexVersion(2);
writeVerifyPack4(false);

File packFile = pack.getPackFile();
String name = packFile.getName();
String base = name.substring(0, name.lastIndexOf('.'));
File indexFile = new File(packFile.getParentFile(), base + ".idx");

// Validate that IndexPack came up with the right CRC32 value.
final PackIndex idx1 = PackIndex.open(indexFile);
assertTrue(idx1 instanceof PackIndexV2);
@@ -544,23 +555,31 @@ public class PackWriterTest extends SampleDataRepositoryTestCase {
}

private void verifyOpenPack(final boolean thin) throws IOException {
final byte[] packData = os.toByteArray();

if (thin) {
final InputStream is = new ByteArrayInputStream(os.toByteArray());
final IndexPack indexer = new IndexPack(db, is, packBase);
PackParser p = index(packData);
try {
indexer.index(new TextProgressMonitor());
p.parse(NullProgressMonitor.INSTANCE);
fail("indexer should grumble about missing object");
} catch (IOException x) {
// expected
}
}
final InputStream is = new ByteArrayInputStream(os.toByteArray());
final IndexPack indexer = new IndexPack(db, is, packBase);
indexer.setKeepEmpty(true);
indexer.setFixThin(thin);
indexer.setIndexVersion(2);
indexer.index(new TextProgressMonitor());
pack = new PackFile(indexFile, packFile);

ObjectDirectoryPackParser p = (ObjectDirectoryPackParser) index(packData);
p.setKeepEmpty(true);
p.setAllowThin(thin);
p.setIndexVersion(2);
p.parse(NullProgressMonitor.INSTANCE);
pack = p.getPackFile();
assertNotNull("have PackFile after parsing", pack);
}

private PackParser index(final byte[] packData) throws IOException {
if (inserter == null)
inserter = dst.newObjectInserter();
return inserter.newPackParser(new ByteArrayInputStream(packData));
}

private void verifyObjectsOrder(final ObjectId objectsOrder[]) {

org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/IndexPackTest.java → org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java View File

@@ -61,13 +61,15 @@ import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryTestCase;
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.storage.file.ObjectDirectoryPackParser;
import org.eclipse.jgit.storage.file.PackFile;
import org.eclipse.jgit.util.NB;
import org.eclipse.jgit.util.TemporaryBuffer;
import org.junit.After;
import org.junit.Test;

/**
@@ -76,8 +78,7 @@ import org.junit.Test;
* to make sure they contain the expected objects (well we don't test
* for all of them unless the packs are very small).
*/
public class IndexPackTest extends RepositoryTestCase {

public class PackParserTest extends RepositoryTestCase {
/**
* Test indexing one of the test packs in the egit repo. It has deltas.
*
@@ -88,9 +89,10 @@ public class IndexPackTest extends RepositoryTestCase {
File packFile = JGitTestUtil.getTestResourceFile("pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.pack");
final InputStream is = new FileInputStream(packFile);
try {
IndexPack pack = new IndexPack(db, is, new File(trash, "tmp_pack1"));
pack.index(new TextProgressMonitor());
PackFile file = new PackFile(new File(trash, "tmp_pack1.idx"), new File(trash, "tmp_pack1.pack"));
ObjectDirectoryPackParser p = (ObjectDirectoryPackParser) index(is);
p.parse(NullProgressMonitor.INSTANCE);
PackFile file = p.getPackFile();

assertTrue(file.hasObject(ObjectId.fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904")));
assertTrue(file.hasObject(ObjectId.fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab")));
assertTrue(file.hasObject(ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259")));
@@ -115,9 +117,10 @@ public class IndexPackTest extends RepositoryTestCase {
File packFile = JGitTestUtil.getTestResourceFile("pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.pack");
final InputStream is = new FileInputStream(packFile);
try {
IndexPack pack = new IndexPack(db, is, new File(trash, "tmp_pack2"));
pack.index(new TextProgressMonitor());
PackFile file = new PackFile(new File(trash, "tmp_pack2.idx"), new File(trash, "tmp_pack2.pack"));
ObjectDirectoryPackParser p = (ObjectDirectoryPackParser) index(is);
p.parse(NullProgressMonitor.INSTANCE);
PackFile file = p.getPackFile();

assertTrue(file.hasObject(ObjectId.fromString("02ba32d3649e510002c21651936b7077aa75ffa9")));
assertTrue(file.hasObject(ObjectId.fromString("0966a434eb1a025db6b71485ab63a3bfbea520b6")));
assertTrue(file.hasObject(ObjectId.fromString("09efc7e59a839528ac7bda9fa020dc9101278680")));
@@ -151,11 +154,9 @@ public class IndexPackTest extends RepositoryTestCase {

digest(pack);

final byte[] raw = pack.toByteArray();
IndexPack ip = IndexPack.create(db, new ByteArrayInputStream(raw));
ip.setFixThin(true);
ip.index(NullProgressMonitor.INSTANCE);
ip.renameAndOpenPack();
PackParser p = index(new ByteArrayInputStream(pack.toByteArray()));
p.setAllowThin(true);
p.parse(NullProgressMonitor.INSTANCE);
}

@Test
@@ -171,10 +172,9 @@ public class IndexPackTest extends RepositoryTestCase {
deflate(pack, data);
digest(pack);

final byte[] raw = pack.toByteArray();
IndexPack ip = IndexPack.create(db, new ByteArrayInputStream(raw));
ip.index(NullProgressMonitor.INSTANCE);
ip.renameAndOpenPack();
PackParser p = index(new ByteArrayInputStream(pack.toByteArray()));
p.setAllowThin(false);
p.parse(NullProgressMonitor.INSTANCE);
}

private void packHeader(TemporaryBuffer.Heap tinyPack, int cnt)
@@ -205,4 +205,18 @@ public class IndexPackTest extends RepositoryTestCase {
md.update(buf.toByteArray());
buf.write(md.digest());
}

private ObjectInserter inserter;

@After
public void release() {
if (inserter != null)
inserter.release();
}

private PackParser index(InputStream in) throws IOException {
if (inserter == null)
inserter = db.newObjectInserter();
return inserter.newPackParser(in);
}
}

+ 15
- 4
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java View File

@@ -67,6 +67,7 @@ import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
@@ -536,12 +537,22 @@ public class ReceivePackRefFilterTest extends LocalDiskRepositoryTestCase {
buf.write(md.digest());
}

private ObjectInserter inserter;

@After
public void release() {
if (inserter != null)
inserter.release();
}

private void openPack(TemporaryBuffer.Heap buf) throws IOException {
if (inserter == null)
inserter = src.newObjectInserter();

final byte[] raw = buf.toByteArray();
IndexPack ip = IndexPack.create(src, new ByteArrayInputStream(raw));
ip.setFixThin(true);
ip.index(PM);
ip.renameAndOpenPack();
PackParser p = inserter.newPackParser(new ByteArrayInputStream(raw));
p.setAllowThin(true);
p.parse(PM);
}

private static PacketLineIn asPacketLineIn(TemporaryBuffer.Heap buf)

+ 0
- 1
org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java View File

@@ -95,7 +95,6 @@ public class CoreConfig {

/**
* @return the preferred pack index file format; 0 for oldest possible.
* @see org.eclipse.jgit.transport.IndexPack
*/
public int getPackIndexVersion() {
return packIndexVersion;

+ 20
- 0
org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java View File

@@ -52,6 +52,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;

import org.eclipse.jgit.transport.PackParser;

/**
* Inserts objects into an existing {@code ObjectDatabase}.
* <p>
@@ -73,6 +75,11 @@ public abstract class ObjectInserter {
throw new UnsupportedOperationException();
}

@Override
public PackParser newPackParser(InputStream in) throws IOException {
throw new UnsupportedOperationException();
}

@Override
public void flush() throws IOException {
// Do nothing.
@@ -282,6 +289,19 @@ public abstract class ObjectInserter {
public abstract ObjectId insert(int objectType, long length, InputStream in)
throws IOException;

/**
* Initialize a parser to read from a pack formatted stream.
*
* @param in
* the input stream. The stream is not closed by the parser, and
* must instead be closed by the caller once parsing is complete.
* @return the pack parser.
* @throws IOException
* the parser instance, which can be configured and then used to
* parse objects into the ObjectDatabase.
*/
public abstract PackParser newPackParser(InputStream in) throws IOException;

/**
* Make all inserted objects visible.
* <p>

+ 11
- 0
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java View File

@@ -58,6 +58,7 @@ import org.eclipse.jgit.lib.ObjectIdSubclassMap;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.storage.pack.ObjectToPack;
import org.eclipse.jgit.storage.pack.PackWriter;
import org.eclipse.jgit.util.FS;

/**
* The cached instance of an {@link ObjectDirectory}.
@@ -132,6 +133,11 @@ class CachedObjectDirectory extends FileObjectDatabase {
return wrapped.getConfig();
}

@Override
FS getFS() {
return wrapped.getFS();
}

@Override
AlternateHandle[] myAlternates() {
if (alts == null) {
@@ -233,6 +239,11 @@ class CachedObjectDirectory extends FileObjectDatabase {
return result;
}

@Override
PackFile openPack(File pack, File idx) throws IOException {
return wrapped.openPack(pack, idx);
}

@Override
void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
WindowCursor curs) throws IOException {

+ 5
- 0
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java View File

@@ -56,6 +56,7 @@ import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.storage.pack.ObjectToPack;
import org.eclipse.jgit.storage.pack.PackWriter;
import org.eclipse.jgit.util.FS;

abstract class FileObjectDatabase extends ObjectDatabase {
static enum InsertLooseObjectResult {
@@ -132,6 +133,8 @@ abstract class FileObjectDatabase extends ObjectDatabase {

abstract Config getConfig();

abstract FS getFS();

/**
* Open an object from this database.
* <p>
@@ -278,6 +281,8 @@ abstract class FileObjectDatabase extends ObjectDatabase {
abstract InsertLooseObjectResult insertUnpackedObject(File tmp,
ObjectId id, boolean createDuplicate) throws IOException;

abstract PackFile openPack(File pack, File idx) throws IOException;

abstract FileObjectDatabase newCachedFileObjectDatabase();

static class AlternateHandle {

+ 11
- 2
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java View File

@@ -233,11 +233,13 @@ public class ObjectDirectory extends FileObjectDatabase {
* path of the pack file to open.
* @param idx
* path of the corresponding index file.
* @return the pack that was opened and added to the database.
* @throws IOException
* index file could not be opened, read, or is not recognized as
* a Git pack file index.
*/
public void openPack(final File pack, final File idx) throws IOException {
public PackFile openPack(final File pack, final File idx)
throws IOException {
final String p = pack.getName();
final String i = idx.getName();

@@ -250,7 +252,9 @@ public class ObjectDirectory extends FileObjectDatabase {
if (!p.substring(0, 45).equals(i.substring(0, 45)))
throw new IOException(MessageFormat.format(JGitText.get().packDoesNotMatchIndex, pack));

insertPack(new PackFile(idx, pack));
PackFile res = new PackFile(idx, pack);
insertPack(res);
return res;
}

@Override
@@ -519,6 +523,11 @@ public class ObjectDirectory extends FileObjectDatabase {
return config;
}

@Override
FS getFS() {
return fs;
}

private void insertPack(final PackFile pf) {
PackList o, n;
do {

+ 6
- 0
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java View File

@@ -63,6 +63,7 @@ import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.transport.PackParser;
import org.eclipse.jgit.util.FileUtils;

/** Creates loose objects in a {@link ObjectDirectory}. */
@@ -100,6 +101,11 @@ class ObjectDirectoryInserter extends ObjectInserter {
throw new ObjectWritingException("Unable to create new object: " + dst);
}

@Override
public PackParser newPackParser(InputStream in) throws IOException {
return new ObjectDirectoryPackParser(db, in);
}

@Override
public void flush() throws IOException {
// Do nothing. Objects are immediately visible.

+ 480
- 0
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryPackParser.java View File

@@ -0,0 +1,480 @@
/*
* Copyright (C) 2008-2011, Google Inc.
* Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.eclipse.jgit.storage.file;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.security.MessageDigest;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.zip.CRC32;
import java.util.zip.Deflater;

import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.transport.PackParser;
import org.eclipse.jgit.transport.PackedObjectInfo;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.NB;

/**
* Consumes a pack stream and stores as a pack file in {@link ObjectDirectory}.
* <p>
* To obtain an instance of a parser, applications should use
* {@link ObjectInserter#newPackParser(InputStream)}.
*/
public class ObjectDirectoryPackParser extends PackParser {
private final FileObjectDatabase db;

/** CRC-32 computation for objects that are appended onto the pack. */
private final CRC32 crc;

/** Running SHA-1 of any base objects appended after {@link #origEnd}. */
private final MessageDigest tailDigest;

/** Preferred format version of the pack-*.idx file to generate. */
private int indexVersion;

/** If true, pack with 0 objects will be stored. Usually these are deleted. */
private boolean keepEmpty;

/** Path of the temporary file holding the pack data. */
private File tmpPack;

/**
* Path of the index created for the pack, to find objects quickly at read
* time.
*/
private File tmpIdx;

/** Read/write handle to {@link #tmpPack} while it is being parsed. */
private RandomAccessFile out;

/** Length of the original pack stream, before missing bases were appended. */
private long origEnd;

/** The original checksum of data up to {@link #origEnd}. */
private byte[] origHash;

/** Current end of the pack file. */
private long packEnd;

/** Checksum of the entire pack file. */
private byte[] packHash;

/** Compresses delta bases when completing a thin pack. */
private Deflater def;

/** The pack that was created, if parsing was successful. */
private PackFile newPack;

ObjectDirectoryPackParser(FileObjectDatabase odb, InputStream src) {
super(odb, src);
this.db = odb;
this.crc = new CRC32();
this.tailDigest = Constants.newMessageDigest();

indexVersion = db.getConfig().get(CoreConfig.KEY).getPackIndexVersion();
}

/**
* Set the pack index file format version this instance will create.
*
* @param version
* the version to write. The special version 0 designates the
* oldest (most compatible) format available for the objects.
* @see PackIndexWriter
*/
public void setIndexVersion(int version) {
indexVersion = version;
}

/**
* Configure this index pack instance to keep an empty pack.
* <p>
* By default an empty pack (a pack with no objects) is not kept, as doi so
* is completely pointless. With no objects in the pack there is no d stored
* by it, so the pack is unnecessary.
*
* @param empty
* true to enable keeping an empty pack.
*/
public void setKeepEmpty(final boolean empty) {
keepEmpty = empty;
}

/**
* Get the imported {@link PackFile}.
* <p>
* This method is supplied only to support testing; applications shouldn't
* be using it directly to access the imported data.
*
* @return the imported PackFile, if parsing was successful.
*/
public PackFile getPackFile() {
return newPack;
}

@Override
public PackLock parse(ProgressMonitor progress) throws IOException {
tmpPack = File.createTempFile("incoming_", ".pack", db.getDirectory());
tmpIdx = new File(db.getDirectory(), baseName(tmpPack) + ".idx");
try {
out = new RandomAccessFile(tmpPack, "rw");

super.parse(progress);

out.seek(packEnd);
out.write(packHash);
out.getChannel().force(true);
out.close();

writeIdx();

tmpPack.setReadOnly();
tmpIdx.setReadOnly();

return renameAndOpenPack(getLockMessage());
} finally {
if (def != null)
def.end();
try {
if (out != null && out.getChannel().isOpen())
out.close();
} catch (IOException closeError) {
// Ignored. We want to delete the file.
}
cleanupTemporaryFiles();
}
}

@Override
protected void onBeginWholeObject(long streamPosition, int type,
long inflatedSize) throws IOException {
crc.reset();
}

@Override
protected void onEndWholeObject(PackedObjectInfo info) throws IOException {
info.setCRC((int) crc.getValue());
}

@Override
protected void onBeginOfsDelta(long streamPosition,
long baseStreamPosition, long inflatedSize) throws IOException {
crc.reset();
}

@Override
protected void onBeginRefDelta(long streamPosition, AnyObjectId baseId,
long inflatedSize) throws IOException {
crc.reset();
}

@Override
protected UnresolvedDelta onEndDelta() throws IOException {
UnresolvedDelta delta = new UnresolvedDelta();
delta.setCRC((int) crc.getValue());
return delta;
}

@Override
protected void onObjectHeader(Source src, byte[] raw, int pos, int len)
throws IOException {
crc.update(raw, pos, len);
}

@Override
protected void onObjectData(Source src, byte[] raw, int pos, int len)
throws IOException {
crc.update(raw, pos, len);
}

@Override
protected void onStoreStream(byte[] raw, int pos, int len)
throws IOException {
out.write(raw, pos, len);
}

@Override
protected void onPackFooter(byte[] hash) throws IOException {
packEnd = out.getFilePointer();
origEnd = packEnd;
origHash = hash;
packHash = hash;
}

@Override
protected ObjectTypeAndSize seekDatabase(UnresolvedDelta delta,
ObjectTypeAndSize info) throws IOException {
out.seek(delta.getOffset());
crc.reset();
return readObjectHeader(info);
}

@Override
protected ObjectTypeAndSize seekDatabase(PackedObjectInfo obj,
ObjectTypeAndSize info) throws IOException {
out.seek(obj.getOffset());
crc.reset();
return readObjectHeader(info);
}

@Override
protected int readDatabase(byte[] dst, int pos, int cnt) throws IOException {
return out.read(dst, pos, cnt);
}

@Override
protected boolean checkCRC(int oldCRC) {
return oldCRC == (int) crc.getValue();
}

private static String baseName(File tmpPack) {
String name = tmpPack.getName();
return name.substring(0, name.lastIndexOf('.'));
}

private void cleanupTemporaryFiles() {
if (tmpIdx != null && !tmpIdx.delete() && tmpIdx.exists())
tmpIdx.deleteOnExit();
if (tmpPack != null && !tmpPack.delete() && tmpPack.exists())
tmpPack.deleteOnExit();
}

@Override
protected boolean onAppendBase(final int typeCode, final byte[] data,
final PackedObjectInfo info) throws IOException {
info.setOffset(packEnd);

final byte[] buf = buffer();
int sz = data.length;
int len = 0;
buf[len++] = (byte) ((typeCode << 4) | sz & 15);
sz >>>= 4;
while (sz > 0) {
buf[len - 1] |= 0x80;
buf[len++] = (byte) (sz & 0x7f);
sz >>>= 7;
}

tailDigest.update(buf, 0, len);
crc.reset();
crc.update(buf, 0, len);
out.seek(packEnd);
out.write(buf, 0, len);
packEnd += len;

if (def == null)
def = new Deflater(Deflater.DEFAULT_COMPRESSION, false);
else
def.reset();
def.setInput(data);
def.finish();

while (!def.finished()) {
len = def.deflate(buf);
tailDigest.update(buf, 0, len);
crc.update(buf, 0, len);
out.write(buf, 0, len);
packEnd += len;
}

info.setCRC((int) crc.getValue());
return true;
}

@Override
protected void onEndThinPack() throws IOException {
final byte[] tailHash = this.tailDigest.digest();
final byte[] buf = buffer();

final MessageDigest origDigest = Constants.newMessageDigest();
final MessageDigest tailDigest = Constants.newMessageDigest();
final MessageDigest packDigest = Constants.newMessageDigest();

long origRemaining = origEnd;
out.seek(0);
out.readFully(buf, 0, 12);
origDigest.update(buf, 0, 12);
origRemaining -= 12;

NB.encodeInt32(buf, 8, getObjectCount());
out.seek(0);
out.write(buf, 0, 12);
packDigest.update(buf, 0, 12);

for (;;) {
final int n = out.read(buf);
if (n < 0)
break;
if (origRemaining != 0) {
final int origCnt = (int) Math.min(n, origRemaining);
origDigest.update(buf, 0, origCnt);
origRemaining -= origCnt;
if (origRemaining == 0)
tailDigest.update(buf, origCnt, n - origCnt);
} else
tailDigest.update(buf, 0, n);

packDigest.update(buf, 0, n);
}

if (!Arrays.equals(origDigest.digest(), origHash)
|| !Arrays.equals(tailDigest.digest(), tailHash))
throw new IOException(
JGitText.get().packCorruptedWhileWritingToFilesystem);

packHash = packDigest.digest();
}

private void writeIdx() throws IOException {
List<PackedObjectInfo> list = getSortedObjectList(null /* by ObjectId */);
final FileOutputStream os = new FileOutputStream(tmpIdx);
try {
final PackIndexWriter iw;
if (indexVersion <= 0)
iw = PackIndexWriter.createOldestPossible(os, list);
else
iw = PackIndexWriter.createVersion(os, indexVersion);
iw.write(list, packHash);
os.getChannel().force(true);
} finally {
os.close();
}
}

private PackLock renameAndOpenPack(final String lockMessage)
throws IOException {
if (!keepEmpty && getObjectCount() == 0) {
cleanupTemporaryFiles();
return null;
}

final MessageDigest d = Constants.newMessageDigest();
final byte[] oeBytes = new byte[Constants.OBJECT_ID_LENGTH];
for (int i = 0; i < getObjectCount(); i++) {
final PackedObjectInfo oe = getObject(i);
oe.copyRawTo(oeBytes, 0);
d.update(oeBytes);
}

final String name = ObjectId.fromRaw(d.digest()).name();
final File packDir = new File(db.getDirectory(), "pack");
final File finalPack = new File(packDir, "pack-" + name + ".pack");
final File finalIdx = new File(packDir, "pack-" + name + ".idx");
final PackLock keep = new PackLock(finalPack, db.getFS());

if (!packDir.exists() && !packDir.mkdir() && !packDir.exists()) {
// The objects/pack directory isn't present, and we are unable
// to create it. There is no way to move this pack in.
//
cleanupTemporaryFiles();
throw new IOException(MessageFormat.format(
JGitText.get().cannotCreateDirectory, packDir
.getAbsolutePath()));
}

if (finalPack.exists()) {
// If the pack is already present we should never replace it.
//
cleanupTemporaryFiles();
return null;
}

if (lockMessage != null) {
// If we have a reason to create a keep file for this pack, do
// so, or fail fast and don't put the pack in place.
//
try {
if (!keep.lock(lockMessage))
throw new IOException(MessageFormat.format(
JGitText.get().cannotLockPackIn, finalPack));
} catch (IOException e) {
cleanupTemporaryFiles();
throw e;
}
}

if (!tmpPack.renameTo(finalPack)) {
cleanupTemporaryFiles();
keep.unlock();
throw new IOException(MessageFormat.format(
JGitText.get().cannotMovePackTo, finalPack));
}

if (!tmpIdx.renameTo(finalIdx)) {
cleanupTemporaryFiles();
keep.unlock();
if (!finalPack.delete())
finalPack.deleteOnExit();
throw new IOException(MessageFormat.format(
JGitText.get().cannotMoveIndexTo, finalIdx));
}

try {
newPack = db.openPack(finalPack, finalIdx);
} catch (IOException err) {
keep.unlock();
if (finalPack.exists())
FileUtils.delete(finalPack);
if (finalIdx.exists())
FileUtils.delete(finalIdx);
throw err;
}

return lockMessage != null ? keep : null;
}
}

+ 12
- 7
org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java View File

@@ -61,6 +61,7 @@ import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Config.SectionParser;
@@ -635,17 +636,21 @@ public abstract class BasePackFetchConnection extends BasePackConnection
}

private void receivePack(final ProgressMonitor monitor) throws IOException {
final IndexPack ip;

InputStream input = in;
if (sideband)
input = new SideBandInputStream(input, monitor, getMessageWriter());

ip = IndexPack.create(local, input);
ip.setFixThin(thinPack);
ip.setObjectChecking(transport.isCheckFetchedObjects());
ip.index(monitor);
packLock = ip.renameAndOpenPack(lockMessage);
ObjectInserter ins = local.newObjectInserter();
try {
PackParser parser = ins.newPackParser(input);
parser.setAllowThin(thinPack);
parser.setObjectChecking(transport.isCheckFetchedObjects());
parser.setLockMessage(lockMessage);
packLock = parser.parse(monitor);
ins.flush();
} finally {
ins.release();
}
}

private static class CancelledException extends Exception {

+ 14
- 11
org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java View File

@@ -66,8 +66,10 @@ import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -96,7 +98,7 @@ class BundleFetchConnection extends BaseFetchConnection {

BundleFetchConnection(Transport transportBundle, final InputStream src) throws TransportException {
transport = transportBundle;
bin = new BufferedInputStream(src, IndexPack.BUFFER_SIZE);
bin = new BufferedInputStream(src);
try {
switch (readSignature()) {
case 2:
@@ -179,9 +181,17 @@ class BundleFetchConnection extends BaseFetchConnection {
throws TransportException {
verifyPrerequisites();
try {
final IndexPack ip = newIndexPack();
ip.index(monitor);
packLock = ip.renameAndOpenPack(lockMessage);
ObjectInserter ins = transport.local.newObjectInserter();
try {
PackParser parser = ins.newPackParser(bin);
parser.setAllowThin(true);
parser.setObjectChecking(transport.isCheckFetchedObjects());
parser.setLockMessage(lockMessage);
packLock = parser.parse(NullProgressMonitor.INSTANCE);
ins.flush();
} finally {
ins.release();
}
} catch (IOException err) {
close();
throw new TransportException(transport.uri, err.getMessage(), err);
@@ -201,13 +211,6 @@ class BundleFetchConnection extends BaseFetchConnection {
return Collections.<PackLock> emptyList();
}

private IndexPack newIndexPack() throws IOException {
final IndexPack ip = IndexPack.create(transport.local, bin);
ip.setFixThin(true);
ip.setObjectChecking(transport.isCheckFetchedObjects());
return ip;
}

private void verifyPrerequisites() throws TransportException {
if (prereqs.isEmpty())
return;

+ 0
- 1384
org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java
File diff suppressed because it is too large
View File


+ 1
- 1
org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java View File

@@ -44,7 +44,7 @@
package org.eclipse.jgit.transport;

/**
* Simple Map<long,Object> helper for {@link IndexPack}.
* Simple Map<long,Object> helper for {@link PackParser}.
*
* @param <V>
* type of the value instance.

+ 1503
- 0
org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
File diff suppressed because it is too large
View File


+ 24
- 17
org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java View File

@@ -70,15 +70,16 @@ import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.errors.UnpackException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Config.SectionParser;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdSubclassMap;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.Config.SectionParser;
import org.eclipse.jgit.revwalk.ObjectWalk;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -161,7 +162,7 @@ public class ReceivePack {

private Writer msgs;

private IndexPack ip;
private PackParser parser;

/** The refs we advertised as existing at the start of the connection. */
private Map<String, Ref> refs;
@@ -630,7 +631,7 @@ public class ReceivePack {
receivePack();
if (needCheckConnectivity())
checkConnectivity();
ip = null;
parser = null;
unpackError = null;
} catch (IOException err) {
unpackError = err;
@@ -779,17 +780,23 @@ public class ReceivePack {
if (timeoutIn != null)
timeoutIn.setTimeout(10 * timeout * 1000);

ip = IndexPack.create(db, rawIn);
ip.setFixThin(true);
ip.setNeedNewObjectIds(checkReferencedIsReachable);
ip.setNeedBaseObjectIds(checkReferencedIsReachable);
ip.setObjectChecking(isCheckReceivedObjects());
ip.index(NullProgressMonitor.INSTANCE);

String lockMsg = "jgit receive-pack";
if (getRefLogIdent() != null)
lockMsg += " from " + getRefLogIdent().toExternalString();
packLock = ip.renameAndOpenPack(lockMsg);
ObjectInserter ins = db.newObjectInserter();
try {
String lockMsg = "jgit receive-pack";
if (getRefLogIdent() != null)
lockMsg += " from " + getRefLogIdent().toExternalString();

parser = ins.newPackParser(rawIn);
parser.setAllowThin(true);
parser.setNeedNewObjectIds(checkReferencedIsReachable);
parser.setNeedBaseObjectIds(checkReferencedIsReachable);
parser.setObjectChecking(isCheckReceivedObjects());
parser.setLockMessage(lockMsg);
packLock = parser.parse(NullProgressMonitor.INSTANCE);
ins.flush();
} finally {
ins.release();
}

if (timeoutIn != null)
timeoutIn.setTimeout(timeout * 1000);
@@ -805,10 +812,10 @@ public class ReceivePack {
ObjectIdSubclassMap<ObjectId> providedObjects = null;

if (checkReferencedIsReachable) {
baseObjects = ip.getBaseObjectIds();
providedObjects = ip.getNewObjectIds();
baseObjects = parser.getBaseObjectIds();
providedObjects = parser.getNewObjectIds();
}
ip = null;
parser = null;

final ObjectWalk ow = new ObjectWalk(db);
ow.setRetainBody(false);

+ 10
- 11
org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java View File

@@ -856,17 +856,16 @@ class WalkFetchConnection extends BaseFetchConnection {
}

void downloadPack(final ProgressMonitor monitor) throws IOException {
final WalkRemoteObjectDatabase.FileStream s;
final IndexPack ip;

s = connection.open("pack/" + packName);
ip = IndexPack.create(local, s.in);
ip.setFixThin(false);
ip.setObjectChecker(objCheck);
ip.index(monitor);
final PackLock keep = ip.renameAndOpenPack(lockMessage);
if (keep != null)
packLocks.add(keep);
String name = "pack/" + packName;
WalkRemoteObjectDatabase.FileStream s = connection.open(name);
PackParser parser = inserter.newPackParser(s.in);
parser.setAllowThin(false);
parser.setObjectChecker(objCheck);
parser.setLockMessage(lockMessage);
PackLock lock = parser.parse(monitor);
if (lock != null)
packLocks.add(lock);
inserter.flush();
}
}
}

Loading…
Cancel
Save