From 6a822f0ebfd8b37bffbb680fa05b8783701ae15e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 23 Jun 2010 10:13:55 -0700 Subject: Remove RepositoryConfig and use FileBasedConfig instead Change the Repository API to use straight-up FileBasedConfig. This lets us remove the subclass RepositoryConfig and stop having a specialized configuration type for repository, letting us instead focus the config type heirarchy on type-of-storage rather than use. Change-Id: I7236800e8090624453a89cb0c7a9a632702691c6 Signed-off-by: Shawn O. Pearce --- .../tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java | 4 ++-- .../tst/org/eclipse/jgit/http/test/HookMessageTest.java | 4 ++-- .../tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'org.eclipse.jgit.http.test') diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java index 47d7806a1e..d8268bf93f 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java @@ -59,10 +59,10 @@ import org.eclipse.jgit.http.server.resolver.ServiceNotEnabledException; import org.eclipse.jgit.http.test.util.HttpTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileBasedConfig; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.RepositoryConfig; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.ReceivePack; @@ -114,7 +114,7 @@ public class AdvertiseErrorTest extends HttpTestCase { remoteRepository = src.getRepository(); remoteURI = toURIish(app, srcName); - RepositoryConfig cfg = remoteRepository.getConfig(); + FileBasedConfig cfg = remoteRepository.getConfig(); cfg.setBoolean("http", null, "receivepack", true); cfg.save(); } diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java index 224ea05c18..b98171ebd2 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java @@ -61,10 +61,10 @@ import org.eclipse.jgit.http.test.util.AccessEvent; import org.eclipse.jgit.http.test.util.HttpTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileBasedConfig; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.RepositoryConfig; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.PreReceiveHook; @@ -124,7 +124,7 @@ public class HookMessageTest extends HttpTestCase { remoteRepository = src.getRepository(); remoteURI = toURIish(app, srcName); - RepositoryConfig cfg = remoteRepository.getConfig(); + FileBasedConfig cfg = remoteRepository.getConfig(); cfg.setBoolean("http", null, "receivepack", true); cfg.save(); } diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java index f7b3bdb203..74dd8af8e2 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java @@ -76,12 +76,12 @@ import org.eclipse.jgit.http.test.util.HttpTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRng; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileBasedConfig; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.ReflogReader; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.RepositoryConfig; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.FetchConnection; @@ -547,7 +547,7 @@ public class SmartClientSmartServerTest extends HttpTestCase { } private void enableReceivePack() throws IOException { - final RepositoryConfig cfg = remoteRepository.getConfig(); + final FileBasedConfig cfg = remoteRepository.getConfig(); cfg.setBoolean("http", null, "receivepack", true); cfg.save(); } -- cgit v1.2.3 From 89d4a7377fc578524dd87289afc367287fd9e653 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Jun 2010 15:45:51 -0700 Subject: Use FileRepository where we assume other file semantics When the surrounding code is already heavily based upon the assumption that we have a FileRepository (e.g. because it created that type of repository) keep the type around and use it directly. This permits us to continue to do things like save the configuration file. Change-Id: Ib783f0f6a11acd6aa305c16d61ccc368b46beecc Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/http/test/AdvertiseErrorTest.java | 5 +- .../eclipse/jgit/http/test/HookMessageTest.java | 5 +- .../eclipse/jgit/http/test/HttpClientTests.java | 17 ++-- .../jgit/http/test/SmartClientSmartServerTest.java | 9 +- .../eclipse/jgit/http/test/util/HttpTestCase.java | 6 +- .../jgit/junit/LocalDiskRepositoryTestCase.java | 8 +- .../src/org/eclipse/jgit/junit/TestRepository.java | 102 +++++++++++---------- .../src/org/eclipse/jgit/pgm/Clone.java | 17 ++-- .../jgit/lib/RepositorySetupWorkDirTest.java | 4 +- .../org/eclipse/jgit/lib/RepositoryTestCase.java | 2 +- .../tst/org/eclipse/jgit/lib/T0003_Basic.java | 6 +- .../org/eclipse/jgit/revwalk/RevWalkTestCase.java | 5 +- .../jgit/transport/ReceivePackRefFilterTest.java | 6 +- 13 files changed, 105 insertions(+), 87 deletions(-) (limited to 'org.eclipse.jgit.http.test') diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java index d8268bf93f..0f6299cd51 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java @@ -60,6 +60,7 @@ import org.eclipse.jgit.http.test.util.HttpTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileBasedConfig; +import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; @@ -71,14 +72,14 @@ import org.eclipse.jgit.transport.Transport; import org.eclipse.jgit.transport.URIish; public class AdvertiseErrorTest extends HttpTestCase { - private Repository remoteRepository; + private FileRepository remoteRepository; private URIish remoteURI; protected void setUp() throws Exception { super.setUp(); - final TestRepository src = createTestRepository(); + final TestRepository src = createTestRepository(); final String srcName = src.getRepository().getDirectory().getName(); ServletContextHandler app = server.addContext("/git"); diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java index b98171ebd2..a2292e6c0e 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java @@ -62,6 +62,7 @@ import org.eclipse.jgit.http.test.util.HttpTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileBasedConfig; +import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; @@ -76,14 +77,14 @@ import org.eclipse.jgit.transport.Transport; import org.eclipse.jgit.transport.URIish; public class HookMessageTest extends HttpTestCase { - private Repository remoteRepository; + private FileRepository remoteRepository; private URIish remoteURI; protected void setUp() throws Exception { super.setUp(); - final TestRepository src = createTestRepository(); + final TestRepository src = createTestRepository(); final String srcName = src.getRepository().getDirectory().getName(); ServletContextHandler app = server.addContext("/git"); diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java index 729466df3b..daf169eb1c 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java @@ -62,6 +62,7 @@ import org.eclipse.jgit.http.test.util.AccessEvent; import org.eclipse.jgit.http.test.util.HttpTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; @@ -71,7 +72,7 @@ import org.eclipse.jgit.transport.Transport; import org.eclipse.jgit.transport.URIish; public class HttpClientTests extends HttpTestCase { - private TestRepository remoteRepository; + private TestRepository remoteRepository; private URIish dumbAuthNoneURI; @@ -95,7 +96,7 @@ public class HttpClientTests extends HttpTestCase { server.setUp(); - final String srcName = nameOf(remoteRepository); + final String srcName = nameOf(remoteRepository.getRepository()); dumbAuthNoneURI = toURIish(dNone, srcName); dumbAuthBasicURI = toURIish(dBasic, srcName); @@ -119,10 +120,10 @@ public class HttpClientTests extends HttpTestCase { public Repository open(HttpServletRequest req, String name) throws RepositoryNotFoundException, ServiceNotEnabledException { - if (!name.equals(nameOf(remoteRepository))) + final FileRepository db = remoteRepository.getRepository(); + if (!name.equals(nameOf(db))) throw new RepositoryNotFoundException(name); - final Repository db = remoteRepository.getRepository(); db.incrementOpen(); return db; } @@ -133,8 +134,8 @@ public class HttpClientTests extends HttpTestCase { return ctx; } - private static String nameOf(final TestRepository db) { - return db.getRepository().getDirectory().getName(); + private static String nameOf(final FileRepository db) { + return db.getDirectory().getName(); } public void testRepositoryNotFound_Dumb() throws Exception { @@ -198,7 +199,7 @@ public class HttpClientTests extends HttpTestCase { } public void testListRemote_Dumb_NoHEAD() throws Exception { - Repository src = remoteRepository.getRepository(); + FileRepository src = remoteRepository.getRepository(); File headref = new File(src.getDirectory(), Constants.HEAD); assertTrue("HEAD used to be present", headref.delete()); assertFalse("HEAD is gone", headref.exists()); @@ -306,7 +307,7 @@ public class HttpClientTests extends HttpTestCase { } public void testListRemote_Smart_UploadPackDisabled() throws Exception { - Repository src = remoteRepository.getRepository(); + FileRepository src = remoteRepository.getRepository(); src.getConfig().setBoolean("http", null, "uploadpack", false); src.getConfig().save(); diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java index 74dd8af8e2..867af2bf67 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java @@ -77,6 +77,7 @@ import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRng; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileBasedConfig; +import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; @@ -94,7 +95,7 @@ import org.eclipse.jgit.transport.URIish; public class SmartClientSmartServerTest extends HttpTestCase { private static final String HDR_TRANSFER_ENCODING = "Transfer-Encoding"; - private Repository remoteRepository; + private FileRepository remoteRepository; private URIish remoteURI; @@ -107,7 +108,7 @@ public class SmartClientSmartServerTest extends HttpTestCase { protected void setUp() throws Exception { super.setUp(); - final TestRepository src = createTestRepository(); + final TestRepository src = createTestRepository(); final String srcName = src.getRepository().getDirectory().getName(); ServletContextHandler app = server.addContext("/git"); @@ -489,10 +490,10 @@ public class SmartClientSmartServerTest extends HttpTestCase { } public void testPush_ChunkedEncoding() throws Exception { - final TestRepository src = createTestRepository(); + final TestRepository src = createTestRepository(); final RevBlob Q_bin = src.blob(new TestRng("Q").nextBytes(128 * 1024)); final RevCommit Q = src.commit().add("Q", Q_bin).create(); - final Repository db = src.getRepository(); + final FileRepository db = src.getRepository(); final String dstName = Constants.R_HEADS + "new.branch"; Transport t; diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java index e259757615..a25ca56787 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java @@ -57,6 +57,7 @@ import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; @@ -82,8 +83,9 @@ public abstract class HttpTestCase extends LocalDiskRepositoryTestCase { super.tearDown(); } - protected TestRepository createTestRepository() throws Exception { - return new TestRepository(createBareRepository()); + protected TestRepository createTestRepository() + throws IOException { + return new TestRepository(createBareRepository()); } protected URIish toURIish(String path) throws URISyntaxException { diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java index 100b632ae3..6920e91886 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java @@ -260,7 +260,7 @@ public abstract class LocalDiskRepositoryTestCase extends TestCase { * @throws IOException * the repository could not be created in the temporary area */ - protected Repository createBareRepository() throws IOException { + protected FileRepository createBareRepository() throws IOException { return createRepository(true /* bare */); } @@ -271,7 +271,7 @@ public abstract class LocalDiskRepositoryTestCase extends TestCase { * @throws IOException * the repository could not be created in the temporary area */ - protected Repository createWorkRepository() throws IOException { + protected FileRepository createWorkRepository() throws IOException { return createRepository(false /* not bare */); } @@ -285,11 +285,11 @@ public abstract class LocalDiskRepositoryTestCase extends TestCase { * @throws IOException * the repository could not be created in the temporary area */ - private Repository createRepository(boolean bare) throws IOException { + private FileRepository createRepository(boolean bare) throws IOException { String uniqueId = System.currentTimeMillis() + "_" + (testCount++); String gitdirName = "test" + uniqueId + (bare ? "" : "/") + Constants.DOT_GIT; File gitdir = new File(trash, gitdirName).getCanonicalFile(); - Repository db = new FileRepository(gitdir); + FileRepository db = new FileRepository(gitdir); assertFalse(gitdir.exists()); db.create(); diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java index 59504aa780..279762ac67 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java @@ -73,10 +73,10 @@ import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Commit; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.LockFile; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectChecker; -import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectDirectory; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectWriter; @@ -99,8 +99,13 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; -/** Wrapper to make creating test data easier. */ -public class TestRepository { +/** + * Wrapper to make creating test data easier. + * + * @param + * type of Repository the test data is stored on. + */ +public class TestRepository { private static final PersonIdent author; private static final PersonIdent committer; @@ -119,7 +124,7 @@ public class TestRepository { committer = new PersonIdent(cn, ce, now, tz); } - private final Repository db; + private final R db; private final RevWalk pool; @@ -132,9 +137,9 @@ public class TestRepository { * * @param db * the test repository to write into. - * @throws Exception + * @throws IOException */ - public TestRepository(Repository db) throws Exception { + public TestRepository(R db) throws IOException { this(db, new RevWalk(db)); } @@ -145,9 +150,9 @@ public class TestRepository { * the test repository to write into. * @param rw * the RevObject pool to use for object lookup. - * @throws Exception + * @throws IOException */ - public TestRepository(Repository db, RevWalk rw) throws Exception { + public TestRepository(R db, RevWalk rw) throws IOException { this.db = db; this.pool = rw; this.writer = new ObjectWriter(db); @@ -155,7 +160,7 @@ public class TestRepository { } /** @return the repository this helper class operates against. */ - public Repository getRepository() { + public R getRepository() { return db; } @@ -443,25 +448,27 @@ public class TestRepository { * @throws Exception */ public void updateServerInfo() throws Exception { - final ObjectDatabase odb = db.getObjectDatabase(); - if (odb instanceof ObjectDirectory) { - RefWriter rw = new RefWriter(db.getAllRefs().values()) { + if (db instanceof FileRepository) { + final FileRepository fr = (FileRepository) db; + RefWriter rw = new RefWriter(fr.getAllRefs().values()) { @Override protected void writeFile(final String name, final byte[] bin) throws IOException { - TestRepository.this.writeFile(name, bin); + File path = new File(fr.getDirectory(), name); + TestRepository.this.writeFile(path, bin); } }; rw.writePackedRefs(); rw.writeInfoRefs(); final StringBuilder w = new StringBuilder(); - for (PackFile p : ((ObjectDirectory) odb).getPacks()) { + for (PackFile p : fr.getObjectDatabase().getPacks()) { w.append("P "); w.append(p.getPackFile().getName()); w.append('\n'); } - writeFile("objects/info/packs", Constants.encodeASCII(w.toString())); + writeFile(new File(new File(fr.getObjectDatabase().getDirectory(), + "info"), "packs"), Constants.encodeASCII(w.toString())); } } @@ -563,38 +570,40 @@ public class TestRepository { * @throws Exception */ public void packAndPrune() throws Exception { - final ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase(); - final PackWriter pw = new PackWriter(db, NullProgressMonitor.INSTANCE); - - Set all = new HashSet(); - for (Ref r : db.getAllRefs().values()) - all.add(r.getObjectId()); - pw.preparePack(all, Collections. emptySet()); - - final ObjectId name = pw.computeName(); - OutputStream out; + if (db.getObjectDatabase() instanceof ObjectDirectory) { + ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase(); + PackWriter pw = new PackWriter(db, NullProgressMonitor.INSTANCE); - final File pack = nameFor(odb, name, ".pack"); - out = new BufferedOutputStream(new FileOutputStream(pack)); - try { - pw.writePack(out); - } finally { - out.close(); - } - pack.setReadOnly(); + Set all = new HashSet(); + for (Ref r : db.getAllRefs().values()) + all.add(r.getObjectId()); + pw.preparePack(all, Collections. emptySet()); + + final ObjectId name = pw.computeName(); + OutputStream out; + + final File pack = nameFor(odb, name, ".pack"); + out = new BufferedOutputStream(new FileOutputStream(pack)); + try { + pw.writePack(out); + } finally { + out.close(); + } + pack.setReadOnly(); + + final File idx = nameFor(odb, name, ".idx"); + out = new BufferedOutputStream(new FileOutputStream(idx)); + try { + pw.writeIndex(out); + } finally { + out.close(); + } + idx.setReadOnly(); - final File idx = nameFor(odb, name, ".idx"); - out = new BufferedOutputStream(new FileOutputStream(idx)); - try { - pw.writeIndex(out); - } finally { - out.close(); + odb.openPack(pack, idx); + updateServerInfo(); + prunePacked(odb); } - idx.setReadOnly(); - - odb.openPack(pack, idx); - updateServerInfo(); - prunePacked(odb); } private void prunePacked(ObjectDirectory odb) { @@ -609,9 +618,8 @@ public class TestRepository { return new File(packdir, "pack-" + name.name() + t); } - private void writeFile(final String name, final byte[] bin) - throws IOException, ObjectWritingException { - final File p = new File(db.getDirectory(), name); + private void writeFile(final File p, final byte[] bin) throws IOException, + ObjectWritingException { final LockFile lck = new LockFile(p); if (!lck.lock()) throw new ObjectWritingException("Can't write " + p); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java index 260b8b8663..60663438fc 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java @@ -82,6 +82,8 @@ class Clone extends AbstractFetchCommand { @Argument(index = 1, metaVar = "metaVar_directory") private String localName; + private FileRepository dst; + @Override protected final boolean requiresRepository() { return false; @@ -103,10 +105,11 @@ class Clone extends AbstractFetchCommand { if (gitdir == null) gitdir = new File(localName, Constants.DOT_GIT); - db = new FileRepository(gitdir); - db.create(); - db.getConfig().setBoolean("core", null, "bare", false); - db.getConfig().save(); + dst = new FileRepository(gitdir); + dst.create(); + dst.getConfig().setBoolean("core", null, "bare", false); + dst.getConfig().save(); + db = dst; out.format(CLIText.get().initializedEmptyGitRepositoryIn, gitdir.getAbsolutePath()); out.println(); @@ -120,13 +123,13 @@ class Clone extends AbstractFetchCommand { private void saveRemote(final URIish uri) throws URISyntaxException, IOException { - final RemoteConfig rc = new RemoteConfig(db.getConfig(), remoteName); + final RemoteConfig rc = new RemoteConfig(dst.getConfig(), remoteName); rc.addURI(uri); rc.addFetchRefSpec(new RefSpec().setForceUpdate(true) .setSourceDestination(Constants.R_HEADS + "*", Constants.R_REMOTES + remoteName + "/*")); - rc.update(db.getConfig()); - db.getConfig().save(); + rc.update(dst.getConfig()); + dst.getConfig().save(); } private FetchResult runFetch() throws NotSupportedException, diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java index e3617305f1..070713d7b3 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java @@ -177,14 +177,14 @@ public class RepositorySetupWorkDirTest extends LocalDiskRepositoryTestCase { } private void setBare(File gitDir, boolean bare) throws IOException { - Repository repo = new FileRepository(gitDir, null); + FileRepository repo = new FileRepository(gitDir, null); repo.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_BARE, bare); repo.getConfig().save(); } private void setWorkTree(File gitDir, File workTree) throws IOException { - Repository repo = new FileRepository(gitDir, null); + FileRepository repo = new FileRepository(gitDir, null); repo.getConfig() .setString(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_WORKTREE, diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java index c5c6d998bb..64df65b4c2 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java @@ -102,7 +102,7 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase { } /** Test repository, initialized for this test case. */ - protected Repository db; + protected FileRepository db; /** Working directory of {@link #db}. */ protected File trash; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java index b44b9038e9..4e87fd624c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java @@ -159,7 +159,7 @@ public class T0003_Basic extends SampleDataRepositoryTestCase { File repo1Parent = new File(trash.getParentFile(), "r1"); File workdir = new File(trash.getParentFile(), "rw"); workdir.mkdir(); - Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); + FileRepository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); repo1initial.create(); repo1initial.getConfig().setString("core", null, "worktree", workdir.getAbsolutePath()); @@ -184,7 +184,7 @@ public class T0003_Basic extends SampleDataRepositoryTestCase { File repo1Parent = new File(trash.getParentFile(), "r1"); File workdir = new File(trash.getParentFile(), "rw"); workdir.mkdir(); - Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); + FileRepository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); repo1initial.create(); repo1initial.getConfig() .setString("core", null, "worktree", "../../rw"); @@ -321,7 +321,7 @@ public class T0003_Basic extends SampleDataRepositoryTestCase { } public void test007_Open() throws IOException { - final Repository db2 = new FileRepository(db.getDirectory()); + final FileRepository db2 = new FileRepository(db.getDirectory()); assertEquals(db.getDirectory(), db2.getDirectory()); assertEquals(db.getObjectsDirectory(), db2.getObjectsDirectory()); assertNotSame(db.getConfig(), db2.getConfig()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java index 64052323f1..9473fe6318 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java @@ -47,18 +47,19 @@ import java.util.Date; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryTestCase; /** Support for tests of the {@link RevWalk} class. */ public abstract class RevWalkTestCase extends RepositoryTestCase { - private TestRepository util; + private TestRepository util; protected RevWalk rw; @Override public void setUp() throws Exception { super.setUp(); - util = new TestRepository(db, createRevWalk()); + util = new TestRepository(db, createRevWalk()); rw = util.getRevWalk(); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java index 40c719f699..b87cc7a99c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java @@ -292,7 +292,7 @@ public class ReceivePackRefFilterTest extends LocalDiskRepositoryTestCase { public void testUsingHiddenCommonBlobFails() throws Exception { // Try to use the 'b' blob that is hidden. // - TestRepository s = new TestRepository(src); + TestRepository s = new TestRepository(src); RevCommit N = s.commit().parent(B).add("q", s.blob("b")).create(); // But don't include it in the pack. @@ -333,7 +333,7 @@ public class ReceivePackRefFilterTest extends LocalDiskRepositoryTestCase { public void testUsingUnknownBlobFails() throws Exception { // Try to use the 'n' blob that is not on the server. // - TestRepository s = new TestRepository(src); + TestRepository s = new TestRepository(src); RevBlob n = s.blob("n"); RevCommit N = s.commit().parent(B).add("q", n).create(); @@ -373,7 +373,7 @@ public class ReceivePackRefFilterTest extends LocalDiskRepositoryTestCase { } public void testUsingUnknownTreeFails() throws Exception { - TestRepository s = new TestRepository(src); + TestRepository s = new TestRepository(src); RevCommit N = s.commit().parent(B).add("q", s.blob("a")).create(); RevTree t = s.parseBody(N).getTree(); -- cgit v1.2.3 From ad5238dc67aa5922c425e6bc829e1152c2e20439 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 26 Jun 2010 16:56:55 -0700 Subject: Move FileRepository to storage.file.FileRepository This move isolates all of the local file specific implementation code into a single package, where their package-private methods and support classes are properly hidden away from the rest of the core library. Because of the sheer number of files impacted, I have limited this change to only the renames and the updated imports. Change-Id: Icca4884e1a418f83f8b617d0c4c78b73d8a4bd17 Signed-off-by: Shawn O. Pearce --- org.eclipse.jgit.http.server/META-INF/MANIFEST.MF | 1 + .../eclipse/jgit/http/server/InfoPacksServlet.java | 4 +- .../eclipse/jgit/http/server/IsLocalFilter.java | 2 +- .../jgit/http/server/ObjectFileServlet.java | 2 +- org.eclipse.jgit.http.test/META-INF/MANIFEST.MF | 1 + .../eclipse/jgit/http/test/AdvertiseErrorTest.java | 4 +- .../eclipse/jgit/http/test/HookMessageTest.java | 4 +- .../eclipse/jgit/http/test/HttpClientTests.java | 2 +- .../jgit/http/test/SmartClientSmartServerTest.java | 6 +- .../eclipse/jgit/http/test/util/HttpTestCase.java | 2 +- org.eclipse.jgit.iplog/META-INF/MANIFEST.MF | 1 + .../src/org/eclipse/jgit/iplog/IpLogMeta.java | 4 +- org.eclipse.jgit.junit/META-INF/MANIFEST.MF | 1 + .../jgit/junit/LocalDiskRepositoryTestCase.java | 8 +- .../org/eclipse/jgit/junit/MockSystemReader.java | 2 +- .../src/org/eclipse/jgit/junit/TestRepository.java | 10 +- org.eclipse.jgit.pgm/META-INF/MANIFEST.MF | 1 + .../src/org/eclipse/jgit/pgm/Clone.java | 2 +- .../src/org/eclipse/jgit/pgm/Init.java | 2 +- .../eclipse/jgit/pgm/debug/RebuildCommitGraph.java | 2 +- .../src/org/eclipse/jgit/pgm/eclipse/Iplog.java | 2 +- org.eclipse.jgit.test/META-INF/MANIFEST.MF | 1 + .../org/eclipse/jgit/lib/ConcurrentRepackTest.java | 266 ----- .../org/eclipse/jgit/lib/PackIndexTestCase.java | 168 ---- .../tst/org/eclipse/jgit/lib/PackIndexV1Test.java | 82 -- .../tst/org/eclipse/jgit/lib/PackIndexV2Test.java | 93 -- .../org/eclipse/jgit/lib/PackReverseIndexTest.java | 121 --- .../tst/org/eclipse/jgit/lib/PackWriterTest.java | 530 ---------- .../tst/org/eclipse/jgit/lib/RefDirectoryTest.java | 1024 ------------------- .../tst/org/eclipse/jgit/lib/RefUpdateTest.java | 840 ---------------- .../tst/org/eclipse/jgit/lib/ReflogReaderTest.java | 198 ---- .../jgit/lib/RepositorySetupWorkDirTest.java | 208 ---- .../org/eclipse/jgit/lib/RepositoryTestCase.java | 1 + .../tst/org/eclipse/jgit/lib/T0003_Basic.java | 754 -------------- .../tst/org/eclipse/jgit/lib/T0004_PackReader.java | 86 -- .../org/eclipse/jgit/lib/WindowCacheGetTest.java | 149 --- .../jgit/lib/WindowCacheReconfigureTest.java | 125 --- .../tst/org/eclipse/jgit/lib/XInputStream.java | 88 -- .../jgit/storage/file/ConcurrentRepackTest.java | 275 ++++++ .../jgit/storage/file/PackIndexTestCase.java | 169 ++++ .../eclipse/jgit/storage/file/PackIndexV1Test.java | 83 ++ .../eclipse/jgit/storage/file/PackIndexV2Test.java | 94 ++ .../jgit/storage/file/PackReverseIndexTest.java | 122 +++ .../eclipse/jgit/storage/file/PackWriterTest.java | 534 ++++++++++ .../jgit/storage/file/RefDirectoryTest.java | 1028 ++++++++++++++++++++ .../eclipse/jgit/storage/file/RefUpdateTest.java | 853 ++++++++++++++++ .../jgit/storage/file/ReflogReaderTest.java | 201 ++++ .../storage/file/RepositorySetupWorkDirTest.java | 211 ++++ .../org/eclipse/jgit/storage/file/T0003_Basic.java | 769 +++++++++++++++ .../jgit/storage/file/T0004_PackReader.java | 90 ++ .../jgit/storage/file/WindowCacheGetTest.java | 153 +++ .../storage/file/WindowCacheReconfigureTest.java | 127 +++ .../eclipse/jgit/storage/file/XInputStream.java | 88 ++ .../org/eclipse/jgit/transport/IndexPackTest.java | 2 +- .../jgit/transport/ReceivePackRefFilterTest.java | 2 +- org.eclipse.jgit/META-INF/MANIFEST.MF | 1 + .../src/org/eclipse/jgit/dircache/DirCache.java | 2 +- .../eclipse/jgit/lib/BaseRepositoryBuilder.java | 3 + .../src/org/eclipse/jgit/lib/ByteArrayWindow.java | 101 -- .../src/org/eclipse/jgit/lib/ByteBufferWindow.java | 92 -- .../src/org/eclipse/jgit/lib/ByteWindow.java | 175 ---- .../eclipse/jgit/lib/CachedObjectDirectory.java | 184 ---- .../jgit/lib/DeltaOfsPackedObjectLoader.java | 82 -- .../eclipse/jgit/lib/DeltaPackedObjectLoader.java | 118 --- .../jgit/lib/DeltaRefPackedObjectLoader.java | 80 -- .../src/org/eclipse/jgit/lib/FileBasedConfig.java | 163 ---- .../org/eclipse/jgit/lib/FileObjectDatabase.java | 207 ---- .../src/org/eclipse/jgit/lib/FileRepository.java | 310 ------ .../eclipse/jgit/lib/FileRepositoryBuilder.java | 88 -- .../jgit/lib/LocalObjectRepresentation.java | 80 -- .../org/eclipse/jgit/lib/LocalObjectToPack.java | 66 -- .../src/org/eclipse/jgit/lib/LockFile.java | 448 --------- .../src/org/eclipse/jgit/lib/ObjectDirectory.java | 621 ------------ .../eclipse/jgit/lib/ObjectDirectoryInserter.java | 177 ---- .../src/org/eclipse/jgit/lib/PackFile.java | 695 ------------- .../src/org/eclipse/jgit/lib/PackIndex.java | 315 ------ .../src/org/eclipse/jgit/lib/PackIndexV1.java | 202 ---- .../src/org/eclipse/jgit/lib/PackIndexV2.java | 279 ------ .../src/org/eclipse/jgit/lib/PackIndexWriter.java | 275 ------ .../org/eclipse/jgit/lib/PackIndexWriterV1.java | 85 -- .../org/eclipse/jgit/lib/PackIndexWriterV2.java | 107 -- .../src/org/eclipse/jgit/lib/PackLock.java | 90 -- .../src/org/eclipse/jgit/lib/PackReverseIndex.java | 193 ---- .../src/org/eclipse/jgit/lib/PackWriter.java | 1 + .../org/eclipse/jgit/lib/PackedObjectLoader.java | 124 --- .../src/org/eclipse/jgit/lib/RefDirectory.java | 1006 ------------------- .../org/eclipse/jgit/lib/RefDirectoryRename.java | 225 ----- .../org/eclipse/jgit/lib/RefDirectoryUpdate.java | 156 --- .../src/org/eclipse/jgit/lib/RefWriter.java | 1 + .../src/org/eclipse/jgit/lib/ReflogReader.java | 184 ---- .../src/org/eclipse/jgit/lib/Repository.java | 1 + .../org/eclipse/jgit/lib/RepositoryBuilder.java | 2 + .../src/org/eclipse/jgit/lib/RepositoryCache.java | 1 + .../org/eclipse/jgit/lib/UnpackedObjectCache.java | 195 ---- .../org/eclipse/jgit/lib/UnpackedObjectLoader.java | 223 ----- .../eclipse/jgit/lib/WholePackedObjectLoader.java | 110 --- .../src/org/eclipse/jgit/lib/WindowCache.java | 624 ------------ .../org/eclipse/jgit/lib/WindowCacheConfig.java | 176 ---- .../src/org/eclipse/jgit/lib/WindowCursor.java | 213 ---- .../eclipse/jgit/storage/file/ByteArrayWindow.java | 101 ++ .../jgit/storage/file/ByteBufferWindow.java | 92 ++ .../org/eclipse/jgit/storage/file/ByteWindow.java | 175 ++++ .../jgit/storage/file/CachedObjectDirectory.java | 194 ++++ .../storage/file/DeltaOfsPackedObjectLoader.java | 84 ++ .../jgit/storage/file/DeltaPackedObjectLoader.java | 120 +++ .../storage/file/DeltaRefPackedObjectLoader.java | 82 ++ .../eclipse/jgit/storage/file/FileBasedConfig.java | 165 ++++ .../jgit/storage/file/FileObjectDatabase.java | 214 ++++ .../eclipse/jgit/storage/file/FileRepository.java | 327 +++++++ .../jgit/storage/file/FileRepositoryBuilder.java | 90 ++ .../storage/file/LocalObjectRepresentation.java | 83 ++ .../jgit/storage/file/LocalObjectToPack.java | 68 ++ .../org/eclipse/jgit/storage/file/LockFile.java | 450 +++++++++ .../eclipse/jgit/storage/file/ObjectDirectory.java | 630 ++++++++++++ .../jgit/storage/file/ObjectDirectoryInserter.java | 182 ++++ .../org/eclipse/jgit/storage/file/PackFile.java | 700 +++++++++++++ .../org/eclipse/jgit/storage/file/PackIndex.java | 318 ++++++ .../org/eclipse/jgit/storage/file/PackIndexV1.java | 205 ++++ .../org/eclipse/jgit/storage/file/PackIndexV2.java | 282 ++++++ .../eclipse/jgit/storage/file/PackIndexWriter.java | 277 ++++++ .../jgit/storage/file/PackIndexWriterV1.java | 85 ++ .../jgit/storage/file/PackIndexWriterV2.java | 107 ++ .../org/eclipse/jgit/storage/file/PackLock.java | 92 ++ .../jgit/storage/file/PackReverseIndex.java | 194 ++++ .../jgit/storage/file/PackedObjectLoader.java | 127 +++ .../eclipse/jgit/storage/file/RefDirectory.java | 1018 +++++++++++++++++++ .../jgit/storage/file/RefDirectoryRename.java | 229 +++++ .../jgit/storage/file/RefDirectoryUpdate.java | 160 +++ .../eclipse/jgit/storage/file/ReflogReader.java | 188 ++++ .../jgit/storage/file/UnpackedObjectCache.java | 195 ++++ .../jgit/storage/file/UnpackedObjectLoader.java | 227 +++++ .../jgit/storage/file/WholePackedObjectLoader.java | 112 +++ .../org/eclipse/jgit/storage/file/WindowCache.java | 624 ++++++++++++ .../jgit/storage/file/WindowCacheConfig.java | 178 ++++ .../eclipse/jgit/storage/file/WindowCursor.java | 222 +++++ .../jgit/transport/BasePackFetchConnection.java | 2 +- .../jgit/transport/BundleFetchConnection.java | 2 +- .../eclipse/jgit/transport/FetchConnection.java | 2 +- .../org/eclipse/jgit/transport/FetchProcess.java | 4 +- .../src/org/eclipse/jgit/transport/IndexPack.java | 4 +- .../org/eclipse/jgit/transport/ReceivePack.java | 2 +- .../org/eclipse/jgit/transport/TransportHttp.java | 2 +- .../org/eclipse/jgit/transport/TransportLocal.java | 2 +- .../jgit/transport/WalkFetchConnection.java | 8 +- .../jgit/transport/WalkRemoteObjectDatabase.java | 2 +- .../src/org/eclipse/jgit/util/SystemReader.java | 2 +- 146 files changed, 13459 insertions(+), 13249 deletions(-) delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexTestCase.java delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV1Test.java delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV2Test.java delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackReverseIndexTest.java delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackWriterTest.java delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogReaderTest.java delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheGetTest.java delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheReconfigureTest.java delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/XInputStream.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexTestCase.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV1Test.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV2Test.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackReverseIndexTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefDirectoryTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefUpdateTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ReflogReaderTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0003_Basic.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheReconfigureTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/XInputStream.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepositoryBuilder.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectRepresentation.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepository.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV1.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackLock.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryUpdate.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ReflogReader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectCache.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCache.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java (limited to 'org.eclipse.jgit.http.test') diff --git a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF index b9aeef5195..edaa1edd19 100644 --- a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF @@ -17,6 +17,7 @@ Import-Package: javax.servlet;version="[2.5.0,3.0.0)", org.eclipse.jgit.lib;version="[0.9.0,0.10.0)", org.eclipse.jgit.nls;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk;version="[0.9.0,0.10.0)", + org.eclipse.jgit.storage.file;version="[0.9.0,0.10.0)", org.eclipse.jgit.transport;version="[0.9.0,0.10.0)", org.eclipse.jgit.util;version="[0.9.0,0.10.0)", org.eclipse.jgit.util.io;version="[0.9.0,0.10.0)" diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java index dff1e8252e..d217fe1c35 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java @@ -53,8 +53,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jgit.lib.ObjectDatabase; -import org.eclipse.jgit.lib.ObjectDirectory; -import org.eclipse.jgit.lib.PackFile; +import org.eclipse.jgit.storage.file.ObjectDirectory; +import org.eclipse.jgit.storage.file.PackFile; /** Sends the current list of pack files, sorted most recent first. */ class InfoPacksServlet extends HttpServlet { diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java index 34edf82792..019ec90bc4 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java @@ -56,8 +56,8 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jgit.lib.ObjectDirectory; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.ObjectDirectory; /** * Requires the target {@link Repository} to be available via local filesystem. diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java index 5d774a8248..84865121c0 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java @@ -60,8 +60,8 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jgit.lib.ObjectDirectory; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.ObjectDirectory; /** Sends any object from {@code GIT_DIR/objects/??/0 38}, or any pack file. */ abstract class ObjectFileServlet extends HttpServlet { diff --git a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF index 370bd40353..21dd60817d 100644 --- a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF @@ -29,5 +29,6 @@ Import-Package: javax.servlet;version="[2.5.0,3.0.0)", org.eclipse.jgit.junit;version="[0.9.0,0.10.0)", org.eclipse.jgit.lib;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk;version="[0.9.0,0.10.0)", + org.eclipse.jgit.storage.file;version="[0.9.0,0.10.0)", org.eclipse.jgit.transport;version="[0.9.0,0.10.0)", org.eclipse.jgit.util;version="[0.9.0,0.10.0)" diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java index 0f6299cd51..db4aa802e2 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java @@ -59,13 +59,13 @@ import org.eclipse.jgit.http.server.resolver.ServiceNotEnabledException; import org.eclipse.jgit.http.test.util.HttpTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileBasedConfig; -import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.transport.ReceivePack; import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.Transport; diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java index a2292e6c0e..18f8dc928b 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java @@ -61,13 +61,13 @@ import org.eclipse.jgit.http.test.util.AccessEvent; import org.eclipse.jgit.http.test.util.HttpTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileBasedConfig; -import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.transport.PreReceiveHook; import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.ReceiveCommand; diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java index daf169eb1c..4cc141bb41 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java @@ -62,11 +62,11 @@ import org.eclipse.jgit.http.test.util.AccessEvent; import org.eclipse.jgit.http.test.util.HttpTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.transport.FetchConnection; import org.eclipse.jgit.transport.Transport; import org.eclipse.jgit.transport.URIish; diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java index 867af2bf67..a7b51c6819 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java @@ -76,15 +76,15 @@ import org.eclipse.jgit.http.test.util.HttpTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRng; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileBasedConfig; -import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.ReflogReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.storage.file.FileRepository; +import org.eclipse.jgit.storage.file.ReflogReader; import org.eclipse.jgit.transport.FetchConnection; import org.eclipse.jgit.transport.HttpTransport; import org.eclipse.jgit.transport.RemoteRefUpdate; diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java index a25ca56787..313b6ad90b 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java @@ -57,11 +57,11 @@ import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.URIish; diff --git a/org.eclipse.jgit.iplog/META-INF/MANIFEST.MF b/org.eclipse.jgit.iplog/META-INF/MANIFEST.MF index 4ff6144004..31a19b5ca0 100644 --- a/org.eclipse.jgit.iplog/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.iplog/META-INF/MANIFEST.MF @@ -15,6 +15,7 @@ Import-Package: org.eclipse.jgit.diff;version="[0.9.0,0.10.0)", org.eclipse.jgit.revplot;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk.filter;version="[0.9.0,0.10.0)", + org.eclipse.jgit.storage.file;version="[0.9.0,0.10.0)", org.eclipse.jgit.transport;version="[0.9.0,0.10.0)", org.eclipse.jgit.treewalk;version="[0.9.0,0.10.0)", org.eclipse.jgit.treewalk.filter;version="[0.9.0,0.10.0)", diff --git a/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/IpLogMeta.java b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/IpLogMeta.java index 89695bdb8d..2799a4a30b 100644 --- a/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/IpLogMeta.java +++ b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/IpLogMeta.java @@ -58,9 +58,9 @@ import java.util.Set; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileBasedConfig; -import org.eclipse.jgit.lib.LockFile; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.storage.file.LockFile; /** * Manages the {@code .eclipse_iplog} file in a project. diff --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF index 051079b78e..ab24b5aaf9 100644 --- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF @@ -18,6 +18,7 @@ Import-Package: junit.framework;version="[3.8.2,4.0.0)", org.eclipse.jgit.revplot;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk.filter;version="[0.9.0,0.10.0)", + org.eclipse.jgit.storage.file;version="[0.9.0,0.10.0)", org.eclipse.jgit.transport;version="[0.9.0,0.10.0)", org.eclipse.jgit.treewalk;version="[0.9.0,0.10.0)", org.eclipse.jgit.treewalk.filter;version="[0.9.0,0.10.0)", diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java index f45e5f67a7..2b82d82d74 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java @@ -62,13 +62,13 @@ import junit.framework.TestCase; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileBasedConfig; -import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryCache; -import org.eclipse.jgit.lib.WindowCache; -import org.eclipse.jgit.lib.WindowCacheConfig; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.storage.file.FileRepository; +import org.eclipse.jgit.storage.file.WindowCache; +import org.eclipse.jgit.storage.file.WindowCacheConfig; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.SystemReader; diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java index c502fb6344..eb08417bc8 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java @@ -52,7 +52,7 @@ import java.util.TimeZone; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileBasedConfig; +import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.SystemReader; diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java index dbf4eaf4ce..ec34e3c3a8 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java @@ -73,14 +73,10 @@ import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Commit; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; -import org.eclipse.jgit.lib.FileRepository; -import org.eclipse.jgit.lib.LockFile; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectChecker; -import org.eclipse.jgit.lib.ObjectDirectory; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.PackFile; import org.eclipse.jgit.lib.PackWriter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; @@ -88,7 +84,6 @@ import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefWriter; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Tag; -import org.eclipse.jgit.lib.PackIndex.MutableEntry; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; @@ -96,6 +91,11 @@ import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.FileRepository; +import org.eclipse.jgit.storage.file.LockFile; +import org.eclipse.jgit.storage.file.ObjectDirectory; +import org.eclipse.jgit.storage.file.PackFile; +import org.eclipse.jgit.storage.file.PackIndex.MutableEntry; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF index 8628896780..0a4376921a 100644 --- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF @@ -17,6 +17,7 @@ Import-Package: org.eclipse.jgit.api;version="[0.9.0,0.10.0)", org.eclipse.jgit.revplot;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk.filter;version="[0.9.0,0.10.0)", + org.eclipse.jgit.storage.file;version="[0.9.0,0.10.0)", org.eclipse.jgit.transport;version="[0.9.0,0.10.0)", org.eclipse.jgit.treewalk;version="[0.9.0,0.10.0)", org.eclipse.jgit.treewalk.filter;version="[0.9.0,0.10.0)", diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java index cc59a6eb5a..22302bba81 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java @@ -57,7 +57,6 @@ import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.Commit; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.GitIndex; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefComparator; @@ -65,6 +64,7 @@ import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.TextProgressMonitor; import org.eclipse.jgit.lib.Tree; import org.eclipse.jgit.lib.WorkDirCheckout; +import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.transport.FetchResult; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteConfig; diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java index b2a9fbde04..c5a696a57f 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java @@ -50,7 +50,7 @@ import java.text.MessageFormat; import org.kohsuke.args4j.Option; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileRepository; +import org.eclipse.jgit.storage.file.FileRepository; @Command(common = true, usage = "usage_CreateAnEmptyGitRepository") class Init extends TextBuiltin { diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java index 1681dbc96e..8ba3e4b90a 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java @@ -62,7 +62,6 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.ObjectWritingException; import org.eclipse.jgit.lib.Commit; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.LockFile; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ObjectWriter; @@ -76,6 +75,7 @@ import org.eclipse.jgit.lib.Tree; import org.eclipse.jgit.pgm.CLIText; import org.eclipse.jgit.pgm.TextBuiltin; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.LockFile; /** * Recreates a repository from another one's commit graph. diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/eclipse/Iplog.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/eclipse/Iplog.java index e13bb1f136..a99e0abca2 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/eclipse/Iplog.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/eclipse/Iplog.java @@ -51,7 +51,6 @@ import java.text.MessageFormat; import org.eclipse.jgit.iplog.IpLogGenerator; import org.eclipse.jgit.iplog.SimpleCookieManager; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.LockFile; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.pgm.CLIText; import org.eclipse.jgit.pgm.Command; @@ -59,6 +58,7 @@ import org.eclipse.jgit.pgm.TextBuiltin; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.LockFile; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index 3b82cf7d1d..b88ccf3467 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -28,6 +28,7 @@ Import-Package: junit.framework;version="[3.8.2,4.0.0)", org.eclipse.jgit.revplot;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk.filter;version="[0.9.0,0.10.0)", + org.eclipse.jgit.storage.file;version="[0.9.0,0.10.0)", org.eclipse.jgit.transport;version="[0.9.0,0.10.0)", org.eclipse.jgit.treewalk;version="[0.9.0,0.10.0)", org.eclipse.jgit.treewalk.filter;version="[0.9.0,0.10.0)", diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java deleted file mode 100644 index e3a62faf23..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (C) 2009-2010, Google Inc. - * Copyright (C) 2009, Robin Rosenberg - * 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.lib; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Arrays; - -import org.eclipse.jgit.errors.IncorrectObjectTypeException; -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.revwalk.RevObject; -import org.eclipse.jgit.revwalk.RevWalk; - -public class ConcurrentRepackTest extends RepositoryTestCase { - public void setUp() throws Exception { - WindowCacheConfig windowCacheConfig = new WindowCacheConfig(); - windowCacheConfig.setPackedGitOpenFiles(1); - WindowCache.reconfigure(windowCacheConfig); - super.setUp(); - } - - protected void tearDown() throws Exception { - super.tearDown(); - WindowCacheConfig windowCacheConfig = new WindowCacheConfig(); - WindowCache.reconfigure(windowCacheConfig); - } - - public void testObjectInNewPack() throws IncorrectObjectTypeException, - IOException { - // Create a new object in a new pack, and test that it is present. - // - final Repository eden = createBareRepository(); - final RevObject o1 = writeBlob(eden, "o1"); - pack(eden, o1); - assertEquals(o1.name(), parse(o1).name()); - } - - public void testObjectMovedToNewPack1() - throws IncorrectObjectTypeException, IOException { - // Create an object and pack it. Then remove that pack and put the - // object into a different pack file, with some other object. We - // still should be able to access the objects. - // - final Repository eden = createBareRepository(); - final RevObject o1 = writeBlob(eden, "o1"); - final File[] out1 = pack(eden, o1); - assertEquals(o1.name(), parse(o1).name()); - - final RevObject o2 = writeBlob(eden, "o2"); - pack(eden, o2, o1); - - // Force close, and then delete, the old pack. - // - whackCache(); - delete(out1); - - // Now here is the interesting thing. Will git figure the new - // object exists in the new pack, and not the old one. - // - assertEquals(o2.name(), parse(o2).name()); - assertEquals(o1.name(), parse(o1).name()); - } - - public void testObjectMovedWithinPack() - throws IncorrectObjectTypeException, IOException { - // Create an object and pack it. - // - final Repository eden = createBareRepository(); - final RevObject o1 = writeBlob(eden, "o1"); - final File[] out1 = pack(eden, o1); - assertEquals(o1.name(), parse(o1).name()); - - // Force close the old pack. - // - whackCache(); - - // Now overwrite the old pack in place. This method of creating a - // different pack under the same file name is partially broken. We - // should also have a different file name because the list of objects - // within the pack has been modified. - // - final RevObject o2 = writeBlob(eden, "o2"); - final PackWriter pw = new PackWriter(eden, NullProgressMonitor.INSTANCE); - pw.addObject(o2); - pw.addObject(o1); - write(out1, pw); - - // Try the old name, then the new name. The old name should cause the - // pack to reload when it opens and the index and pack mismatch. - // - assertEquals(o1.name(), parse(o1).name()); - assertEquals(o2.name(), parse(o2).name()); - } - - public void testObjectMovedToNewPack2() - throws IncorrectObjectTypeException, IOException { - // Create an object and pack it. Then remove that pack and put the - // object into a different pack file, with some other object. We - // still should be able to access the objects. - // - final Repository eden = createBareRepository(); - final RevObject o1 = writeBlob(eden, "o1"); - final File[] out1 = pack(eden, o1); - assertEquals(o1.name(), parse(o1).name()); - - final ObjectLoader load1 = db.openBlob(o1); - assertNotNull(load1); - - final RevObject o2 = writeBlob(eden, "o2"); - pack(eden, o2, o1); - - // Force close, and then delete, the old pack. - // - whackCache(); - delete(out1); - - // Now here is the interesting thing... can the loader we made - // earlier still resolve the object, even though its underlying - // pack is gone, but the object still exists. - // - final ObjectLoader load2 = db.openBlob(o1); - assertNotNull(load2); - assertNotSame(load1, load2); - - final byte[] data2 = load2.getCachedBytes(); - final byte[] data1 = load1.getCachedBytes(); - assertNotNull(data2); - assertNotNull(data1); - assertNotSame(data1, data2); // cache should be per-pack, not per object - assertTrue(Arrays.equals(data1, data2)); - assertEquals(load2.getType(), load1.getType()); - } - - private static void whackCache() { - final WindowCacheConfig config = new WindowCacheConfig(); - config.setPackedGitOpenFiles(1); - WindowCache.reconfigure(config); - } - - private RevObject parse(final AnyObjectId id) - throws MissingObjectException, IOException { - return new RevWalk(db).parseAny(id); - } - - private File[] pack(final Repository src, final RevObject... list) - throws IOException { - final PackWriter pw = new PackWriter(src, NullProgressMonitor.INSTANCE); - for (final RevObject o : list) { - pw.addObject(o); - } - - final ObjectId name = pw.computeName(); - final File packFile = fullPackFileName(name, ".pack"); - final File idxFile = fullPackFileName(name, ".idx"); - final File[] files = new File[] { packFile, idxFile }; - write(files, pw); - return files; - } - - private static void write(final File[] files, final PackWriter pw) - throws IOException { - final long begin = files[0].getParentFile().lastModified(); - OutputStream out; - - out = new BufferedOutputStream(new FileOutputStream(files[0])); - try { - pw.writePack(out); - } finally { - out.close(); - } - - out = new BufferedOutputStream(new FileOutputStream(files[1])); - try { - pw.writeIndex(out); - } finally { - out.close(); - } - - touch(begin, files[0].getParentFile()); - } - - private static void delete(final File[] list) { - final long begin = list[0].getParentFile().lastModified(); - for (final File f : list) { - f.delete(); - assertFalse(f + " was removed", f.exists()); - } - touch(begin, list[0].getParentFile()); - } - - private static void touch(final long begin, final File dir) { - while (begin >= dir.lastModified()) { - try { - Thread.sleep(25); - } catch (InterruptedException ie) { - // - } - dir.setLastModified(System.currentTimeMillis()); - } - } - - private File fullPackFileName(final ObjectId name, final String suffix) { - final File packdir = new File(db.getObjectDatabase().getDirectory(), "pack"); - return new File(packdir, "pack-" + name.name() + suffix); - } - - private RevObject writeBlob(final Repository repo, final String data) - throws IOException { - final RevWalk revWalk = new RevWalk(repo); - final byte[] bytes = Constants.encode(data); - final ObjectWriter ow = new ObjectWriter(repo); - final ObjectId id = ow.writeBlob(bytes); - try { - parse(id); - fail("Object " + id.name() + " should not exist in test repository"); - } catch (MissingObjectException e) { - // Ok - } - return revWalk.lookupBlob(id); - } -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexTestCase.java deleted file mode 100644 index d1c5c5eccd..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexTestCase.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2008, Marek Zawirski - * 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.lib; - -import java.io.File; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.lib.PackIndex.MutableEntry; - -public abstract class PackIndexTestCase extends RepositoryTestCase { - - PackIndex smallIdx; - - PackIndex denseIdx; - - public void setUp() throws Exception { - super.setUp(); - smallIdx = PackIndex.open(getFileForPack34be9032()); - denseIdx = PackIndex.open(getFileForPackdf2982f28()); - } - - /** - * Return file with appropriate index version for prepared pack. - * - * @return file with index - */ - public abstract File getFileForPack34be9032(); - - /** - * Return file with appropriate index version for prepared pack. - * - * @return file with index - */ - public abstract File getFileForPackdf2982f28(); - - /** - * Verify CRC32 support. - * - * @throws MissingObjectException - * @throws UnsupportedOperationException - */ - public abstract void testCRC32() throws MissingObjectException, - UnsupportedOperationException; - - /** - * Test contracts of Iterator methods and this implementation remove() - * limitations. - */ - public void testIteratorMethodsContract() { - Iterator iter = smallIdx.iterator(); - while (iter.hasNext()) { - iter.next(); - } - - try { - iter.next(); - fail("next() unexpectedly returned element"); - } catch (NoSuchElementException x) { - // expected - } - - try { - iter.remove(); - fail("remove() shouldn't be implemented"); - } catch (UnsupportedOperationException x) { - // expected - } - } - - /** - * Test results of iterator comparing to content of well-known (prepared) - * small index. - */ - public void testIteratorReturnedValues1() { - Iterator iter = smallIdx.iterator(); - assertEquals("4b825dc642cb6eb9a060e54bf8d69288fbee4904", iter.next() - .name()); - assertEquals("540a36d136cf413e4b064c2b0e0a4db60f77feab", iter.next() - .name()); - assertEquals("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259", iter.next() - .name()); - assertEquals("6ff87c4664981e4397625791c8ea3bbb5f2279a3", iter.next() - .name()); - assertEquals("82c6b885ff600be425b4ea96dee75dca255b69e7", iter.next() - .name()); - assertEquals("902d5476fa249b7abc9d84c611577a81381f0327", iter.next() - .name()); - assertEquals("aabf2ffaec9b497f0950352b3e582d73035c2035", iter.next() - .name()); - assertEquals("c59759f143fb1fe21c197981df75a7ee00290799", iter.next() - .name()); - assertFalse(iter.hasNext()); - } - - /** - * Compare offset from iterator entries with output of findOffset() method. - */ - public void testCompareEntriesOffsetsWithFindOffsets() { - for (MutableEntry me : smallIdx) { - assertEquals(smallIdx.findOffset(me.toObjectId()), me.getOffset()); - } - for (MutableEntry me : denseIdx) { - assertEquals(denseIdx.findOffset(me.toObjectId()), me.getOffset()); - } - } - - /** - * Test partial results of iterator comparing to content of well-known - * (prepared) dense index, that may need multi-level indexing. - */ - public void testIteratorReturnedValues2() { - Iterator iter = denseIdx.iterator(); - while (!iter.next().name().equals( - "0a3d7772488b6b106fb62813c4d6d627918d9181")) { - // just iterating - } - assertEquals("1004d0d7ac26fbf63050a234c9b88a46075719d3", iter.next() - .name()); // same level-1 - assertEquals("10da5895682013006950e7da534b705252b03be6", iter.next() - .name()); // same level-1 - assertEquals("1203b03dc816ccbb67773f28b3c19318654b0bc8", iter.next() - .name()); - } - -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV1Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV1Test.java deleted file mode 100644 index f3082fb294..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV1Test.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2008, Imran M Yousuf - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2009, Matthias Sohn - * 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.lib; - -import java.io.File; - -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.util.JGitTestUtil; - -public class PackIndexV1Test extends PackIndexTestCase { - @Override - public File getFileForPack34be9032() { - return JGitTestUtil.getTestResourceFile( - "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idx"); - } - - @Override - public File getFileForPackdf2982f28() { - return JGitTestUtil.getTestResourceFile( - "pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.idx"); - } - - /** - * Verify CRC32 - V1 should not index anything. - * - * @throws MissingObjectException - */ - @Override - public void testCRC32() throws MissingObjectException { - assertFalse(smallIdx.hasCRC32Support()); - try { - smallIdx.findCRC32(ObjectId - .fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904")); - fail("index V1 shouldn't support CRC"); - } catch (UnsupportedOperationException x) { - // expected - } - } -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV2Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV2Test.java deleted file mode 100644 index c5669f9d24..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV2Test.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2008, Imran M Yousuf - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2009, Matthias Sohn - * 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.lib; - -import java.io.File; - -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.util.JGitTestUtil; - -public class PackIndexV2Test extends PackIndexTestCase { - @Override - public File getFileForPack34be9032() { - return JGitTestUtil.getTestResourceFile( - "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idxV2"); - } - - @Override - public File getFileForPackdf2982f28() { - return JGitTestUtil.getTestResourceFile( - "pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.idxV2"); - } - - /** - * Verify CRC32 indexing. - * - * @throws UnsupportedOperationException - * @throws MissingObjectException - */ - @Override - public void testCRC32() throws MissingObjectException, - UnsupportedOperationException { - assertTrue(smallIdx.hasCRC32Support()); - assertEquals(0x00000000C2B64258l, smallIdx.findCRC32(ObjectId - .fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904"))); - assertEquals(0x0000000072AD57C2l, smallIdx.findCRC32(ObjectId - .fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"))); - assertEquals(0x00000000FF10A479l, smallIdx.findCRC32(ObjectId - .fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"))); - assertEquals(0x0000000034B27DDCl, smallIdx.findCRC32(ObjectId - .fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3"))); - assertEquals(0x000000004743F1E4l, smallIdx.findCRC32(ObjectId - .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"))); - assertEquals(0x00000000640B358Bl, smallIdx.findCRC32(ObjectId - .fromString("902d5476fa249b7abc9d84c611577a81381f0327"))); - assertEquals(0x000000002A17CB5El, smallIdx.findCRC32(ObjectId - .fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"))); - assertEquals(0x000000000B3B5BA6l, smallIdx.findCRC32(ObjectId - .fromString("c59759f143fb1fe21c197981df75a7ee00290799"))); - } -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackReverseIndexTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackReverseIndexTest.java deleted file mode 100644 index 19b705813f..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackReverseIndexTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2008, Imran M Yousuf - * Copyright (C) 2008, Marek Zawirski - * 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.lib; - -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.lib.PackIndex.MutableEntry; -import org.eclipse.jgit.util.JGitTestUtil; - -public class PackReverseIndexTest extends RepositoryTestCase { - - private PackIndex idx; - - private PackReverseIndex reverseIdx; - - /** - * Set up tested class instance, test constructor by the way. - */ - public void setUp() throws Exception { - super.setUp(); - // index with both small (< 2^31) and big offsets - idx = PackIndex.open(JGitTestUtil.getTestResourceFile( - "pack-huge.idx")); - reverseIdx = new PackReverseIndex(idx); - } - - /** - * Test findObject() for all index entries. - */ - public void testFindObject() { - for (MutableEntry me : idx) - assertEquals(me.toObjectId(), reverseIdx.findObject(me.getOffset())); - } - - /** - * Test findObject() with illegal argument. - */ - public void testFindObjectWrongOffset() { - assertNull(reverseIdx.findObject(0)); - } - - /** - * Test findNextOffset() for all index entries. - * - * @throws CorruptObjectException - */ - public void testFindNextOffset() throws CorruptObjectException { - long offset = findFirstOffset(); - assertTrue(offset > 0); - for (int i = 0; i < idx.getObjectCount(); i++) { - long newOffset = reverseIdx.findNextOffset(offset, Long.MAX_VALUE); - assertTrue(newOffset > offset); - if (i == idx.getObjectCount() - 1) - assertEquals(newOffset, Long.MAX_VALUE); - else - assertEquals(newOffset, idx.findOffset(reverseIdx - .findObject(newOffset))); - offset = newOffset; - } - } - - /** - * Test findNextOffset() with wrong illegal argument as offset. - */ - public void testFindNextOffsetWrongOffset() { - try { - reverseIdx.findNextOffset(0, Long.MAX_VALUE); - fail("findNextOffset() should throw exception"); - } catch (CorruptObjectException x) { - // expected - } - } - - private long findFirstOffset() { - long min = Long.MAX_VALUE; - for (MutableEntry me : idx) - min = Math.min(min, me.getOffset()); - return min; - } -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackWriterTest.java deleted file mode 100644 index c291968b74..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackWriterTest.java +++ /dev/null @@ -1,530 +0,0 @@ -/* - * Copyright (C) 2008, Marek Zawirski - * 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.lib; - -import java.io.ByteArrayInputStream; -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; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; - -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.lib.PackIndex.MutableEntry; -import org.eclipse.jgit.revwalk.RevObject; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.transport.IndexPack; -import org.eclipse.jgit.util.JGitTestUtil; - -public class PackWriterTest extends SampleDataRepositoryTestCase { - - private static final List EMPTY_LIST_OBJECT = Collections - . emptyList(); - - private static final List EMPTY_LIST_REVS = Collections - . emptyList(); - - private PackWriter writer; - - private ByteArrayOutputStream os; - - private File packBase; - - private File packFile; - - private File indexFile; - - private PackFile pack; - - 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"); - writer = new PackWriter(db, new TextProgressMonitor()); - } - - /** - * Test constructor for exceptions, default settings, initialization. - */ - public void testContructor() { - assertEquals(false, writer.isDeltaBaseAsOffset()); - assertEquals(true, writer.isReuseDeltas()); - assertEquals(true, writer.isReuseObjects()); - assertEquals(0, writer.getObjectsNumber()); - } - - /** - * Change default settings and verify them. - */ - public void testModifySettings() { - writer.setDeltaBaseAsOffset(true); - writer.setReuseDeltas(false); - writer.setReuseObjects(false); - - assertEquals(true, writer.isDeltaBaseAsOffset()); - assertEquals(false, writer.isReuseDeltas()); - assertEquals(false, writer.isReuseObjects()); - } - - /** - * Write empty pack by providing empty sets of interesting/uninteresting - * objects and check for correct format. - * - * @throws IOException - */ - public void testWriteEmptyPack1() throws IOException { - createVerifyOpenPack(EMPTY_LIST_OBJECT, EMPTY_LIST_OBJECT, false, false); - - assertEquals(0, writer.getObjectsNumber()); - assertEquals(0, pack.getObjectCount()); - assertEquals("da39a3ee5e6b4b0d3255bfef95601890afd80709", writer - .computeName().name()); - } - - /** - * Write empty pack by providing empty iterator of objects to write and - * check for correct format. - * - * @throws IOException - */ - public void testWriteEmptyPack2() throws IOException { - createVerifyOpenPack(EMPTY_LIST_REVS.iterator()); - - assertEquals(0, writer.getObjectsNumber()); - assertEquals(0, pack.getObjectCount()); - } - - /** - * Try to pass non-existing object as uninteresting, with non-ignoring - * setting. - * - * @throws IOException - */ - public void testNotIgnoreNonExistingObjects() throws IOException { - final ObjectId nonExisting = ObjectId - .fromString("0000000000000000000000000000000000000001"); - try { - createVerifyOpenPack(EMPTY_LIST_OBJECT, Collections.nCopies(1, - nonExisting), false, false); - fail("Should have thrown MissingObjectException"); - } catch (MissingObjectException x) { - // expected - } - } - - /** - * Try to pass non-existing object as uninteresting, with ignoring setting. - * - * @throws IOException - */ - public void testIgnoreNonExistingObjects() throws IOException { - final ObjectId nonExisting = ObjectId - .fromString("0000000000000000000000000000000000000001"); - createVerifyOpenPack(EMPTY_LIST_OBJECT, Collections.nCopies(1, - nonExisting), false, true); - // shouldn't throw anything - } - - /** - * Create pack basing on only interesting objects, then precisely verify - * content. No delta reuse here. - * - * @throws IOException - */ - public void testWritePack1() throws IOException { - writer.setReuseDeltas(false); - writeVerifyPack1(); - } - - /** - * Test writing pack without object reuse. Pack content/preparation as in - * {@link #testWritePack1()}. - * - * @throws IOException - */ - public void testWritePack1NoObjectReuse() throws IOException { - writer.setReuseDeltas(false); - writer.setReuseObjects(false); - writeVerifyPack1(); - } - - /** - * Create pack basing on both interesting and uninteresting objects, then - * precisely verify content. No delta reuse here. - * - * @throws IOException - */ - public void testWritePack2() throws IOException { - writeVerifyPack2(false); - } - - /** - * Test pack writing with deltas reuse, delta-base first rule. Pack - * content/preparation as in {@link #testWritePack2()}. - * - * @throws IOException - */ - public void testWritePack2DeltasReuseRefs() throws IOException { - writeVerifyPack2(true); - } - - /** - * Test pack writing with delta reuse. Delta bases referred as offsets. Pack - * configuration as in {@link #testWritePack2DeltasReuseRefs()}. - * - * @throws IOException - */ - public void testWritePack2DeltasReuseOffsets() throws IOException { - writer.setDeltaBaseAsOffset(true); - writeVerifyPack2(true); - } - - /** - * Test pack writing with delta reuse. Raw-data copy (reuse) is made on a - * pack with CRC32 index. Pack configuration as in - * {@link #testWritePack2DeltasReuseRefs()}. - * - * @throws IOException - */ - public void testWritePack2DeltasCRC32Copy() throws IOException { - final File packDir = new File(db.getObjectDatabase().getDirectory(), "pack"); - final File crc32Pack = new File(packDir, - "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.pack"); - final File crc32Idx = new File(packDir, - "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idx"); - copyFile(JGitTestUtil.getTestResourceFile( - "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idxV2"), - crc32Idx); - db.openPack(crc32Pack, crc32Idx); - - writeVerifyPack2(true); - } - - /** - * Create pack basing on fixed objects list, then precisely verify content. - * No delta reuse here. - * - * @throws IOException - * @throws MissingObjectException - * - */ - public void testWritePack3() throws MissingObjectException, IOException { - writer.setReuseDeltas(false); - final ObjectId forcedOrder[] = new ObjectId[] { - ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"), - ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"), - ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), - ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"), - ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"), - ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") }; - final RevWalk parser = new RevWalk(db); - final RevObject forcedOrderRevs[] = new RevObject[forcedOrder.length]; - for (int i = 0; i < forcedOrder.length; i++) - forcedOrderRevs[i] = parser.parseAny(forcedOrder[i]); - - createVerifyOpenPack(Arrays.asList(forcedOrderRevs).iterator()); - - assertEquals(forcedOrder.length, writer.getObjectsNumber()); - verifyObjectsOrder(forcedOrder); - assertEquals("ed3f96b8327c7c66b0f8f70056129f0769323d86", writer - .computeName().name()); - } - - /** - * Another pack creation: basing on both interesting and uninteresting - * objects. No delta reuse possible here, as this is a specific case when we - * write only 1 commit, associated with 1 tree, 1 blob. - * - * @throws IOException - */ - public void testWritePack4() throws IOException { - writeVerifyPack4(false); - } - - /** - * Test thin pack writing: 1 blob delta base is on objects edge. Pack - * configuration as in {@link #testWritePack4()}. - * - * @throws IOException - */ - public void testWritePack4ThinPack() throws IOException { - writeVerifyPack4(true); - } - - /** - * Compare sizes of packs created using {@link #testWritePack2()} and - * {@link #testWritePack2DeltasReuseRefs()}. The pack using deltas should - * be smaller. - * - * @throws Exception - */ - public void testWritePack2SizeDeltasVsNoDeltas() throws Exception { - testWritePack2(); - final long sizePack2NoDeltas = os.size(); - tearDown(); - setUp(); - testWritePack2DeltasReuseRefs(); - final long sizePack2DeltasRefs = os.size(); - - assertTrue(sizePack2NoDeltas > sizePack2DeltasRefs); - } - - /** - * Compare sizes of packs created using - * {@link #testWritePack2DeltasReuseRefs()} and - * {@link #testWritePack2DeltasReuseOffsets()}. The pack with delta bases - * written as offsets should be smaller. - * - * @throws Exception - */ - public void testWritePack2SizeOffsetsVsRefs() throws Exception { - testWritePack2DeltasReuseRefs(); - final long sizePack2DeltasRefs = os.size(); - tearDown(); - setUp(); - testWritePack2DeltasReuseOffsets(); - final long sizePack2DeltasOffsets = os.size(); - - assertTrue(sizePack2DeltasRefs > sizePack2DeltasOffsets); - } - - /** - * Compare sizes of packs created using {@link #testWritePack4()} and - * {@link #testWritePack4ThinPack()}. Obviously, the thin pack should be - * smaller. - * - * @throws Exception - */ - public void testWritePack4SizeThinVsNoThin() throws Exception { - testWritePack4(); - final long sizePack4 = os.size(); - tearDown(); - setUp(); - testWritePack4ThinPack(); - final long sizePack4Thin = os.size(); - - assertTrue(sizePack4 > sizePack4Thin); - } - - public void testWriteIndex() throws Exception { - writer.setIndexVersion(2); - writeVerifyPack4(false); - - // Validate that IndexPack came up with the right CRC32 value. - final PackIndex idx1 = PackIndex.open(indexFile); - assertTrue(idx1 instanceof PackIndexV2); - assertEquals(0x4743F1E4L, idx1.findCRC32(ObjectId - .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"))); - - // Validate that an index written by PackWriter is the same. - final File idx2File = new File(indexFile.getAbsolutePath() + ".2"); - final FileOutputStream is = new FileOutputStream(idx2File); - try { - writer.writeIndex(is); - } finally { - is.close(); - } - final PackIndex idx2 = PackIndex.open(idx2File); - assertTrue(idx2 instanceof PackIndexV2); - assertEquals(idx1.getObjectCount(), idx2.getObjectCount()); - assertEquals(idx1.getOffset64Count(), idx2.getOffset64Count()); - - for (int i = 0; i < idx1.getObjectCount(); i++) { - final ObjectId id = idx1.getObjectId(i); - assertEquals(id, idx2.getObjectId(i)); - assertEquals(idx1.findOffset(id), idx2.findOffset(id)); - assertEquals(idx1.findCRC32(id), idx2.findCRC32(id)); - } - } - - // TODO: testWritePackDeltasCycle() - // TODO: testWritePackDeltasDepth() - - private void writeVerifyPack1() throws IOException { - final LinkedList interestings = new LinkedList(); - interestings.add(ObjectId - .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")); - createVerifyOpenPack(interestings, EMPTY_LIST_OBJECT, false, false); - - final ObjectId expectedOrder[] = new ObjectId[] { - ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"), - ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"), - ObjectId.fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"), - ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), - ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"), - ObjectId.fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904"), - ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"), - ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") }; - - assertEquals(expectedOrder.length, writer.getObjectsNumber()); - verifyObjectsOrder(expectedOrder); - assertEquals("34be9032ac282b11fa9babdc2b2a93ca996c9c2f", writer - .computeName().name()); - } - - private void writeVerifyPack2(boolean deltaReuse) throws IOException { - writer.setReuseDeltas(deltaReuse); - final LinkedList interestings = new LinkedList(); - interestings.add(ObjectId - .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")); - final LinkedList uninterestings = new LinkedList(); - uninterestings.add(ObjectId - .fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab")); - createVerifyOpenPack(interestings, uninterestings, false, false); - - final ObjectId expectedOrder[] = new ObjectId[] { - ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"), - ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"), - ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), - ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"), - ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"), - ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") }; - if (deltaReuse) { - // objects order influenced (swapped) by delta-base first rule - ObjectId temp = expectedOrder[4]; - expectedOrder[4] = expectedOrder[5]; - expectedOrder[5] = temp; - } - assertEquals(expectedOrder.length, writer.getObjectsNumber()); - verifyObjectsOrder(expectedOrder); - assertEquals("ed3f96b8327c7c66b0f8f70056129f0769323d86", writer - .computeName().name()); - } - - private void writeVerifyPack4(final boolean thin) throws IOException { - final LinkedList interestings = new LinkedList(); - interestings.add(ObjectId - .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")); - final LinkedList uninterestings = new LinkedList(); - uninterestings.add(ObjectId - .fromString("c59759f143fb1fe21c197981df75a7ee00290799")); - createVerifyOpenPack(interestings, uninterestings, thin, false); - - final ObjectId writtenObjects[] = new ObjectId[] { - ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"), - ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), - ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") }; - assertEquals(writtenObjects.length, writer.getObjectsNumber()); - ObjectId expectedObjects[]; - if (thin) { - expectedObjects = new ObjectId[4]; - System.arraycopy(writtenObjects, 0, expectedObjects, 0, - writtenObjects.length); - expectedObjects[3] = ObjectId - .fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3"); - - } else { - expectedObjects = writtenObjects; - } - verifyObjectsOrder(expectedObjects); - assertEquals("cded4b74176b4456afa456768b2b5aafb41c44fc", writer - .computeName().name()); - } - - private void createVerifyOpenPack(final Collection interestings, - final Collection uninterestings, final boolean thin, - final boolean ignoreMissingUninteresting) - throws MissingObjectException, IOException { - writer.setThin(thin); - writer.setIgnoreMissingUninteresting(ignoreMissingUninteresting); - writer.preparePack(interestings, uninterestings); - writer.writePack(os); - verifyOpenPack(thin); - } - - private void createVerifyOpenPack(final Iterator objectSource) - throws MissingObjectException, IOException { - writer.preparePack(objectSource); - writer.writePack(os); - verifyOpenPack(false); - } - - private void verifyOpenPack(final boolean thin) throws IOException { - if (thin) { - final InputStream is = new ByteArrayInputStream(os.toByteArray()); - final IndexPack indexer = new IndexPack(db, is, packBase); - try { - indexer.index(new TextProgressMonitor()); - 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); - } - - private void verifyObjectsOrder(final ObjectId objectsOrder[]) { - final List entries = new ArrayList(); - - for (MutableEntry me : pack) { - entries.add(me.cloneEntry()); - } - Collections.sort(entries, new Comparator() { - public int compare(MutableEntry o1, MutableEntry o2) { - return Long.signum(o1.getOffset() - o2.getOffset()); - } - }); - - int i = 0; - for (MutableEntry me : entries) { - assertEquals(objectsOrder[i++].toObjectId(), me.toObjectId()); - } - } -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java deleted file mode 100644 index a2812901bc..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java +++ /dev/null @@ -1,1024 +0,0 @@ -/* - * Copyright (C) 2010, Google Inc. - * 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.lib; - -import static org.eclipse.jgit.lib.Constants.HEAD; -import static org.eclipse.jgit.lib.Constants.R_HEADS; -import static org.eclipse.jgit.lib.Constants.R_TAGS; -import static org.eclipse.jgit.lib.Ref.Storage.LOOSE; -import static org.eclipse.jgit.lib.Ref.Storage.NEW; - -import java.io.File; -import java.io.IOException; -import java.util.Map; - -import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; -import org.eclipse.jgit.junit.TestRepository; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevTag; - -public class RefDirectoryTest extends LocalDiskRepositoryTestCase { - private Repository diskRepo; - - private TestRepository repo; - - private RefDirectory refdir; - - private RevCommit A; - - private RevCommit B; - - private RevTag v1_0; - - protected void setUp() throws Exception { - super.setUp(); - - diskRepo = createBareRepository(); - refdir = (RefDirectory) diskRepo.getRefDatabase(); - - repo = new TestRepository(diskRepo); - A = repo.commit().create(); - B = repo.commit(repo.getRevWalk().parseCommit(A)); - v1_0 = repo.tag("v1_0", B); - repo.getRevWalk().parseBody(v1_0); - } - - public void testCreate() throws IOException { - // setUp above created the directory. We just have to test it. - File d = diskRepo.getDirectory(); - assertSame(diskRepo, refdir.getRepository()); - - assertTrue(new File(d, "refs").isDirectory()); - assertTrue(new File(d, "logs").isDirectory()); - assertTrue(new File(d, "logs/refs").isDirectory()); - assertFalse(new File(d, "packed-refs").exists()); - - assertTrue(new File(d, "refs/heads").isDirectory()); - assertTrue(new File(d, "refs/tags").isDirectory()); - assertEquals(2, new File(d, "refs").list().length); - assertEquals(0, new File(d, "refs/heads").list().length); - assertEquals(0, new File(d, "refs/tags").list().length); - - assertTrue(new File(d, "logs/refs/heads").isDirectory()); - assertFalse(new File(d, "logs/HEAD").exists()); - assertEquals(0, new File(d, "logs/refs/heads").list().length); - - assertEquals("ref: refs/heads/master\n", read(new File(d, HEAD))); - } - - public void testGetRefs_EmptyDatabase() throws IOException { - Map all; - - all = refdir.getRefs(RefDatabase.ALL); - assertTrue("no references", all.isEmpty()); - - all = refdir.getRefs(R_HEADS); - assertTrue("no references", all.isEmpty()); - - all = refdir.getRefs(R_TAGS); - assertTrue("no references", all.isEmpty()); - } - - public void testGetRefs_HeadOnOneBranch() throws IOException { - Map all; - Ref head, master; - - writeLooseRef("refs/heads/master", A); - - all = refdir.getRefs(RefDatabase.ALL); - assertEquals(2, all.size()); - assertTrue("has HEAD", all.containsKey(HEAD)); - assertTrue("has master", all.containsKey("refs/heads/master")); - - head = all.get(HEAD); - master = all.get("refs/heads/master"); - - assertEquals(HEAD, head.getName()); - assertTrue(head.isSymbolic()); - assertSame(LOOSE, head.getStorage()); - assertSame("uses same ref as target", master, head.getTarget()); - - assertEquals("refs/heads/master", master.getName()); - assertFalse(master.isSymbolic()); - assertSame(LOOSE, master.getStorage()); - assertEquals(A, master.getObjectId()); - } - - public void testGetRefs_DeatchedHead1() throws IOException { - Map all; - Ref head; - - writeLooseRef(HEAD, A); - BUG_WorkAroundRacyGitIssues(HEAD); - - all = refdir.getRefs(RefDatabase.ALL); - assertEquals(1, all.size()); - assertTrue("has HEAD", all.containsKey(HEAD)); - - head = all.get(HEAD); - - assertEquals(HEAD, head.getName()); - assertFalse(head.isSymbolic()); - assertSame(LOOSE, head.getStorage()); - assertEquals(A, head.getObjectId()); - } - - public void testGetRefs_DeatchedHead2() throws IOException { - Map all; - Ref head, master; - - writeLooseRef(HEAD, A); - writeLooseRef("refs/heads/master", B); - BUG_WorkAroundRacyGitIssues(HEAD); - - all = refdir.getRefs(RefDatabase.ALL); - assertEquals(2, all.size()); - - head = all.get(HEAD); - master = all.get("refs/heads/master"); - - assertEquals(HEAD, head.getName()); - assertFalse(head.isSymbolic()); - assertSame(LOOSE, head.getStorage()); - assertEquals(A, head.getObjectId()); - - assertEquals("refs/heads/master", master.getName()); - assertFalse(master.isSymbolic()); - assertSame(LOOSE, master.getStorage()); - assertEquals(B, master.getObjectId()); - } - - public void testGetRefs_DeeplyNestedBranch() throws IOException { - String name = "refs/heads/a/b/c/d/e/f/g/h/i/j/k"; - Map all; - Ref r; - - writeLooseRef(name, A); - - all = refdir.getRefs(RefDatabase.ALL); - assertEquals(1, all.size()); - - r = all.get(name); - assertEquals(name, r.getName()); - assertFalse(r.isSymbolic()); - assertSame(LOOSE, r.getStorage()); - assertEquals(A, r.getObjectId()); - } - - public void testGetRefs_HeadBranchNotBorn() throws IOException { - Map all; - Ref a, b; - - writeLooseRef("refs/heads/A", A); - writeLooseRef("refs/heads/B", B); - - all = refdir.getRefs(RefDatabase.ALL); - assertEquals(2, all.size()); - assertFalse("no HEAD", all.containsKey(HEAD)); - - a = all.get("refs/heads/A"); - b = all.get("refs/heads/B"); - - assertEquals(A, a.getObjectId()); - assertEquals(B, b.getObjectId()); - - assertEquals("refs/heads/A", a.getName()); - assertEquals("refs/heads/B", b.getName()); - } - - public void testGetRefs_LooseOverridesPacked() throws IOException { - Map heads; - Ref a; - - writeLooseRef("refs/heads/master", B); - writePackedRef("refs/heads/master", A); - - heads = refdir.getRefs(R_HEADS); - assertEquals(1, heads.size()); - - a = heads.get("master"); - assertEquals("refs/heads/master", a.getName()); - assertEquals(B, a.getObjectId()); - } - - public void testGetRefs_IgnoresGarbageRef1() throws IOException { - Map heads; - Ref a; - - writeLooseRef("refs/heads/A", A); - write(new File(diskRepo.getDirectory(), "refs/heads/bad"), "FAIL\n"); - - heads = refdir.getRefs(RefDatabase.ALL); - assertEquals(1, heads.size()); - - a = heads.get("refs/heads/A"); - assertEquals("refs/heads/A", a.getName()); - assertEquals(A, a.getObjectId()); - } - - public void testGetRefs_IgnoresGarbageRef2() throws IOException { - Map heads; - Ref a; - - writeLooseRef("refs/heads/A", A); - write(new File(diskRepo.getDirectory(), "refs/heads/bad"), ""); - - heads = refdir.getRefs(RefDatabase.ALL); - assertEquals(1, heads.size()); - - a = heads.get("refs/heads/A"); - assertEquals("refs/heads/A", a.getName()); - assertEquals(A, a.getObjectId()); - } - - public void testGetRefs_IgnoresGarbageRef3() throws IOException { - Map heads; - Ref a; - - writeLooseRef("refs/heads/A", A); - write(new File(diskRepo.getDirectory(), "refs/heads/bad"), "\n"); - - heads = refdir.getRefs(RefDatabase.ALL); - assertEquals(1, heads.size()); - - a = heads.get("refs/heads/A"); - assertEquals("refs/heads/A", a.getName()); - assertEquals(A, a.getObjectId()); - } - - public void testGetRefs_IgnoresGarbageRef4() throws IOException { - Map heads; - Ref a, b, c; - - writeLooseRef("refs/heads/A", A); - writeLooseRef("refs/heads/B", B); - writeLooseRef("refs/heads/C", A); - heads = refdir.getRefs(RefDatabase.ALL); - assertEquals(3, heads.size()); - assertTrue(heads.containsKey("refs/heads/A")); - assertTrue(heads.containsKey("refs/heads/B")); - assertTrue(heads.containsKey("refs/heads/C")); - - writeLooseRef("refs/heads/B", "FAIL\n"); - BUG_WorkAroundRacyGitIssues("refs/heads/B"); - - heads = refdir.getRefs(RefDatabase.ALL); - assertEquals(2, heads.size()); - - a = heads.get("refs/heads/A"); - b = heads.get("refs/heads/B"); - c = heads.get("refs/heads/C"); - - assertEquals("refs/heads/A", a.getName()); - assertEquals(A, a.getObjectId()); - - assertNull("no refs/heads/B", b); - - assertEquals("refs/heads/C", c.getName()); - assertEquals(A, c.getObjectId()); - } - - public void testGetRefs_InvalidName() throws IOException { - writeLooseRef("refs/heads/A", A); - - assertTrue("empty refs/heads", refdir.getRefs("refs/heads").isEmpty()); - assertTrue("empty objects", refdir.getRefs("objects").isEmpty()); - assertTrue("empty objects/", refdir.getRefs("objects/").isEmpty()); - } - - public void testGetRefs_HeadsOnly_AllLoose() throws IOException { - Map heads; - Ref a, b; - - writeLooseRef("refs/heads/A", A); - writeLooseRef("refs/heads/B", B); - writeLooseRef("refs/tags/v1.0", v1_0); - - heads = refdir.getRefs(R_HEADS); - assertEquals(2, heads.size()); - - a = heads.get("A"); - b = heads.get("B"); - - assertEquals("refs/heads/A", a.getName()); - assertEquals("refs/heads/B", b.getName()); - - assertEquals(A, a.getObjectId()); - assertEquals(B, b.getObjectId()); - } - - public void testGetRefs_HeadsOnly_AllPacked1() throws IOException { - Map heads; - Ref a; - - deleteLooseRef(HEAD); - writePackedRef("refs/heads/A", A); - - heads = refdir.getRefs(R_HEADS); - assertEquals(1, heads.size()); - - a = heads.get("A"); - - assertEquals("refs/heads/A", a.getName()); - assertEquals(A, a.getObjectId()); - } - - public void testGetRefs_HeadsOnly_SymrefToPacked() throws IOException { - Map heads; - Ref master, other; - - writeLooseRef("refs/heads/other", "ref: refs/heads/master\n"); - writePackedRef("refs/heads/master", A); - - heads = refdir.getRefs(R_HEADS); - assertEquals(2, heads.size()); - - master = heads.get("master"); - other = heads.get("other"); - - assertEquals("refs/heads/master", master.getName()); - assertEquals(A, master.getObjectId()); - - assertEquals("refs/heads/other", other.getName()); - assertEquals(A, other.getObjectId()); - assertSame(master, other.getTarget()); - } - - public void testGetRefs_HeadsOnly_Mixed() throws IOException { - Map heads; - Ref a, b; - - writeLooseRef("refs/heads/A", A); - writeLooseRef("refs/heads/B", B); - writePackedRef("refs/tags/v1.0", v1_0); - - heads = refdir.getRefs(R_HEADS); - assertEquals(2, heads.size()); - - a = heads.get("A"); - b = heads.get("B"); - - assertEquals("refs/heads/A", a.getName()); - assertEquals("refs/heads/B", b.getName()); - - assertEquals(A, a.getObjectId()); - assertEquals(B, b.getObjectId()); - } - - public void testGetRefs_TagsOnly_AllLoose() throws IOException { - Map tags; - Ref a; - - writeLooseRef("refs/heads/A", A); - writeLooseRef("refs/tags/v1.0", v1_0); - - tags = refdir.getRefs(R_TAGS); - assertEquals(1, tags.size()); - - a = tags.get("v1.0"); - - assertEquals("refs/tags/v1.0", a.getName()); - assertEquals(v1_0, a.getObjectId()); - } - - public void testGetRefs_TagsOnly_AllPacked() throws IOException { - Map tags; - Ref a; - - deleteLooseRef(HEAD); - writePackedRef("refs/tags/v1.0", v1_0); - - tags = refdir.getRefs(R_TAGS); - assertEquals(1, tags.size()); - - a = tags.get("v1.0"); - - assertEquals("refs/tags/v1.0", a.getName()); - assertEquals(v1_0, a.getObjectId()); - } - - public void testGetRefs_DiscoversNewLoose1() throws IOException { - Map orig, next; - Ref orig_r, next_r; - - writeLooseRef("refs/heads/master", A); - orig = refdir.getRefs(RefDatabase.ALL); - - writeLooseRef("refs/heads/next", B); - next = refdir.getRefs(RefDatabase.ALL); - - assertEquals(2, orig.size()); - assertEquals(3, next.size()); - - assertFalse(orig.containsKey("refs/heads/next")); - assertTrue(next.containsKey("refs/heads/next")); - - orig_r = orig.get("refs/heads/master"); - next_r = next.get("refs/heads/master"); - assertEquals(A, orig_r.getObjectId()); - assertSame("uses cached instance", orig_r, next_r); - assertSame("same HEAD", orig_r, orig.get(HEAD).getTarget()); - assertSame("same HEAD", orig_r, next.get(HEAD).getTarget()); - - next_r = next.get("refs/heads/next"); - assertSame(LOOSE, next_r.getStorage()); - assertEquals(B, next_r.getObjectId()); - } - - public void testGetRefs_DiscoversNewLoose2() throws IOException { - Map orig, next, news; - - writeLooseRef("refs/heads/pu", A); - orig = refdir.getRefs(RefDatabase.ALL); - - writeLooseRef("refs/heads/new/B", B); - news = refdir.getRefs("refs/heads/new/"); - next = refdir.getRefs(RefDatabase.ALL); - - assertEquals(1, orig.size()); - assertEquals(2, next.size()); - assertEquals(1, news.size()); - - assertTrue(orig.containsKey("refs/heads/pu")); - assertTrue(next.containsKey("refs/heads/pu")); - assertFalse(news.containsKey("refs/heads/pu")); - - assertFalse(orig.containsKey("refs/heads/new/B")); - assertTrue(next.containsKey("refs/heads/new/B")); - assertTrue(news.containsKey("B")); - } - - public void testGetRefs_DiscoversModifiedLoose() throws IOException { - Map all; - - writeLooseRef("refs/heads/master", A); - all = refdir.getRefs(RefDatabase.ALL); - assertEquals(A, all.get(HEAD).getObjectId()); - - writeLooseRef("refs/heads/master", B); - BUG_WorkAroundRacyGitIssues("refs/heads/master"); - all = refdir.getRefs(RefDatabase.ALL); - assertEquals(B, all.get(HEAD).getObjectId()); - } - - public void testGetRef_DiscoversModifiedLoose() throws IOException { - Map all; - - writeLooseRef("refs/heads/master", A); - all = refdir.getRefs(RefDatabase.ALL); - assertEquals(A, all.get(HEAD).getObjectId()); - - writeLooseRef("refs/heads/master", B); - BUG_WorkAroundRacyGitIssues("refs/heads/master"); - - Ref master = refdir.getRef("refs/heads/master"); - assertEquals(B, master.getObjectId()); - } - - public void testGetRefs_DiscoversDeletedLoose1() throws IOException { - Map orig, next; - Ref orig_r, next_r; - - writeLooseRef("refs/heads/B", B); - writeLooseRef("refs/heads/master", A); - orig = refdir.getRefs(RefDatabase.ALL); - - deleteLooseRef("refs/heads/B"); - next = refdir.getRefs(RefDatabase.ALL); - - assertEquals(3, orig.size()); - assertEquals(2, next.size()); - - assertTrue(orig.containsKey("refs/heads/B")); - assertFalse(next.containsKey("refs/heads/B")); - - orig_r = orig.get("refs/heads/master"); - next_r = next.get("refs/heads/master"); - assertEquals(A, orig_r.getObjectId()); - assertSame("uses cached instance", orig_r, next_r); - assertSame("same HEAD", orig_r, orig.get(HEAD).getTarget()); - assertSame("same HEAD", orig_r, next.get(HEAD).getTarget()); - - orig_r = orig.get("refs/heads/B"); - assertSame(LOOSE, orig_r.getStorage()); - assertEquals(B, orig_r.getObjectId()); - } - - public void testGetRef_DiscoversDeletedLoose() throws IOException { - Map all; - - writeLooseRef("refs/heads/master", A); - all = refdir.getRefs(RefDatabase.ALL); - assertEquals(A, all.get(HEAD).getObjectId()); - - deleteLooseRef("refs/heads/master"); - assertNull(refdir.getRef("refs/heads/master")); - assertTrue(refdir.getRefs(RefDatabase.ALL).isEmpty()); - } - - public void testGetRefs_DiscoversDeletedLoose2() throws IOException { - Map orig, next; - - writeLooseRef("refs/heads/master", A); - writeLooseRef("refs/heads/pu", B); - orig = refdir.getRefs(RefDatabase.ALL); - - deleteLooseRef("refs/heads/pu"); - next = refdir.getRefs(RefDatabase.ALL); - - assertEquals(3, orig.size()); - assertEquals(2, next.size()); - - assertTrue(orig.containsKey("refs/heads/pu")); - assertFalse(next.containsKey("refs/heads/pu")); - } - - public void testGetRefs_DiscoversDeletedLoose3() throws IOException { - Map orig, next; - - writeLooseRef("refs/heads/master", A); - writeLooseRef("refs/heads/next", B); - writeLooseRef("refs/heads/pu", B); - writeLooseRef("refs/tags/v1.0", v1_0); - orig = refdir.getRefs(RefDatabase.ALL); - - deleteLooseRef("refs/heads/pu"); - deleteLooseRef("refs/heads/next"); - next = refdir.getRefs(RefDatabase.ALL); - - assertEquals(5, orig.size()); - assertEquals(3, next.size()); - - assertTrue(orig.containsKey("refs/heads/pu")); - assertTrue(orig.containsKey("refs/heads/next")); - assertFalse(next.containsKey("refs/heads/pu")); - assertFalse(next.containsKey("refs/heads/next")); - } - - public void testGetRefs_DiscoversDeletedLoose4() throws IOException { - Map orig, next; - Ref orig_r, next_r; - - writeLooseRef("refs/heads/B", B); - writeLooseRef("refs/heads/master", A); - orig = refdir.getRefs(RefDatabase.ALL); - - deleteLooseRef("refs/heads/master"); - next = refdir.getRefs("refs/heads/"); - - assertEquals(3, orig.size()); - assertEquals(1, next.size()); - - assertTrue(orig.containsKey("refs/heads/B")); - assertTrue(orig.containsKey("refs/heads/master")); - assertTrue(next.containsKey("B")); - assertFalse(next.containsKey("master")); - - orig_r = orig.get("refs/heads/B"); - next_r = next.get("B"); - assertEquals(B, orig_r.getObjectId()); - assertSame("uses cached instance", orig_r, next_r); - } - - public void testGetRefs_DiscoversDeletedLoose5() throws IOException { - Map orig, next; - - writeLooseRef("refs/heads/master", A); - writeLooseRef("refs/heads/pu", B); - orig = refdir.getRefs(RefDatabase.ALL); - - deleteLooseRef("refs/heads/pu"); - writeLooseRef("refs/tags/v1.0", v1_0); - next = refdir.getRefs(RefDatabase.ALL); - - assertEquals(3, orig.size()); - assertEquals(3, next.size()); - - assertTrue(orig.containsKey("refs/heads/pu")); - assertFalse(orig.containsKey("refs/tags/v1.0")); - assertFalse(next.containsKey("refs/heads/pu")); - assertTrue(next.containsKey("refs/tags/v1.0")); - } - - public void testGetRefs_SkipsLockFiles() throws IOException { - Map all; - - writeLooseRef("refs/heads/master", A); - writeLooseRef("refs/heads/pu.lock", B); - all = refdir.getRefs(RefDatabase.ALL); - - assertEquals(2, all.size()); - - assertTrue(all.containsKey(HEAD)); - assertTrue(all.containsKey("refs/heads/master")); - assertFalse(all.containsKey("refs/heads/pu.lock")); - } - - public void testGetRefs_CycleInSymbolicRef() throws IOException { - Map all; - Ref r; - - writeLooseRef("refs/1", "ref: refs/2\n"); - writeLooseRef("refs/2", "ref: refs/3\n"); - writeLooseRef("refs/3", "ref: refs/4\n"); - writeLooseRef("refs/4", "ref: refs/5\n"); - writeLooseRef("refs/5", "ref: refs/end\n"); - writeLooseRef("refs/end", A); - - all = refdir.getRefs(RefDatabase.ALL); - r = all.get("refs/1"); - assertNotNull("has 1", r); - - assertEquals("refs/1", r.getName()); - assertEquals(A, r.getObjectId()); - assertTrue(r.isSymbolic()); - - r = r.getTarget(); - assertEquals("refs/2", r.getName()); - assertEquals(A, r.getObjectId()); - assertTrue(r.isSymbolic()); - - r = r.getTarget(); - assertEquals("refs/3", r.getName()); - assertEquals(A, r.getObjectId()); - assertTrue(r.isSymbolic()); - - r = r.getTarget(); - assertEquals("refs/4", r.getName()); - assertEquals(A, r.getObjectId()); - assertTrue(r.isSymbolic()); - - r = r.getTarget(); - assertEquals("refs/5", r.getName()); - assertEquals(A, r.getObjectId()); - assertTrue(r.isSymbolic()); - - r = r.getTarget(); - assertEquals("refs/end", r.getName()); - assertEquals(A, r.getObjectId()); - assertFalse(r.isSymbolic()); - - writeLooseRef("refs/5", "ref: refs/6\n"); - writeLooseRef("refs/6", "ref: refs/end\n"); - BUG_WorkAroundRacyGitIssues("refs/5"); - all = refdir.getRefs(RefDatabase.ALL); - r = all.get("refs/1"); - assertNull("mising 1 due to cycle", r); - } - - public void testGetRefs_PackedNotPeeled_Sorted() throws IOException { - Map all; - - writePackedRefs("" + // - A.name() + " refs/heads/master\n" + // - B.name() + " refs/heads/other\n" + // - v1_0.name() + " refs/tags/v1.0\n"); - all = refdir.getRefs(RefDatabase.ALL); - - assertEquals(4, all.size()); - final Ref head = all.get(HEAD); - final Ref master = all.get("refs/heads/master"); - final Ref other = all.get("refs/heads/other"); - final Ref tag = all.get("refs/tags/v1.0"); - - assertEquals(A, master.getObjectId()); - assertFalse(master.isPeeled()); - assertNull(master.getPeeledObjectId()); - - assertEquals(B, other.getObjectId()); - assertFalse(other.isPeeled()); - assertNull(other.getPeeledObjectId()); - - assertSame(master, head.getTarget()); - assertEquals(A, head.getObjectId()); - assertFalse(head.isPeeled()); - assertNull(head.getPeeledObjectId()); - - assertEquals(v1_0, tag.getObjectId()); - assertFalse(tag.isPeeled()); - assertNull(tag.getPeeledObjectId()); - } - - public void testGetRef_PackedNotPeeled_WrongSort() throws IOException { - writePackedRefs("" + // - v1_0.name() + " refs/tags/v1.0\n" + // - B.name() + " refs/heads/other\n" + // - A.name() + " refs/heads/master\n"); - - final Ref head = refdir.getRef(HEAD); - final Ref master = refdir.getRef("refs/heads/master"); - final Ref other = refdir.getRef("refs/heads/other"); - final Ref tag = refdir.getRef("refs/tags/v1.0"); - - assertEquals(A, master.getObjectId()); - assertFalse(master.isPeeled()); - assertNull(master.getPeeledObjectId()); - - assertEquals(B, other.getObjectId()); - assertFalse(other.isPeeled()); - assertNull(other.getPeeledObjectId()); - - assertSame(master, head.getTarget()); - assertEquals(A, head.getObjectId()); - assertFalse(head.isPeeled()); - assertNull(head.getPeeledObjectId()); - - assertEquals(v1_0, tag.getObjectId()); - assertFalse(tag.isPeeled()); - assertNull(tag.getPeeledObjectId()); - } - - public void testGetRefs_PackedWithPeeled() throws IOException { - Map all; - - writePackedRefs("# pack-refs with: peeled \n" + // - A.name() + " refs/heads/master\n" + // - B.name() + " refs/heads/other\n" + // - v1_0.name() + " refs/tags/v1.0\n" + // - "^" + v1_0.getObject().name() + "\n"); - all = refdir.getRefs(RefDatabase.ALL); - - assertEquals(4, all.size()); - final Ref head = all.get(HEAD); - final Ref master = all.get("refs/heads/master"); - final Ref other = all.get("refs/heads/other"); - final Ref tag = all.get("refs/tags/v1.0"); - - assertEquals(A, master.getObjectId()); - assertTrue(master.isPeeled()); - assertNull(master.getPeeledObjectId()); - - assertEquals(B, other.getObjectId()); - assertTrue(other.isPeeled()); - assertNull(other.getPeeledObjectId()); - - assertSame(master, head.getTarget()); - assertEquals(A, head.getObjectId()); - assertTrue(head.isPeeled()); - assertNull(head.getPeeledObjectId()); - - assertEquals(v1_0, tag.getObjectId()); - assertTrue(tag.isPeeled()); - assertEquals(v1_0.getObject(), tag.getPeeledObjectId()); - } - - public void testGetRef_EmptyDatabase() throws IOException { - Ref r; - - r = refdir.getRef(HEAD); - assertTrue(r.isSymbolic()); - assertSame(LOOSE, r.getStorage()); - assertEquals("refs/heads/master", r.getTarget().getName()); - assertSame(NEW, r.getTarget().getStorage()); - assertNull(r.getTarget().getObjectId()); - - assertNull(refdir.getRef("refs/heads/master")); - assertNull(refdir.getRef("refs/tags/v1.0")); - assertNull(refdir.getRef("FETCH_HEAD")); - assertNull(refdir.getRef("NOT.A.REF.NAME")); - assertNull(refdir.getRef("master")); - assertNull(refdir.getRef("v1.0")); - } - - public void testGetRef_FetchHead() throws IOException { - // This is an odd special case where we need to make sure we read - // exactly the first 40 bytes of the file and nothing further on - // that line, or the remainder of the file. - write(new File(diskRepo.getDirectory(), "FETCH_HEAD"), A.name() - + "\tnot-for-merge" - + "\tbranch 'master' of git://egit.eclipse.org/jgit\n"); - - Ref r = refdir.getRef("FETCH_HEAD"); - assertFalse(r.isSymbolic()); - assertEquals(A, r.getObjectId()); - assertEquals("FETCH_HEAD", r.getName()); - assertFalse(r.isPeeled()); - assertNull(r.getPeeledObjectId()); - } - - public void testGetRef_AnyHeadWithGarbage() throws IOException { - write(new File(diskRepo.getDirectory(), "refs/heads/A"), A.name() - + "012345 . this is not a standard reference\n" - + "#and even more junk\n"); - - Ref r = refdir.getRef("refs/heads/A"); - assertFalse(r.isSymbolic()); - assertEquals(A, r.getObjectId()); - assertEquals("refs/heads/A", r.getName()); - assertFalse(r.isPeeled()); - assertNull(r.getPeeledObjectId()); - } - - public void testGetRefs_CorruptSymbolicReference() throws IOException { - String name = "refs/heads/A"; - writeLooseRef(name, "ref: \n"); - assertTrue(refdir.getRefs(RefDatabase.ALL).isEmpty()); - } - - public void testGetRef_CorruptSymbolicReference() throws IOException { - String name = "refs/heads/A"; - writeLooseRef(name, "ref: \n"); - try { - refdir.getRef(name); - fail("read an invalid reference"); - } catch (IOException err) { - String msg = err.getMessage(); - assertEquals("Not a ref: " + name + ": ref:", msg); - } - } - - public void testGetRefs_CorruptObjectIdReference() throws IOException { - String name = "refs/heads/A"; - String content = "zoo" + A.name(); - writeLooseRef(name, content + "\n"); - assertTrue(refdir.getRefs(RefDatabase.ALL).isEmpty()); - } - - public void testGetRef_CorruptObjectIdReference() throws IOException { - String name = "refs/heads/A"; - String content = "zoo" + A.name(); - writeLooseRef(name, content + "\n"); - try { - refdir.getRef(name); - fail("read an invalid reference"); - } catch (IOException err) { - String msg = err.getMessage(); - assertEquals("Not a ref: " + name + ": " + content, msg); - } - } - - public void testIsNameConflicting() throws IOException { - writeLooseRef("refs/heads/a/b", A); - writePackedRef("refs/heads/q", B); - - // new references cannot replace an existing container - assertTrue(refdir.isNameConflicting("refs")); - assertTrue(refdir.isNameConflicting("refs/heads")); - assertTrue(refdir.isNameConflicting("refs/heads/a")); - - // existing reference is not conflicting - assertFalse(refdir.isNameConflicting("refs/heads/a/b")); - - // new references are not conflicting - assertFalse(refdir.isNameConflicting("refs/heads/a/d")); - assertFalse(refdir.isNameConflicting("refs/heads/master")); - - // existing reference must not be used as a container - assertTrue(refdir.isNameConflicting("refs/heads/a/b/c")); - assertTrue(refdir.isNameConflicting("refs/heads/q/master")); - } - - public void testPeelLooseTag() throws IOException { - writeLooseRef("refs/tags/v1_0", v1_0); - writeLooseRef("refs/tags/current", "ref: refs/tags/v1_0\n"); - - final Ref tag = refdir.getRef("refs/tags/v1_0"); - final Ref cur = refdir.getRef("refs/tags/current"); - - assertEquals(v1_0, tag.getObjectId()); - assertFalse(tag.isSymbolic()); - assertFalse(tag.isPeeled()); - assertNull(tag.getPeeledObjectId()); - - assertEquals(v1_0, cur.getObjectId()); - assertTrue(cur.isSymbolic()); - assertFalse(cur.isPeeled()); - assertNull(cur.getPeeledObjectId()); - - final Ref tag_p = refdir.peel(tag); - final Ref cur_p = refdir.peel(cur); - - assertNotSame(tag, tag_p); - assertFalse(tag_p.isSymbolic()); - assertTrue(tag_p.isPeeled()); - assertEquals(v1_0, tag_p.getObjectId()); - assertEquals(v1_0.getObject(), tag_p.getPeeledObjectId()); - assertSame(tag_p, refdir.peel(tag_p)); - - assertNotSame(cur, cur_p); - assertEquals("refs/tags/current", cur_p.getName()); - assertTrue(cur_p.isSymbolic()); - assertEquals("refs/tags/v1_0", cur_p.getTarget().getName()); - assertTrue(cur_p.isPeeled()); - assertEquals(v1_0, cur_p.getObjectId()); - assertEquals(v1_0.getObject(), cur_p.getPeeledObjectId()); - - // reuses cached peeling later, but not immediately due to - // the implementation so we have to fetch it once. - final Ref tag_p2 = refdir.getRef("refs/tags/v1_0"); - assertFalse(tag_p2.isSymbolic()); - assertTrue(tag_p2.isPeeled()); - assertEquals(v1_0, tag_p2.getObjectId()); - assertEquals(v1_0.getObject(), tag_p2.getPeeledObjectId()); - - assertSame(tag_p2, refdir.getRef("refs/tags/v1_0")); - assertSame(tag_p2, refdir.getRef("refs/tags/current").getTarget()); - assertSame(tag_p2, refdir.peel(tag_p2)); - } - - public void testPeelCommit() throws IOException { - writeLooseRef("refs/heads/master", A); - - Ref master = refdir.getRef("refs/heads/master"); - assertEquals(A, master.getObjectId()); - assertFalse(master.isPeeled()); - assertNull(master.getPeeledObjectId()); - - Ref master_p = refdir.peel(master); - assertNotSame(master, master_p); - assertEquals(A, master_p.getObjectId()); - assertTrue(master_p.isPeeled()); - assertNull(master_p.getPeeledObjectId()); - - // reuses cached peeling later, but not immediately due to - // the implementation so we have to fetch it once. - Ref master_p2 = refdir.getRef("refs/heads/master"); - assertNotSame(master, master_p2); - assertEquals(A, master_p2.getObjectId()); - assertTrue(master_p2.isPeeled()); - assertNull(master_p2.getPeeledObjectId()); - assertSame(master_p2, refdir.peel(master_p2)); - } - - private void writeLooseRef(String name, AnyObjectId id) throws IOException { - writeLooseRef(name, id.name() + "\n"); - } - - private void writeLooseRef(String name, String content) throws IOException { - write(new File(diskRepo.getDirectory(), name), content); - } - - private void writePackedRef(String name, AnyObjectId id) throws IOException { - writePackedRefs(id.name() + " " + name + "\n"); - } - - private void writePackedRefs(String content) throws IOException { - File pr = new File(diskRepo.getDirectory(), "packed-refs"); - write(pr, content); - } - - private void deleteLooseRef(String name) { - File path = new File(diskRepo.getDirectory(), name); - assertTrue("deleted " + name, path.delete()); - } - - /** - * Kick the timestamp of a local file. - *

- * We shouldn't have to make these method calls. The cache is using file - * system timestamps, and on many systems unit tests run faster than the - * modification clock. Dumping the cache after we make an edit behind - * RefDirectory's back allows the tests to pass. - * - * @param name - * the file in the repository to force a time change on. - */ - private void BUG_WorkAroundRacyGitIssues(String name) { - File path = new File(diskRepo.getDirectory(), name); - long old = path.lastModified(); - long set = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009 - path.setLastModified(set); - assertTrue("time changed", old != path.lastModified()); - } -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java deleted file mode 100644 index 161bd3eff1..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java +++ /dev/null @@ -1,840 +0,0 @@ -/* - * Copyright (C) 2008, Charles O'Farrell - * Copyright (C) 2009-2010, Google Inc. - * Copyright (C) 2008-2009, Robin Rosenberg - * 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.lib; - -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import org.eclipse.jgit.lib.RefUpdate.Result; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; - -public class RefUpdateTest extends SampleDataRepositoryTestCase { - - private void writeSymref(String src, String dst) throws IOException { - RefUpdate u = db.updateRef(src); - switch (u.link(dst)) { - case NEW: - case FORCED: - case NO_CHANGE: - break; - default: - fail("link " + src + " to " + dst); - } - } - - private RefUpdate updateRef(final String name) throws IOException { - final RefUpdate ref = db.updateRef(name); - ref.setNewObjectId(db.resolve(Constants.HEAD)); - return ref; - } - - private void delete(final RefUpdate ref, final Result expected) - throws IOException { - delete(ref, expected, true, true); - } - - private void delete(final RefUpdate ref, final Result expected, - final boolean exists, final boolean removed) throws IOException { - assertEquals(exists, db.getAllRefs().containsKey(ref.getName())); - assertEquals(expected, ref.delete()); - assertEquals(!removed, db.getAllRefs().containsKey(ref.getName())); - } - - public void testNoCacheObjectIdSubclass() throws IOException { - final String newRef = "refs/heads/abc"; - final RefUpdate ru = updateRef(newRef); - final RevCommit newid = new RevCommit(ru.getNewObjectId()) { - // empty - }; - ru.setNewObjectId(newid); - Result update = ru.update(); - assertEquals(Result.NEW, update); - final Ref r = db.getAllRefs().get(newRef); - assertNotNull(r); - assertEquals(newRef, r.getName()); - assertNotNull(r.getObjectId()); - assertNotSame(newid, r.getObjectId()); - assertSame(ObjectId.class, r.getObjectId().getClass()); - assertEquals(newid.copy(), r.getObjectId()); - List reverseEntries1 = db.getReflogReader("refs/heads/abc").getReverseEntries(); - org.eclipse.jgit.lib.ReflogReader.Entry entry1 = reverseEntries1.get(0); - assertEquals(1, reverseEntries1.size()); - assertEquals(ObjectId.zeroId(), entry1.getOldId()); - assertEquals(r.getObjectId(), entry1.getNewId()); - assertEquals(new PersonIdent(db).toString(), entry1.getWho().toString()); - assertEquals("", entry1.getComment()); - List reverseEntries2 = db.getReflogReader("HEAD").getReverseEntries(); - assertEquals(0, reverseEntries2.size()); - } - - public void testNewNamespaceConflictWithLoosePrefixNameExists() - throws IOException { - final String newRef = "refs/heads/z"; - final RefUpdate ru = updateRef(newRef); - final RevCommit newid = new RevCommit(ru.getNewObjectId()) { - // empty - }; - ru.setNewObjectId(newid); - Result update = ru.update(); - assertEquals(Result.NEW, update); - // end setup - final String newRef2 = "refs/heads/z/a"; - final RefUpdate ru2 = updateRef(newRef2); - final RevCommit newid2 = new RevCommit(ru2.getNewObjectId()) { - // empty - }; - ru.setNewObjectId(newid2); - Result update2 = ru2.update(); - assertEquals(Result.LOCK_FAILURE, update2); - assertEquals(1, db.getReflogReader("refs/heads/z").getReverseEntries().size()); - assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); - } - - public void testNewNamespaceConflictWithPackedPrefixNameExists() - throws IOException { - final String newRef = "refs/heads/master/x"; - final RefUpdate ru = updateRef(newRef); - final RevCommit newid = new RevCommit(ru.getNewObjectId()) { - // empty - }; - ru.setNewObjectId(newid); - Result update = ru.update(); - assertEquals(Result.LOCK_FAILURE, update); - assertNull(db.getReflogReader("refs/heads/master/x")); - assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); - } - - public void testNewNamespaceConflictWithLoosePrefixOfExisting() - throws IOException { - final String newRef = "refs/heads/z/a"; - final RefUpdate ru = updateRef(newRef); - final RevCommit newid = new RevCommit(ru.getNewObjectId()) { - // empty - }; - ru.setNewObjectId(newid); - Result update = ru.update(); - assertEquals(Result.NEW, update); - // end setup - final String newRef2 = "refs/heads/z"; - final RefUpdate ru2 = updateRef(newRef2); - final RevCommit newid2 = new RevCommit(ru2.getNewObjectId()) { - // empty - }; - ru.setNewObjectId(newid2); - Result update2 = ru2.update(); - assertEquals(Result.LOCK_FAILURE, update2); - assertEquals(1, db.getReflogReader("refs/heads/z/a").getReverseEntries().size()); - assertNull(db.getReflogReader("refs/heads/z")); - assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); - } - - public void testNewNamespaceConflictWithPackedPrefixOfExisting() - throws IOException { - final String newRef = "refs/heads/prefix"; - final RefUpdate ru = updateRef(newRef); - final RevCommit newid = new RevCommit(ru.getNewObjectId()) { - // empty - }; - ru.setNewObjectId(newid); - Result update = ru.update(); - assertEquals(Result.LOCK_FAILURE, update); - assertNull(db.getReflogReader("refs/heads/prefix")); - assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); - } - - /** - * Delete a ref that is pointed to by HEAD - * - * @throws IOException - */ - public void testDeleteHEADreferencedRef() throws IOException { - ObjectId pid = db.resolve("refs/heads/master^"); - RefUpdate updateRef = db.updateRef("refs/heads/master"); - updateRef.setNewObjectId(pid); - updateRef.setForceUpdate(true); - Result update = updateRef.update(); - assertEquals(Result.FORCED, update); // internal - - RefUpdate updateRef2 = db.updateRef("refs/heads/master"); - Result delete = updateRef2.delete(); - assertEquals(Result.REJECTED_CURRENT_BRANCH, delete); - assertEquals(pid, db.resolve("refs/heads/master")); - assertEquals(1,db.getReflogReader("refs/heads/master").getReverseEntries().size()); - assertEquals(0,db.getReflogReader("HEAD").getReverseEntries().size()); - } - - public void testLooseDelete() throws IOException { - final String newRef = "refs/heads/abc"; - RefUpdate ref = updateRef(newRef); - ref.update(); // create loose ref - ref = updateRef(newRef); // refresh - delete(ref, Result.NO_CHANGE); - assertNull(db.getReflogReader("refs/heads/abc")); - } - - public void testDeleteHead() throws IOException { - final RefUpdate ref = updateRef(Constants.HEAD); - delete(ref, Result.REJECTED_CURRENT_BRANCH, true, false); - assertEquals(0, db.getReflogReader("refs/heads/master").getReverseEntries().size()); - assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); - } - - /** - * Delete a loose ref and make sure the directory in refs is deleted too, - * and the reflog dir too - * - * @throws IOException - */ - public void testDeleteLooseAndItsDirectory() throws IOException { - ObjectId pid = db.resolve("refs/heads/c^"); - RefUpdate updateRef = db.updateRef("refs/heads/z/c"); - updateRef.setNewObjectId(pid); - updateRef.setForceUpdate(true); - updateRef.setRefLogMessage("new test ref", false); - Result update = updateRef.update(); - assertEquals(Result.NEW, update); // internal - assertTrue(new File(db.getDirectory(), Constants.R_HEADS + "z") - .exists()); - assertTrue(new File(db.getDirectory(), "logs/refs/heads/z").exists()); - - // The real test here - RefUpdate updateRef2 = db.updateRef("refs/heads/z/c"); - updateRef2.setForceUpdate(true); - Result delete = updateRef2.delete(); - assertEquals(Result.FORCED, delete); - assertNull(db.resolve("refs/heads/z/c")); - assertFalse(new File(db.getDirectory(), Constants.R_HEADS + "z") - .exists()); - assertFalse(new File(db.getDirectory(), "logs/refs/heads/z").exists()); - } - - public void testDeleteNotFound() throws IOException { - final RefUpdate ref = updateRef("refs/heads/xyz"); - delete(ref, Result.NEW, false, true); - } - - public void testDeleteFastForward() throws IOException { - final RefUpdate ref = updateRef("refs/heads/a"); - delete(ref, Result.FAST_FORWARD); - } - - public void testDeleteForce() throws IOException { - final RefUpdate ref = db.updateRef("refs/heads/b"); - ref.setNewObjectId(db.resolve("refs/heads/a")); - delete(ref, Result.REJECTED, true, false); - ref.setForceUpdate(true); - delete(ref, Result.FORCED); - } - - public void testRefKeySameAsName() { - Map allRefs = db.getAllRefs(); - for (Entry e : allRefs.entrySet()) { - assertEquals(e.getKey(), e.getValue().getName()); - - } - } - - /** - * Try modify a ref forward, fast forward - * - * @throws IOException - */ - public void testUpdateRefForward() throws IOException { - ObjectId ppid = db.resolve("refs/heads/master^"); - ObjectId pid = db.resolve("refs/heads/master"); - - RefUpdate updateRef = db.updateRef("refs/heads/master"); - updateRef.setNewObjectId(ppid); - updateRef.setForceUpdate(true); - Result update = updateRef.update(); - assertEquals(Result.FORCED, update); - assertEquals(ppid, db.resolve("refs/heads/master")); - - // real test - RefUpdate updateRef2 = db.updateRef("refs/heads/master"); - updateRef2.setNewObjectId(pid); - Result update2 = updateRef2.update(); - assertEquals(Result.FAST_FORWARD, update2); - assertEquals(pid, db.resolve("refs/heads/master")); - } - - /** - * Update the HEAD ref. Only it should be changed, not what it points to. - * - * @throws Exception - */ - public void testUpdateRefDetached() throws Exception { - ObjectId pid = db.resolve("refs/heads/master"); - ObjectId ppid = db.resolve("refs/heads/master^"); - RefUpdate updateRef = db.updateRef("HEAD", true); - updateRef.setForceUpdate(true); - updateRef.setNewObjectId(ppid); - Result update = updateRef.update(); - assertEquals(Result.FORCED, update); - assertEquals(ppid, db.resolve("HEAD")); - Ref ref = db.getRef("HEAD"); - assertEquals("HEAD", ref.getName()); - assertTrue("is detached", !ref.isSymbolic()); - - // the branch HEAD referred to is left untouched - assertEquals(pid, db.resolve("refs/heads/master")); - ReflogReader reflogReader = new ReflogReader(db, "HEAD"); - org.eclipse.jgit.lib.ReflogReader.Entry e = reflogReader.getReverseEntries().get(0); - assertEquals(pid, e.getOldId()); - assertEquals(ppid, e.getNewId()); - assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress()); - assertEquals("GIT_COMMITTER_NAME", e.getWho().getName()); - assertEquals(1250379778000L, e.getWho().getWhen().getTime()); - } - - /** - * Update the HEAD ref when the referenced branch is unborn - * - * @throws Exception - */ - public void testUpdateRefDetachedUnbornHead() throws Exception { - ObjectId ppid = db.resolve("refs/heads/master^"); - writeSymref("HEAD", "refs/heads/unborn"); - RefUpdate updateRef = db.updateRef("HEAD", true); - updateRef.setForceUpdate(true); - updateRef.setNewObjectId(ppid); - Result update = updateRef.update(); - assertEquals(Result.NEW, update); - assertEquals(ppid, db.resolve("HEAD")); - Ref ref = db.getRef("HEAD"); - assertEquals("HEAD", ref.getName()); - assertTrue("is detached", !ref.isSymbolic()); - - // the branch HEAD referred to is left untouched - assertNull(db.resolve("refs/heads/unborn")); - ReflogReader reflogReader = new ReflogReader(db, "HEAD"); - org.eclipse.jgit.lib.ReflogReader.Entry e = reflogReader.getReverseEntries().get(0); - assertEquals(ObjectId.zeroId(), e.getOldId()); - assertEquals(ppid, e.getNewId()); - assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress()); - assertEquals("GIT_COMMITTER_NAME", e.getWho().getName()); - assertEquals(1250379778000L, e.getWho().getWhen().getTime()); - } - - /** - * Delete a ref that exists both as packed and loose. Make sure the ref - * cannot be resolved after delete. - * - * @throws IOException - */ - public void testDeleteLoosePacked() throws IOException { - ObjectId pid = db.resolve("refs/heads/c^"); - RefUpdate updateRef = db.updateRef("refs/heads/c"); - updateRef.setNewObjectId(pid); - updateRef.setForceUpdate(true); - Result update = updateRef.update(); - assertEquals(Result.FORCED, update); // internal - - // The real test here - RefUpdate updateRef2 = db.updateRef("refs/heads/c"); - updateRef2.setForceUpdate(true); - Result delete = updateRef2.delete(); - assertEquals(Result.FORCED, delete); - assertNull(db.resolve("refs/heads/c")); - } - - /** - * Try modify a ref to same - * - * @throws IOException - */ - public void testUpdateRefNoChange() throws IOException { - ObjectId pid = db.resolve("refs/heads/master"); - RefUpdate updateRef = db.updateRef("refs/heads/master"); - updateRef.setNewObjectId(pid); - Result update = updateRef.update(); - assertEquals(Result.NO_CHANGE, update); - assertEquals(pid, db.resolve("refs/heads/master")); - } - - /** - * Test case originating from - * bug 285991 - * - * Make sure the in memory cache is updated properly after - * update of symref. This one did not fail because the - * ref was packed due to implementation issues. - * - * @throws Exception - */ - public void testRefsCacheAfterUpdate() throws Exception { - // Do not use the defalt repo for this case. - Map allRefs = db.getAllRefs(); - ObjectId oldValue = db.resolve("HEAD"); - ObjectId newValue = db.resolve("HEAD^"); - // first make HEAD refer to loose ref - RefUpdate updateRef = db.updateRef(Constants.HEAD); - updateRef.setForceUpdate(true); - updateRef.setNewObjectId(newValue); - Result update = updateRef.update(); - assertEquals(Result.FORCED, update); - - // now update that ref - updateRef = db.updateRef(Constants.HEAD); - updateRef.setForceUpdate(true); - updateRef.setNewObjectId(oldValue); - update = updateRef.update(); - assertEquals(Result.FAST_FORWARD, update); - - allRefs = db.getAllRefs(); - Ref master = allRefs.get("refs/heads/master"); - Ref head = allRefs.get("HEAD"); - assertEquals("refs/heads/master", master.getName()); - assertEquals("HEAD", head.getName()); - assertTrue("is symbolic reference", head.isSymbolic()); - assertSame(master, head.getTarget()); - } - - /** - * Test case originating from - * bug 285991 - * - * Make sure the in memory cache is updated properly after - * update of symref. - * - * @throws Exception - */ - public void testRefsCacheAfterUpdateLooseOnly() throws Exception { - // Do not use the defalt repo for this case. - Map allRefs = db.getAllRefs(); - ObjectId oldValue = db.resolve("HEAD"); - writeSymref(Constants.HEAD, "refs/heads/newref"); - RefUpdate updateRef = db.updateRef(Constants.HEAD); - updateRef.setForceUpdate(true); - updateRef.setNewObjectId(oldValue); - Result update = updateRef.update(); - assertEquals(Result.NEW, update); - - allRefs = db.getAllRefs(); - Ref head = allRefs.get("HEAD"); - Ref newref = allRefs.get("refs/heads/newref"); - assertEquals("refs/heads/newref", newref.getName()); - assertEquals("HEAD", head.getName()); - assertTrue("is symbolic reference", head.isSymbolic()); - assertSame(newref, head.getTarget()); - } - - /** - * Try modify a ref, but get wrong expected old value - * - * @throws IOException - */ - public void testUpdateRefLockFailureWrongOldValue() throws IOException { - ObjectId pid = db.resolve("refs/heads/master"); - RefUpdate updateRef = db.updateRef("refs/heads/master"); - updateRef.setNewObjectId(pid); - updateRef.setExpectedOldObjectId(db.resolve("refs/heads/master^")); - Result update = updateRef.update(); - assertEquals(Result.LOCK_FAILURE, update); - assertEquals(pid, db.resolve("refs/heads/master")); - } - - /** - * Try modify a ref forward, fast forward, checking old value first - * - * @throws IOException - */ - public void testUpdateRefForwardWithCheck1() throws IOException { - ObjectId ppid = db.resolve("refs/heads/master^"); - ObjectId pid = db.resolve("refs/heads/master"); - - RefUpdate updateRef = db.updateRef("refs/heads/master"); - updateRef.setNewObjectId(ppid); - updateRef.setForceUpdate(true); - Result update = updateRef.update(); - assertEquals(Result.FORCED, update); - assertEquals(ppid, db.resolve("refs/heads/master")); - - // real test - RefUpdate updateRef2 = db.updateRef("refs/heads/master"); - updateRef2.setExpectedOldObjectId(ppid); - updateRef2.setNewObjectId(pid); - Result update2 = updateRef2.update(); - assertEquals(Result.FAST_FORWARD, update2); - assertEquals(pid, db.resolve("refs/heads/master")); - } - - /** - * Try modify a ref forward, fast forward, checking old commit first - * - * @throws IOException - */ - public void testUpdateRefForwardWithCheck2() throws IOException { - ObjectId ppid = db.resolve("refs/heads/master^"); - ObjectId pid = db.resolve("refs/heads/master"); - - RefUpdate updateRef = db.updateRef("refs/heads/master"); - updateRef.setNewObjectId(ppid); - updateRef.setForceUpdate(true); - Result update = updateRef.update(); - assertEquals(Result.FORCED, update); - assertEquals(ppid, db.resolve("refs/heads/master")); - - // real test - RevCommit old = new RevWalk(db).parseCommit(ppid); - RefUpdate updateRef2 = db.updateRef("refs/heads/master"); - updateRef2.setExpectedOldObjectId(old); - updateRef2.setNewObjectId(pid); - Result update2 = updateRef2.update(); - assertEquals(Result.FAST_FORWARD, update2); - assertEquals(pid, db.resolve("refs/heads/master")); - } - - /** - * Try modify a ref that is locked - * - * @throws IOException - */ - public void testUpdateRefLockFailureLocked() throws IOException { - ObjectId opid = db.resolve("refs/heads/master"); - ObjectId pid = db.resolve("refs/heads/master^"); - RefUpdate updateRef = db.updateRef("refs/heads/master"); - updateRef.setNewObjectId(pid); - LockFile lockFile1 = new LockFile(new File(db.getDirectory(),"refs/heads/master")); - try { - assertTrue(lockFile1.lock()); // precondition to test - Result update = updateRef.update(); - assertEquals(Result.LOCK_FAILURE, update); - assertEquals(opid, db.resolve("refs/heads/master")); - LockFile lockFile2 = new LockFile(new File(db.getDirectory(),"refs/heads/master")); - assertFalse(lockFile2.lock()); // was locked, still is - } finally { - lockFile1.unlock(); - } - } - - /** - * Try to delete a ref. Delete requires force. - * - * @throws IOException - */ - public void testDeleteLoosePackedRejected() throws IOException { - ObjectId pid = db.resolve("refs/heads/c^"); - ObjectId oldpid = db.resolve("refs/heads/c"); - RefUpdate updateRef = db.updateRef("refs/heads/c"); - updateRef.setNewObjectId(pid); - Result update = updateRef.update(); - assertEquals(Result.REJECTED, update); - assertEquals(oldpid, db.resolve("refs/heads/c")); - } - - public void testRenameBranchNoPreviousLog() throws IOException { - assertFalse("precondition, no log on old branchg", new File(db - .getDirectory(), "logs/refs/heads/b").exists()); - ObjectId rb = db.resolve("refs/heads/b"); - ObjectId oldHead = db.resolve(Constants.HEAD); - assertFalse(rb.equals(oldHead)); // assumption for this test - RefRename renameRef = db.renameRef("refs/heads/b", - "refs/heads/new/name"); - Result result = renameRef.rename(); - assertEquals(Result.RENAMED, result); - assertEquals(rb, db.resolve("refs/heads/new/name")); - assertNull(db.resolve("refs/heads/b")); - assertEquals(1, db.getReflogReader("new/name").getReverseEntries().size()); - assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name") - .getLastEntry().getComment()); - assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists()); - assertEquals(oldHead, db.resolve(Constants.HEAD)); // unchanged - } - - public void testRenameBranchHasPreviousLog() throws IOException { - ObjectId rb = db.resolve("refs/heads/b"); - ObjectId oldHead = db.resolve(Constants.HEAD); - assertFalse("precondition for this test, branch b != HEAD", rb - .equals(oldHead)); - writeReflog(db, rb, rb, "Just a message", "refs/heads/b"); - assertTrue("log on old branch", new File(db.getDirectory(), - "logs/refs/heads/b").exists()); - RefRename renameRef = db.renameRef("refs/heads/b", - "refs/heads/new/name"); - Result result = renameRef.rename(); - assertEquals(Result.RENAMED, result); - assertEquals(rb, db.resolve("refs/heads/new/name")); - assertNull(db.resolve("refs/heads/b")); - assertEquals(2, db.getReflogReader("new/name").getReverseEntries().size()); - assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name") - .getLastEntry().getComment()); - assertEquals("Just a message", db.getReflogReader("new/name") - .getReverseEntries().get(1).getComment()); - assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists()); - assertEquals(oldHead, db.resolve(Constants.HEAD)); // unchanged - } - - public void testRenameCurrentBranch() throws IOException { - ObjectId rb = db.resolve("refs/heads/b"); - writeSymref(Constants.HEAD, "refs/heads/b"); - ObjectId oldHead = db.resolve(Constants.HEAD); - assertTrue("internal test condition, b == HEAD", rb.equals(oldHead)); - writeReflog(db, rb, rb, "Just a message", "refs/heads/b"); - assertTrue("log on old branch", new File(db.getDirectory(), - "logs/refs/heads/b").exists()); - RefRename renameRef = db.renameRef("refs/heads/b", - "refs/heads/new/name"); - Result result = renameRef.rename(); - assertEquals(Result.RENAMED, result); - assertEquals(rb, db.resolve("refs/heads/new/name")); - assertNull(db.resolve("refs/heads/b")); - assertEquals("Branch: renamed b to new/name", db.getReflogReader( - "new/name").getLastEntry().getComment()); - assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists()); - assertEquals(rb, db.resolve(Constants.HEAD)); - assertEquals(2, db.getReflogReader("new/name").getReverseEntries().size()); - assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name").getReverseEntries().get(0).getComment()); - assertEquals("Just a message", db.getReflogReader("new/name").getReverseEntries().get(1).getComment()); - } - - public void testRenameBranchAlsoInPack() throws IOException { - ObjectId rb = db.resolve("refs/heads/b"); - ObjectId rb2 = db.resolve("refs/heads/b~1"); - assertEquals(Ref.Storage.PACKED, db.getRef("refs/heads/b").getStorage()); - RefUpdate updateRef = db.updateRef("refs/heads/b"); - updateRef.setNewObjectId(rb2); - updateRef.setForceUpdate(true); - Result update = updateRef.update(); - assertEquals("internal check new ref is loose", Result.FORCED, update); - assertEquals(Ref.Storage.LOOSE, db.getRef("refs/heads/b").getStorage()); - writeReflog(db, rb, rb, "Just a message", "refs/heads/b"); - assertTrue("log on old branch", new File(db.getDirectory(), - "logs/refs/heads/b").exists()); - RefRename renameRef = db.renameRef("refs/heads/b", - "refs/heads/new/name"); - Result result = renameRef.rename(); - assertEquals(Result.RENAMED, result); - assertEquals(rb2, db.resolve("refs/heads/new/name")); - assertNull(db.resolve("refs/heads/b")); - assertEquals("Branch: renamed b to new/name", db.getReflogReader( - "new/name").getLastEntry().getComment()); - assertEquals(3, db.getReflogReader("refs/heads/new/name").getReverseEntries().size()); - assertEquals("Branch: renamed b to new/name", db.getReflogReader("refs/heads/new/name").getReverseEntries().get(0).getComment()); - assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); - // make sure b's log file is gone too. - assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists()); - - // Create new Repository instance, to reread caches and make sure our - // assumptions are persistent. - Repository ndb = new FileRepository(db.getDirectory()); - assertEquals(rb2, ndb.resolve("refs/heads/new/name")); - assertNull(ndb.resolve("refs/heads/b")); - } - - public void tryRenameWhenLocked(String toLock, String fromName, - String toName, String headPointsTo) throws IOException { - // setup - writeSymref(Constants.HEAD, headPointsTo); - ObjectId oldfromId = db.resolve(fromName); - ObjectId oldHeadId = db.resolve(Constants.HEAD); - writeReflog(db, oldfromId, oldfromId, "Just a message", - fromName); - List oldFromLog = db - .getReflogReader(fromName).getReverseEntries(); - List oldHeadLog = oldHeadId != null ? db - .getReflogReader(Constants.HEAD).getReverseEntries() : null; - - assertTrue("internal check, we have a log", new File(db.getDirectory(), - "logs/" + fromName).exists()); - - // "someone" has branch X locked - LockFile lockFile = new LockFile(new File(db.getDirectory(), toLock)); - try { - assertTrue(lockFile.lock()); - - // Now this is our test - RefRename renameRef = db.renameRef(fromName, toName); - Result result = renameRef.rename(); - assertEquals(Result.LOCK_FAILURE, result); - - // Check that the involved refs are the same despite the failure - assertExists(false, toName); - if (!toLock.equals(toName)) - assertExists(false, toName + ".lock"); - assertExists(true, toLock + ".lock"); - if (!toLock.equals(fromName)) - assertExists(false, "logs/" + fromName + ".lock"); - assertExists(false, "logs/" + toName + ".lock"); - assertEquals(oldHeadId, db.resolve(Constants.HEAD)); - assertEquals(oldfromId, db.resolve(fromName)); - assertNull(db.resolve(toName)); - assertEquals(oldFromLog.toString(), db.getReflogReader(fromName) - .getReverseEntries().toString()); - if (oldHeadId != null) - assertEquals(oldHeadLog.toString(), db.getReflogReader( - Constants.HEAD).getReverseEntries().toString()); - } finally { - lockFile.unlock(); - } - } - - private void assertExists(boolean positive, String toName) { - assertEquals(toName + (positive ? " " : " does not ") + "exist", - positive, new File(db.getDirectory(), toName).exists()); - } - - public void testRenameBranchCannotLockAFileHEADisFromLockHEAD() - throws IOException { - tryRenameWhenLocked("HEAD", "refs/heads/b", "refs/heads/new/name", - "refs/heads/b"); - } - - public void testRenameBranchCannotLockAFileHEADisFromLockFrom() - throws IOException { - tryRenameWhenLocked("refs/heads/b", "refs/heads/b", - "refs/heads/new/name", "refs/heads/b"); - } - - public void testRenameBranchCannotLockAFileHEADisFromLockTo() - throws IOException { - tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b", - "refs/heads/new/name", "refs/heads/b"); - } - - public void testRenameBranchCannotLockAFileHEADisToLockFrom() - throws IOException { - tryRenameWhenLocked("refs/heads/b", "refs/heads/b", - "refs/heads/new/name", "refs/heads/new/name"); - } - - public void testRenameBranchCannotLockAFileHEADisToLockTo() - throws IOException { - tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b", - "refs/heads/new/name", "refs/heads/new/name"); - } - - public void testRenameBranchCannotLockAFileHEADisOtherLockFrom() - throws IOException { - tryRenameWhenLocked("refs/heads/b", "refs/heads/b", - "refs/heads/new/name", "refs/heads/a"); - } - - public void testRenameBranchCannotLockAFileHEADisOtherLockTo() - throws IOException { - tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b", - "refs/heads/new/name", "refs/heads/a"); - } - - public void testRenameRefNameColission1avoided() throws IOException { - // setup - ObjectId rb = db.resolve("refs/heads/b"); - writeSymref(Constants.HEAD, "refs/heads/a"); - RefUpdate updateRef = db.updateRef("refs/heads/a"); - updateRef.setNewObjectId(rb); - updateRef.setRefLogMessage("Setup", false); - assertEquals(Result.FAST_FORWARD, updateRef.update()); - ObjectId oldHead = db.resolve(Constants.HEAD); - assertTrue(rb.equals(oldHead)); // assumption for this test - writeReflog(db, rb, rb, "Just a message", "refs/heads/a"); - assertTrue("internal check, we have a log", new File(db.getDirectory(), - "logs/refs/heads/a").exists()); - - // Now this is our test - RefRename renameRef = db.renameRef("refs/heads/a", "refs/heads/a/b"); - Result result = renameRef.rename(); - assertEquals(Result.RENAMED, result); - assertNull(db.resolve("refs/heads/a")); - assertEquals(rb, db.resolve("refs/heads/a/b")); - assertEquals(3, db.getReflogReader("a/b").getReverseEntries().size()); - assertEquals("Branch: renamed a to a/b", db.getReflogReader("a/b") - .getReverseEntries().get(0).getComment()); - assertEquals("Just a message", db.getReflogReader("a/b") - .getReverseEntries().get(1).getComment()); - assertEquals("Setup", db.getReflogReader("a/b").getReverseEntries() - .get(2).getComment()); - // same thing was logged to HEAD - assertEquals("Branch: renamed a to a/b", db.getReflogReader("HEAD") - .getReverseEntries().get(0).getComment()); - } - - public void testRenameRefNameColission2avoided() throws IOException { - // setup - ObjectId rb = db.resolve("refs/heads/b"); - writeSymref(Constants.HEAD, "refs/heads/prefix/a"); - RefUpdate updateRef = db.updateRef("refs/heads/prefix/a"); - updateRef.setNewObjectId(rb); - updateRef.setRefLogMessage("Setup", false); - updateRef.setForceUpdate(true); - assertEquals(Result.FORCED, updateRef.update()); - ObjectId oldHead = db.resolve(Constants.HEAD); - assertTrue(rb.equals(oldHead)); // assumption for this test - writeReflog(db, rb, rb, "Just a message", - "refs/heads/prefix/a"); - assertTrue("internal check, we have a log", new File(db.getDirectory(), - "logs/refs/heads/prefix/a").exists()); - - // Now this is our test - RefRename renameRef = db.renameRef("refs/heads/prefix/a", - "refs/heads/prefix"); - Result result = renameRef.rename(); - assertEquals(Result.RENAMED, result); - - assertNull(db.resolve("refs/heads/prefix/a")); - assertEquals(rb, db.resolve("refs/heads/prefix")); - assertEquals(3, db.getReflogReader("prefix").getReverseEntries().size()); - assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader( - "prefix").getReverseEntries().get(0).getComment()); - assertEquals("Just a message", db.getReflogReader("prefix") - .getReverseEntries().get(1).getComment()); - assertEquals("Setup", db.getReflogReader("prefix").getReverseEntries() - .get(2).getComment()); - assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader( - "HEAD").getReverseEntries().get(0).getComment()); - } - - private void writeReflog(Repository db, ObjectId oldId, ObjectId newId, - String msg, String refName) throws IOException { - RefDirectory refs = (RefDirectory) db.getRefDatabase(); - RefDirectoryUpdate update = refs.newUpdate(refName, true); - update.setNewObjectId(newId); - refs.log(update, msg, true); - } -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogReaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogReaderTest.java deleted file mode 100644 index 6144851fcd..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogReaderTest.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (C) 2009, Robin Rosenberg - * Copyright (C) 2009, Robin Rosenberg - * 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.lib; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.List; - -import org.eclipse.jgit.lib.ReflogReader.Entry; - -public class ReflogReaderTest extends SampleDataRepositoryTestCase { - - static byte[] oneLine = "da85355dfc525c9f6f3927b876f379f46ccf826e 3e7549db262d1e836d9bf0af7e22355468f1717c A O Thor Too 1243028200 +0200\tcommit: Add a toString for debugging to RemoteRefUpdate\n" - .getBytes(); - - static byte[] twoLine = ("0000000000000000000000000000000000000000 c6734895958052a9dbc396cff4459dc1a25029ab A U Thor 1243028201 -0100\tbranch: Created from rr/renamebranchv4\n" - + "c6734895958052a9dbc396cff4459dc1a25029ab 54794942a18a237c57a80719afed44bb78172b10 Same A U Thor 1243028202 +0100\trebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d\n") - .getBytes(); - - static byte[] twoLineWithAppendInProgress = ("0000000000000000000000000000000000000000 c6734895958052a9dbc396cff4459dc1a25029ab A U Thor 1243028201 -0100\tbranch: Created from rr/renamebranchv4\n" - + "c6734895958052a9dbc396cff4459dc1a25029ab 54794942a18a237c57a80719afed44bb78172b10 Same A U Thor 1243028202 +0100\trebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d\n" - + "54794942a18a237c57a80719afed44bb78172b10 ") - .getBytes(); - - static byte[] aLine = "1111111111111111111111111111111111111111 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor 1243028201 -0100\tbranch: change to a\n" - .getBytes(); - - static byte[] masterLine = "2222222222222222222222222222222222222222 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor 1243028201 -0100\tbranch: change to master\n" - .getBytes(); - - static byte[] headLine = "3333333333333333333333333333333333333333 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor 1243028201 -0100\tbranch: change to HEAD\n" - .getBytes(); - - public void testReadOneLine() throws Exception { - setupReflog("logs/refs/heads/master", oneLine); - - ReflogReader reader = new ReflogReader(db, "refs/heads/master"); - Entry e = reader.getLastEntry(); - assertEquals(ObjectId - .fromString("da85355dfc525c9f6f3927b876f379f46ccf826e"), e - .getOldId()); - assertEquals(ObjectId - .fromString("3e7549db262d1e836d9bf0af7e22355468f1717c"), e - .getNewId()); - assertEquals("A O Thor Too", e.getWho().getName()); - assertEquals("authortoo@wri.tr", e.getWho().getEmailAddress()); - assertEquals(120, e.getWho().getTimeZoneOffset()); - assertEquals("2009-05-22T23:36:40", iso(e.getWho())); - assertEquals("commit: Add a toString for debugging to RemoteRefUpdate", - e.getComment()); - } - - private String iso(PersonIdent id) { - final SimpleDateFormat fmt; - fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - fmt.setTimeZone(id.getTimeZone()); - return fmt.format(id.getWhen()); - } - - public void testReadTwoLine() throws Exception { - setupReflog("logs/refs/heads/master", twoLine); - - ReflogReader reader = new ReflogReader(db, "refs/heads/master"); - List reverseEntries = reader.getReverseEntries(); - assertEquals(2, reverseEntries.size()); - Entry e = reverseEntries.get(0); - assertEquals(ObjectId - .fromString("c6734895958052a9dbc396cff4459dc1a25029ab"), e - .getOldId()); - assertEquals(ObjectId - .fromString("54794942a18a237c57a80719afed44bb78172b10"), e - .getNewId()); - assertEquals("Same A U Thor", e.getWho().getName()); - assertEquals("same.author@example.com", e.getWho().getEmailAddress()); - assertEquals(60, e.getWho().getTimeZoneOffset()); - assertEquals("2009-05-22T22:36:42", iso(e.getWho())); - assertEquals( - "rebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d", - e.getComment()); - - e = reverseEntries.get(1); - assertEquals(ObjectId - .fromString("0000000000000000000000000000000000000000"), e - .getOldId()); - assertEquals(ObjectId - .fromString("c6734895958052a9dbc396cff4459dc1a25029ab"), e - .getNewId()); - assertEquals("A U Thor", e.getWho().getName()); - assertEquals("thor@committer.au", e.getWho().getEmailAddress()); - assertEquals(-60, e.getWho().getTimeZoneOffset()); - assertEquals("2009-05-22T20:36:41", iso(e.getWho())); - assertEquals("branch: Created from rr/renamebranchv4", e.getComment()); - } - - public void testReadWhileAppendIsInProgress() throws Exception { - setupReflog("logs/refs/heads/master", twoLineWithAppendInProgress); - ReflogReader reader = new ReflogReader(db, "refs/heads/master"); - List reverseEntries = reader.getReverseEntries(); - assertEquals(2, reverseEntries.size()); - Entry e = reverseEntries.get(0); - assertEquals(ObjectId - .fromString("c6734895958052a9dbc396cff4459dc1a25029ab"), e - .getOldId()); - assertEquals(ObjectId - .fromString("54794942a18a237c57a80719afed44bb78172b10"), e - .getNewId()); - assertEquals("Same A U Thor", e.getWho().getName()); - assertEquals("same.author@example.com", e.getWho().getEmailAddress()); - assertEquals(60, e.getWho().getTimeZoneOffset()); - assertEquals("2009-05-22T22:36:42", iso(e.getWho())); - assertEquals( - "rebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d", - e.getComment()); - // while similar to testReadTwoLine, we can assume that if we get the last entry - // right, everything else is too - } - - - public void testReadRightLog() throws Exception { - setupReflog("logs/refs/heads/a", aLine); - setupReflog("logs/refs/heads/master", masterLine); - setupReflog("logs/HEAD", headLine); - assertEquals("branch: change to master", db.getReflogReader("master") - .getLastEntry().getComment()); - assertEquals("branch: change to a", db.getReflogReader("a") - .getLastEntry().getComment()); - assertEquals("branch: change to HEAD", db.getReflogReader("HEAD") - .getLastEntry().getComment()); - } - - public void testNoLog() throws Exception { - assertEquals(0, db.getReflogReader("master").getReverseEntries().size()); - assertNull(db.getReflogReader("master").getLastEntry()); - } - - private void setupReflog(String logName, byte[] data) - throws FileNotFoundException, IOException { - File logfile = new File(db.getDirectory(), logName); - if (!logfile.getParentFile().mkdirs() - && !logfile.getParentFile().isDirectory()) { - throw new IOException( - "oops, cannot create the directory for the test reflog file" - + logfile); - } - FileOutputStream fileOutputStream = new FileOutputStream(logfile); - try { - fileOutputStream.write(data); - } finally { - fileOutputStream.close(); - } - } - -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java deleted file mode 100644 index 52cb46bae1..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (C) 2010, Mathias Kinzler - * - * 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.lib; - -import java.io.File; -import java.io.IOException; - -import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; - -/** - * Tests for setting up the working directory when creating a Repository - */ -public class RepositorySetupWorkDirTest extends LocalDiskRepositoryTestCase { - - public void testIsBare_CreateRepositoryFromArbitraryGitDir() - throws Exception { - File gitDir = getFile("workdir"); - assertTrue(new FileRepository(gitDir).isBare()); - } - - public void testNotBare_CreateRepositoryFromDotGitGitDir() throws Exception { - File gitDir = getFile("workdir", Constants.DOT_GIT); - Repository repo = new FileRepository(gitDir); - assertFalse(repo.isBare()); - assertWorkdirPath(repo, "workdir"); - assertGitdirPath(repo, "workdir", Constants.DOT_GIT); - } - - public void testWorkdirIsParentDir_CreateRepositoryFromDotGitGitDir() - throws Exception { - File gitDir = getFile("workdir", Constants.DOT_GIT); - Repository repo = new FileRepository(gitDir); - String workdir = repo.getWorkTree().getName(); - assertEquals(workdir, "workdir"); - } - - public void testNotBare_CreateRepositoryFromWorkDirOnly() throws Exception { - File workdir = getFile("workdir", "repo"); - FileRepository repo = new FileRepositoryBuilder().setWorkTree(workdir).build(); - assertFalse(repo.isBare()); - assertWorkdirPath(repo, "workdir", "repo"); - assertGitdirPath(repo, "workdir", "repo", Constants.DOT_GIT); - } - - public void testWorkdirIsDotGit_CreateRepositoryFromWorkDirOnly() - throws Exception { - File workdir = getFile("workdir", "repo"); - FileRepository repo = new FileRepositoryBuilder().setWorkTree(workdir).build(); - assertGitdirPath(repo, "workdir", "repo", Constants.DOT_GIT); - } - - public void testNotBare_CreateRepositoryFromGitDirOnlyWithWorktreeConfig() - throws Exception { - File gitDir = getFile("workdir", "repoWithConfig"); - File workTree = getFile("workdir", "treeRoot"); - setWorkTree(gitDir, workTree); - FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); - assertFalse(repo.isBare()); - assertWorkdirPath(repo, "workdir", "treeRoot"); - assertGitdirPath(repo, "workdir", "repoWithConfig"); - } - - public void testBare_CreateRepositoryFromGitDirOnlyWithBareConfigTrue() - throws Exception { - File gitDir = getFile("workdir", "repoWithConfig"); - setBare(gitDir, true); - FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); - assertTrue(repo.isBare()); - } - - public void testWorkdirIsParent_CreateRepositoryFromGitDirOnlyWithBareConfigFalse() - throws Exception { - File gitDir = getFile("workdir", "repoWithBareConfigTrue", "child"); - setBare(gitDir, false); - FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); - assertWorkdirPath(repo, "workdir", "repoWithBareConfigTrue"); - } - - public void testNotBare_CreateRepositoryFromGitDirOnlyWithBareConfigFalse() - throws Exception { - File gitDir = getFile("workdir", "repoWithBareConfigFalse", "child"); - setBare(gitDir, false); - FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); - assertFalse(repo.isBare()); - assertWorkdirPath(repo, "workdir", "repoWithBareConfigFalse"); - assertGitdirPath(repo, "workdir", "repoWithBareConfigFalse", "child"); - } - - public void testExceptionThrown_BareRepoGetWorkDir() throws Exception { - File gitDir = getFile("workdir"); - try { - new FileRepository(gitDir).getWorkTree(); - fail("Expected IllegalStateException missing"); - } catch (IllegalStateException e) { - // expected - } - } - - public void testExceptionThrown_BareRepoGetIndex() throws Exception { - File gitDir = getFile("workdir"); - try { - new FileRepository(gitDir).getIndex(); - fail("Expected IllegalStateException missing"); - } catch (IllegalStateException e) { - // expected - } - } - - public void testExceptionThrown_BareRepoGetIndexFile() throws Exception { - File gitDir = getFile("workdir"); - try { - new FileRepository(gitDir).getIndexFile(); - fail("Expected Exception missing"); - } catch (IllegalStateException e) { - // expected - } - } - - private File getFile(String... pathComponents) { - String rootPath = new File(new File("target"), "trash").getPath(); - for (String pathComponent : pathComponents) - rootPath = rootPath + File.separatorChar + pathComponent; - File result = new File(rootPath); - result.mkdir(); - return result; - } - - private void setBare(File gitDir, boolean bare) throws IOException, - ConfigInvalidException { - FileBasedConfig cfg = configFor(gitDir); - cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_BARE, bare); - cfg.save(); - } - - private void setWorkTree(File gitDir, File workTree) throws IOException, - ConfigInvalidException { - String path = workTree.getAbsolutePath(); - FileBasedConfig cfg = configFor(gitDir); - cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_WORKTREE, path); - cfg.save(); - } - - private FileBasedConfig configFor(File gitDir) throws IOException, - ConfigInvalidException { - FileBasedConfig cfg = new FileBasedConfig(new File(gitDir, "config")); - cfg.load(); - return cfg; - } - - private void assertGitdirPath(Repository repo, String... expected) - throws IOException { - File exp = getFile(expected).getCanonicalFile(); - File act = repo.getDirectory().getCanonicalFile(); - assertEquals("Wrong Git Directory", exp, act); - } - - private void assertWorkdirPath(Repository repo, String... expected) - throws IOException { - File exp = getFile(expected).getCanonicalFile(); - File act = repo.getWorkTree().getCanonicalFile(); - assertEquals("Wrong working Directory", exp, act); - } -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java index cc45722447..e78f8512a2 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java @@ -54,6 +54,7 @@ import java.io.InputStreamReader; import java.io.Reader; import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.storage.file.FileRepository; /** * Base class for most JGit unit tests. diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java deleted file mode 100644 index 484692ee21..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java +++ /dev/null @@ -1,754 +0,0 @@ -/* - * Copyright (C) 2007, Dave Watson - * Copyright (C) 2007-2010, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; - -import org.eclipse.jgit.errors.ConfigInvalidException; - -public class T0003_Basic extends SampleDataRepositoryTestCase { - public void test001_Initalize() { - final File gitdir = new File(trash, Constants.DOT_GIT); - final File objects = new File(gitdir, "objects"); - final File objects_pack = new File(objects, "pack"); - final File objects_info = new File(objects, "info"); - final File refs = new File(gitdir, "refs"); - final File refs_heads = new File(refs, "heads"); - final File refs_tags = new File(refs, "tags"); - final File HEAD = new File(gitdir, "HEAD"); - - assertTrue("Exists " + trash, trash.isDirectory()); - assertTrue("Exists " + objects, objects.isDirectory()); - assertTrue("Exists " + objects_pack, objects_pack.isDirectory()); - assertTrue("Exists " + objects_info, objects_info.isDirectory()); - assertEquals(2, objects.listFiles().length); - assertTrue("Exists " + refs, refs.isDirectory()); - assertTrue("Exists " + refs_heads, refs_heads.isDirectory()); - assertTrue("Exists " + refs_tags, refs_tags.isDirectory()); - assertTrue("Exists " + HEAD, HEAD.isFile()); - assertEquals(23, HEAD.length()); - } - - public void test000_openRepoBadArgs() throws IOException { - try { - new FileRepositoryBuilder().build(); - fail("Must pass either GIT_DIR or GIT_WORK_TREE"); - } catch (IllegalArgumentException e) { - assertEquals( - "Either GIT_DIR or GIT_WORK_TREE must be passed to Repository constructor", - e.getMessage()); - } - } - - /** - * Check the default rules for looking up directories and files within a - * repo when the gitDir is given. - * - * @throws IOException - */ - public void test000_openrepo_default_gitDirSet() throws IOException { - File repo1Parent = new File(trash.getParentFile(), "r1"); - Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); - repo1initial.create(); - repo1initial.close(); - - File theDir = new File(repo1Parent, Constants.DOT_GIT); - FileRepository r = new FileRepositoryBuilder().setGitDir(theDir).build(); - assertEqualsPath(theDir, r.getDirectory()); - assertEqualsPath(repo1Parent, r.getWorkTree()); - assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); - assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory()); - } - - /** - * Check that we can pass both a git directory and a work tree - * repo when the gitDir is given. - * - * @throws IOException - */ - public void test000_openrepo_default_gitDirAndWorkTreeSet() throws IOException { - File repo1Parent = new File(trash.getParentFile(), "r1"); - Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); - repo1initial.create(); - repo1initial.close(); - - File theDir = new File(repo1Parent, Constants.DOT_GIT); - FileRepository r = new FileRepositoryBuilder().setGitDir(theDir) - .setWorkTree(repo1Parent.getParentFile()).build(); - assertEqualsPath(theDir, r.getDirectory()); - assertEqualsPath(repo1Parent.getParentFile(), r.getWorkTree()); - assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); - assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory()); - } - - /** - * Check the default rules for looking up directories and files within a - * repo when the workTree is given. - * - * @throws IOException - */ - public void test000_openrepo_default_workDirSet() throws IOException { - File repo1Parent = new File(trash.getParentFile(), "r1"); - Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); - repo1initial.create(); - repo1initial.close(); - - File theDir = new File(repo1Parent, Constants.DOT_GIT); - FileRepository r = new FileRepositoryBuilder().setWorkTree(repo1Parent).build(); - assertEqualsPath(theDir, r.getDirectory()); - assertEqualsPath(repo1Parent, r.getWorkTree()); - assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); - assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory()); - } - - /** - * Check that worktree config has an effect, given absolute path. - * - * @throws IOException - */ - public void test000_openrepo_default_absolute_workdirconfig() - throws IOException { - File repo1Parent = new File(trash.getParentFile(), "r1"); - File workdir = new File(trash.getParentFile(), "rw"); - workdir.mkdir(); - FileRepository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); - repo1initial.create(); - repo1initial.getConfig().setString("core", null, "worktree", - workdir.getAbsolutePath()); - repo1initial.getConfig().save(); - repo1initial.close(); - - File theDir = new File(repo1Parent, Constants.DOT_GIT); - FileRepository r = new FileRepositoryBuilder().setGitDir(theDir).build(); - assertEqualsPath(theDir, r.getDirectory()); - assertEqualsPath(workdir, r.getWorkTree()); - assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); - assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory()); - } - - /** - * Check that worktree config has an effect, given a relative path. - * - * @throws IOException - */ - public void test000_openrepo_default_relative_workdirconfig() - throws IOException { - File repo1Parent = new File(trash.getParentFile(), "r1"); - File workdir = new File(trash.getParentFile(), "rw"); - workdir.mkdir(); - FileRepository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); - repo1initial.create(); - repo1initial.getConfig() - .setString("core", null, "worktree", "../../rw"); - repo1initial.getConfig().save(); - repo1initial.close(); - - File theDir = new File(repo1Parent, Constants.DOT_GIT); - FileRepository r = new FileRepositoryBuilder().setGitDir(theDir).build(); - assertEqualsPath(theDir, r.getDirectory()); - assertEqualsPath(workdir, r.getWorkTree()); - assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); - assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory()); - } - - /** - * Check that the given index file is honored and the alternate object - * directories too - * - * @throws IOException - */ - public void test000_openrepo_alternate_index_file_and_objdirs() - throws IOException { - File repo1Parent = new File(trash.getParentFile(), "r1"); - File indexFile = new File(trash, "idx"); - File objDir = new File(trash, "../obj"); - File altObjDir = db.getObjectDatabase().getDirectory(); - Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); - repo1initial.create(); - repo1initial.close(); - - File theDir = new File(repo1Parent, Constants.DOT_GIT); - FileRepository r = new FileRepositoryBuilder() // - .setGitDir(theDir).setObjectDirectory(objDir) // - .addAlternateObjectDirectory(altObjDir) // - .setIndexFile(indexFile) // - .build(); - assertEqualsPath(theDir, r.getDirectory()); - assertEqualsPath(theDir.getParentFile(), r.getWorkTree()); - assertEqualsPath(indexFile, r.getIndexFile()); - assertEqualsPath(objDir, r.getObjectDatabase().getDirectory()); - assertNotNull(r.mapCommit("6db9c2ebf75590eef973081736730a9ea169a0c4")); - // Must close or the default repo pack files created by this test gets - // locked via the alternate object directories on Windows. - r.close(); - } - - protected void assertEqualsPath(File expected, File actual) - throws IOException { - assertEquals(expected.getCanonicalPath(), actual.getCanonicalPath()); - } - - public void test002_WriteEmptyTree() throws IOException { - // One of our test packs contains the empty tree object. If the pack is - // open when we create it we won't write the object file out as a loose - // object (as it already exists in the pack). - // - final Repository newdb = createBareRepository(); - final Tree t = new Tree(newdb); - t.accept(new WriteTree(trash, newdb), TreeEntry.MODIFIED_ONLY); - assertEquals("4b825dc642cb6eb9a060e54bf8d69288fbee4904", t.getId() - .name()); - final File o = new File(new File(new File(newdb.getDirectory(), - "objects"), "4b"), "825dc642cb6eb9a060e54bf8d69288fbee4904"); - assertTrue("Exists " + o, o.isFile()); - assertTrue("Read-only " + o, !o.canWrite()); - } - - public void test002_WriteEmptyTree2() throws IOException { - // File shouldn't exist as it is in a test pack. - // - final Tree t = new Tree(db); - t.accept(new WriteTree(trash, db), TreeEntry.MODIFIED_ONLY); - assertEquals("4b825dc642cb6eb9a060e54bf8d69288fbee4904", t.getId() - .name()); - final File o = new File(new File( - new File(db.getDirectory(), "objects"), "4b"), - "825dc642cb6eb9a060e54bf8d69288fbee4904"); - assertFalse("Exists " + o, o.isFile()); - } - - public void test003_WriteShouldBeEmptyTree() throws IOException { - final Tree t = new Tree(db); - final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); - t.addFile("should-be-empty").setId(emptyId); - t.accept(new WriteTree(trash, db), TreeEntry.MODIFIED_ONLY); - assertEquals("7bb943559a305bdd6bdee2cef6e5df2413c3d30a", t.getId() - .name()); - - File o; - o = new File(new File(new File(db.getDirectory(), "objects"), "7b"), - "b943559a305bdd6bdee2cef6e5df2413c3d30a"); - assertTrue("Exists " + o, o.isFile()); - assertTrue("Read-only " + o, !o.canWrite()); - - o = new File(new File(new File(db.getDirectory(), "objects"), "e6"), - "9de29bb2d1d6434b8b29ae775ad8c2e48c5391"); - assertTrue("Exists " + o, o.isFile()); - assertTrue("Read-only " + o, !o.canWrite()); - } - - public void test005_ReadSimpleConfig() { - final Config c = db.getConfig(); - assertNotNull(c); - assertEquals("0", c.getString("core", null, "repositoryformatversion")); - assertEquals("0", c.getString("CoRe", null, "REPOSITORYFoRmAtVeRsIoN")); - assertEquals("true", c.getString("core", null, "filemode")); - assertEquals("true", c.getString("cOrE", null, "fIlEModE")); - assertNull(c.getString("notavalue", null, "reallyNotAValue")); - } - - public void test006_ReadUglyConfig() throws IOException, - ConfigInvalidException { - final File cfg = new File(db.getDirectory(), "config"); - final FileBasedConfig c = new FileBasedConfig(cfg); - final FileWriter pw = new FileWriter(cfg); - final String configStr = " [core];comment\n\tfilemode = yes\n" - + "[user]\n" - + " email = A U Thor # Just an example...\n" - + " name = \"A Thor \\\\ \\\"\\t \"\n" - + " defaultCheckInComment = a many line\\n\\\ncomment\\n\\\n" - + " to test\n"; - pw.write(configStr); - pw.close(); - c.load(); - assertEquals("yes", c.getString("core", null, "filemode")); - assertEquals("A U Thor ", c - .getString("user", null, "email")); - assertEquals("A Thor \\ \"\t ", c.getString("user", null, "name")); - assertEquals("a many line\ncomment\n to test", c.getString("user", - null, "defaultCheckInComment")); - c.save(); - final FileReader fr = new FileReader(cfg); - final char[] cbuf = new char[configStr.length()]; - fr.read(cbuf); - fr.close(); - assertEquals(configStr, new String(cbuf)); - } - - public void test007_Open() throws IOException { - final FileRepository db2 = new FileRepository(db.getDirectory()); - assertEquals(db.getDirectory(), db2.getDirectory()); - assertEquals(db.getObjectDatabase().getDirectory(), db2.getObjectDatabase().getDirectory()); - assertNotSame(db.getConfig(), db2.getConfig()); - } - - public void test008_FailOnWrongVersion() throws IOException { - final File cfg = new File(db.getDirectory(), "config"); - final FileWriter pw = new FileWriter(cfg); - final String badvers = "ihopethisisneveraversion"; - final String configStr = "[core]\n" + "\trepositoryFormatVersion=" - + badvers + "\n"; - pw.write(configStr); - pw.close(); - - try { - new FileRepository(db.getDirectory()); - fail("incorrectly opened a bad repository"); - } catch (IOException ioe) { - assertTrue(ioe.getMessage().indexOf("format") > 0); - assertTrue(ioe.getMessage().indexOf(badvers) > 0); - } - } - - public void test009_CreateCommitOldFormat() throws IOException { - final Tree t = new Tree(db); - final FileTreeEntry f = t.addFile("i-am-a-file"); - writeTrashFile(f.getName(), "and this is the data in me\n"); - t.accept(new WriteTree(trash, db), TreeEntry.MODIFIED_ONLY); - assertEquals(ObjectId.fromString("00b1f73724f493096d1ffa0b0f1f1482dbb8c936"), - t.getTreeId()); - - final Commit c = new Commit(db); - c.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); - c.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60)); - c.setMessage("A Commit\n"); - c.setTree(t); - assertEquals(t.getTreeId(), c.getTreeId()); - c.commit(); - final ObjectId cmtid = ObjectId.fromString( - "803aec4aba175e8ab1d666873c984c0308179099"); - assertEquals(cmtid, c.getCommitId()); - - // Verify the commit we just wrote is in the correct format. - ObjectDatabase odb = db.getObjectDatabase(); - assertTrue("is ObjectDirectory", odb instanceof ObjectDirectory); - final XInputStream xis = new XInputStream(new FileInputStream( - ((ObjectDirectory) odb).fileFor(cmtid))); - try { - assertEquals(0x78, xis.readUInt8()); - assertEquals(0x9c, xis.readUInt8()); - assertTrue(0x789c % 31 == 0); - } finally { - xis.close(); - } - - // Verify we can read it. - final Commit c2 = db.mapCommit(cmtid); - assertNotNull(c2); - assertEquals(c.getMessage(), c2.getMessage()); - assertEquals(c.getTreeId(), c2.getTreeId()); - assertEquals(c.getAuthor(), c2.getAuthor()); - assertEquals(c.getCommitter(), c2.getCommitter()); - } - - public void test012_SubtreeExternalSorting() throws IOException { - final ObjectId emptyBlob = new ObjectWriter(db).writeBlob(new byte[0]); - final Tree t = new Tree(db); - final FileTreeEntry e0 = t.addFile("a-"); - final FileTreeEntry e1 = t.addFile("a-b"); - final FileTreeEntry e2 = t.addFile("a/b"); - final FileTreeEntry e3 = t.addFile("a="); - final FileTreeEntry e4 = t.addFile("a=b"); - - e0.setId(emptyBlob); - e1.setId(emptyBlob); - e2.setId(emptyBlob); - e3.setId(emptyBlob); - e4.setId(emptyBlob); - - t.accept(new WriteTree(trash, db), TreeEntry.MODIFIED_ONLY); - assertEquals(ObjectId.fromString("b47a8f0a4190f7572e11212769090523e23eb1ea"), - t.getId()); - } - - public void test020_createBlobTag() throws IOException { - final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); - final Tag t = new Tag(db); - t.setObjId(emptyId); - t.setType("blob"); - t.setTag("test020"); - t.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); - t.setMessage("test020 tagged\n"); - t.tag(); - assertEquals("6759556b09fbb4fd8ae5e315134481cc25d46954", t.getTagId().name()); - - Tag mapTag = db.mapTag("test020"); - assertEquals("blob", mapTag.getType()); - assertEquals("test020 tagged\n", mapTag.getMessage()); - assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag.getAuthor()); - assertEquals("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", mapTag.getObjId().name()); - } - - public void test020b_createBlobPlainTag() throws IOException { - test020_createBlobTag(); - Tag t = new Tag(db); - t.setTag("test020b"); - t.setObjId(ObjectId.fromString("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")); - t.tag(); - - Tag mapTag = db.mapTag("test020b"); - assertEquals("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", mapTag.getObjId().name()); - - // We do not repeat the plain tag test for other object types - } - - public void test021_createTreeTag() throws IOException { - final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); - final Tree almostEmptyTree = new Tree(db); - almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, "empty".getBytes(), false)); - final ObjectId almostEmptyTreeId = new ObjectWriter(db).writeTree(almostEmptyTree); - final Tag t = new Tag(db); - t.setObjId(almostEmptyTreeId); - t.setType("tree"); - t.setTag("test021"); - t.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); - t.setMessage("test021 tagged\n"); - t.tag(); - assertEquals("b0517bc8dbe2096b419d42424cd7030733f4abe5", t.getTagId().name()); - - Tag mapTag = db.mapTag("test021"); - assertEquals("tree", mapTag.getType()); - assertEquals("test021 tagged\n", mapTag.getMessage()); - assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag.getAuthor()); - assertEquals("417c01c8795a35b8e835113a85a5c0c1c77f67fb", mapTag.getObjId().name()); - } - - public void test022_createCommitTag() throws IOException { - final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); - final Tree almostEmptyTree = new Tree(db); - almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, "empty".getBytes(), false)); - final ObjectId almostEmptyTreeId = new ObjectWriter(db).writeTree(almostEmptyTree); - final Commit almostEmptyCommit = new Commit(db); - almostEmptyCommit.setAuthor(new PersonIdent(author, 1154236443000L, -2 * 60)); // not exactly the same - almostEmptyCommit.setCommitter(new PersonIdent(author, 1154236443000L, -2 * 60)); - almostEmptyCommit.setMessage("test022\n"); - almostEmptyCommit.setTreeId(almostEmptyTreeId); - ObjectId almostEmptyCommitId = new ObjectWriter(db).writeCommit(almostEmptyCommit); - final Tag t = new Tag(db); - t.setObjId(almostEmptyCommitId); - t.setType("commit"); - t.setTag("test022"); - t.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); - t.setMessage("test022 tagged\n"); - t.tag(); - assertEquals("0ce2ebdb36076ef0b38adbe077a07d43b43e3807", t.getTagId().name()); - - Tag mapTag = db.mapTag("test022"); - assertEquals("commit", mapTag.getType()); - assertEquals("test022 tagged\n", mapTag.getMessage()); - assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag.getAuthor()); - assertEquals("b5d3b45a96b340441f5abb9080411705c51cc86c", mapTag.getObjId().name()); - } - - public void test023_createCommitNonAnullii() throws IOException { - final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); - final Tree almostEmptyTree = new Tree(db); - almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, "empty".getBytes(), false)); - final ObjectId almostEmptyTreeId = new ObjectWriter(db).writeTree(almostEmptyTree); - Commit commit = new Commit(db); - commit.setTreeId(almostEmptyTreeId); - commit.setAuthor(new PersonIdent("Joe H\u00e4cker","joe@example.com",4294967295000L,60)); - commit.setCommitter(new PersonIdent("Joe Hacker","joe2@example.com",4294967295000L,60)); - commit.setEncoding("UTF-8"); - commit.setMessage("\u00dcbergeeks"); - ObjectId cid = new ObjectWriter(db).writeCommit(commit); - assertEquals("4680908112778718f37e686cbebcc912730b3154", cid.name()); - Commit loadedCommit = db.mapCommit(cid); - assertNotSame(loadedCommit, commit); - assertEquals(commit.getMessage(), loadedCommit.getMessage()); - } - - public void test024_createCommitNonAscii() throws IOException { - final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); - final Tree almostEmptyTree = new Tree(db); - almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, "empty".getBytes(), false)); - final ObjectId almostEmptyTreeId = new ObjectWriter(db).writeTree(almostEmptyTree); - Commit commit = new Commit(db); - commit.setTreeId(almostEmptyTreeId); - commit.setAuthor(new PersonIdent("Joe H\u00e4cker","joe@example.com",4294967295000L,60)); - commit.setCommitter(new PersonIdent("Joe Hacker","joe2@example.com",4294967295000L,60)); - commit.setEncoding("ISO-8859-1"); - commit.setMessage("\u00dcbergeeks"); - ObjectId cid = new ObjectWriter(db).writeCommit(commit); - assertEquals("2979b39d385014b33287054b87f77bcb3ecb5ebf", cid.name()); - } - - public void test025_packedRefs() throws IOException { - test020_createBlobTag(); - test021_createTreeTag(); - test022_createCommitTag(); - - if (!new File(db.getDirectory(),"refs/tags/test020").delete()) throw new Error("Cannot delete unpacked tag"); - if (!new File(db.getDirectory(),"refs/tags/test021").delete()) throw new Error("Cannot delete unpacked tag"); - if (!new File(db.getDirectory(),"refs/tags/test022").delete()) throw new Error("Cannot delete unpacked tag"); - - // We cannot resolve it now, since we have no ref - Tag mapTag20missing = db.mapTag("test020"); - assertNull(mapTag20missing); - - // Construct packed refs file - PrintWriter w = new PrintWriter(new FileWriter(new File(db.getDirectory(), "packed-refs"))); - w.println("# packed-refs with: peeled"); - w.println("6759556b09fbb4fd8ae5e315134481cc25d46954 refs/tags/test020"); - w.println("^e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"); - w.println("b0517bc8dbe2096b419d42424cd7030733f4abe5 refs/tags/test021"); - w.println("^417c01c8795a35b8e835113a85a5c0c1c77f67fb"); - w.println("0ce2ebdb36076ef0b38adbe077a07d43b43e3807 refs/tags/test022"); - w.println("^b5d3b45a96b340441f5abb9080411705c51cc86c"); - w.close(); - ((RefDirectory)db.getRefDatabase()).rescan(); - - Tag mapTag20 = db.mapTag("test020"); - assertNotNull("have tag test020", mapTag20); - assertEquals("blob", mapTag20.getType()); - assertEquals("test020 tagged\n", mapTag20.getMessage()); - assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag20.getAuthor()); - assertEquals("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", mapTag20.getObjId().name()); - - Tag mapTag21 = db.mapTag("test021"); - assertEquals("tree", mapTag21.getType()); - assertEquals("test021 tagged\n", mapTag21.getMessage()); - assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag21.getAuthor()); - assertEquals("417c01c8795a35b8e835113a85a5c0c1c77f67fb", mapTag21.getObjId().name()); - - Tag mapTag22 = db.mapTag("test022"); - assertEquals("commit", mapTag22.getType()); - assertEquals("test022 tagged\n", mapTag22.getMessage()); - assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag22.getAuthor()); - assertEquals("b5d3b45a96b340441f5abb9080411705c51cc86c", mapTag22.getObjId().name()); - } - - public void test025_computeSha1NoStore() throws IOException { - byte[] data = "test025 some data, more than 16 bytes to get good coverage" - .getBytes("ISO-8859-1"); - // TODO: but we do not test legacy header writing - final ObjectId id = new ObjectWriter(db).computeBlobSha1(data.length, - new ByteArrayInputStream(data)); - assertEquals("4f561df5ecf0dfbd53a0dc0f37262fef075d9dde", id.name()); - } - - public void test026_CreateCommitMultipleparents() throws IOException { - final Tree t = new Tree(db); - final FileTreeEntry f = t.addFile("i-am-a-file"); - writeTrashFile(f.getName(), "and this is the data in me\n"); - t.accept(new WriteTree(trash, db), TreeEntry.MODIFIED_ONLY); - assertEquals(ObjectId.fromString("00b1f73724f493096d1ffa0b0f1f1482dbb8c936"), - t.getTreeId()); - - final Commit c1 = new Commit(db); - c1.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); - c1.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60)); - c1.setMessage("A Commit\n"); - c1.setTree(t); - assertEquals(t.getTreeId(), c1.getTreeId()); - c1.commit(); - final ObjectId cmtid1 = ObjectId.fromString( - "803aec4aba175e8ab1d666873c984c0308179099"); - assertEquals(cmtid1, c1.getCommitId()); - - final Commit c2 = new Commit(db); - c2.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); - c2.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60)); - c2.setMessage("A Commit 2\n"); - c2.setTree(t); - assertEquals(t.getTreeId(), c2.getTreeId()); - c2.setParentIds(new ObjectId[] { c1.getCommitId() } ); - c2.commit(); - final ObjectId cmtid2 = ObjectId.fromString( - "95d068687c91c5c044fb8c77c5154d5247901553"); - assertEquals(cmtid2, c2.getCommitId()); - - Commit rm2 = db.mapCommit(cmtid2); - assertNotSame(c2, rm2); // assert the parsed objects is not from the cache - assertEquals(c2.getAuthor(), rm2.getAuthor()); - assertEquals(c2.getCommitId(), rm2.getCommitId()); - assertEquals(c2.getMessage(), rm2.getMessage()); - assertEquals(c2.getTree().getTreeId(), rm2.getTree().getTreeId()); - assertEquals(1, rm2.getParentIds().length); - assertEquals(c1.getCommitId(), rm2.getParentIds()[0]); - - final Commit c3 = new Commit(db); - c3.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); - c3.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60)); - c3.setMessage("A Commit 3\n"); - c3.setTree(t); - assertEquals(t.getTreeId(), c3.getTreeId()); - c3.setParentIds(new ObjectId[] { c1.getCommitId(), c2.getCommitId() }); - c3.commit(); - final ObjectId cmtid3 = ObjectId.fromString( - "ce6e1ce48fbeeb15a83f628dc8dc2debefa066f4"); - assertEquals(cmtid3, c3.getCommitId()); - - Commit rm3 = db.mapCommit(cmtid3); - assertNotSame(c3, rm3); // assert the parsed objects is not from the cache - assertEquals(c3.getAuthor(), rm3.getAuthor()); - assertEquals(c3.getCommitId(), rm3.getCommitId()); - assertEquals(c3.getMessage(), rm3.getMessage()); - assertEquals(c3.getTree().getTreeId(), rm3.getTree().getTreeId()); - assertEquals(2, rm3.getParentIds().length); - assertEquals(c1.getCommitId(), rm3.getParentIds()[0]); - assertEquals(c2.getCommitId(), rm3.getParentIds()[1]); - - final Commit c4 = new Commit(db); - c4.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); - c4.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60)); - c4.setMessage("A Commit 4\n"); - c4.setTree(t); - assertEquals(t.getTreeId(), c3.getTreeId()); - c4.setParentIds(new ObjectId[] { c1.getCommitId(), c2.getCommitId(), c3.getCommitId() }); - c4.commit(); - final ObjectId cmtid4 = ObjectId.fromString( - "d1fca9fe3fef54e5212eb67902c8ed3e79736e27"); - assertEquals(cmtid4, c4.getCommitId()); - - Commit rm4 = db.mapCommit(cmtid4); - assertNotSame(c4, rm3); // assert the parsed objects is not from the cache - assertEquals(c4.getAuthor(), rm4.getAuthor()); - assertEquals(c4.getCommitId(), rm4.getCommitId()); - assertEquals(c4.getMessage(), rm4.getMessage()); - assertEquals(c4.getTree().getTreeId(), rm4.getTree().getTreeId()); - assertEquals(3, rm4.getParentIds().length); - assertEquals(c1.getCommitId(), rm4.getParentIds()[0]); - assertEquals(c2.getCommitId(), rm4.getParentIds()[1]); - assertEquals(c3.getCommitId(), rm4.getParentIds()[2]); - } - - public void test027_UnpackedRefHigherPriorityThanPacked() throws IOException { - PrintWriter writer = new PrintWriter(new FileWriter(new File(db.getDirectory(), "refs/heads/a"))); - String unpackedId = "7f822839a2fe9760f386cbbbcb3f92c5fe81def7"; - writer.print(unpackedId); - writer.print('\n'); - writer.close(); - - ObjectId resolved = db.resolve("refs/heads/a"); - assertEquals(unpackedId, resolved.name()); - } - - public void test028_LockPackedRef() throws IOException { - writeTrashFile(".git/packed-refs", "7f822839a2fe9760f386cbbbcb3f92c5fe81def7 refs/heads/foobar"); - writeTrashFile(".git/HEAD", "ref: refs/heads/foobar\n"); - BUG_WorkAroundRacyGitIssues("packed-refs"); - BUG_WorkAroundRacyGitIssues("HEAD"); - - ObjectId resolve = db.resolve("HEAD"); - assertEquals("7f822839a2fe9760f386cbbbcb3f92c5fe81def7", resolve.name()); - - RefUpdate lockRef = db.updateRef("HEAD"); - ObjectId newId = ObjectId.fromString("07f822839a2fe9760f386cbbbcb3f92c5fe81def"); - lockRef.setNewObjectId(newId); - assertEquals(RefUpdate.Result.FORCED, lockRef.forceUpdate()); - - assertTrue(new File(db.getDirectory(), "refs/heads/foobar").exists()); - assertEquals(newId, db.resolve("refs/heads/foobar")); - - // Again. The ref already exists - RefUpdate lockRef2 = db.updateRef("HEAD"); - ObjectId newId2 = ObjectId.fromString("7f822839a2fe9760f386cbbbcb3f92c5fe81def7"); - lockRef2.setNewObjectId(newId2); - assertEquals(RefUpdate.Result.FORCED, lockRef2.forceUpdate()); - - assertTrue(new File(db.getDirectory(), "refs/heads/foobar").exists()); - assertEquals(newId2, db.resolve("refs/heads/foobar")); - } - - public void test029_mapObject() throws IOException { - assertEquals(new byte[0].getClass(), db.mapObject(ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"), null).getClass()); - assertEquals(Commit.class, db.mapObject(ObjectId.fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"), null).getClass()); - assertEquals(Tree.class, db.mapObject(ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), null).getClass()); - assertEquals(Tag.class, db.mapObject(ObjectId.fromString("17768080a2318cd89bba4c8b87834401e2095703"), null).getClass()); - } - - public void test30_stripWorkDir() { - File relCwd = new File("."); - File absCwd = relCwd.getAbsoluteFile(); - File absBase = new File(new File(absCwd, "repo"), "workdir"); - File relBase = new File(new File(relCwd, "repo"), "workdir"); - assertEquals(absBase.getAbsolutePath(), relBase.getAbsolutePath()); - - File relBaseFile = new File(new File(relBase, "other"), "module.c"); - File absBaseFile = new File(new File(absBase, "other"), "module.c"); - assertEquals("other/module.c", Repository.stripWorkDir(relBase, relBaseFile)); - assertEquals("other/module.c", Repository.stripWorkDir(relBase, absBaseFile)); - assertEquals("other/module.c", Repository.stripWorkDir(absBase, relBaseFile)); - assertEquals("other/module.c", Repository.stripWorkDir(absBase, absBaseFile)); - - File relNonFile = new File(new File(relCwd, "not-repo"), ".gitignore"); - File absNonFile = new File(new File(absCwd, "not-repo"), ".gitignore"); - assertEquals("", Repository.stripWorkDir(relBase, relNonFile)); - assertEquals("", Repository.stripWorkDir(absBase, absNonFile)); - - assertEquals("", Repository.stripWorkDir(db.getWorkTree(), db.getWorkTree())); - - File file = new File(new File(db.getWorkTree(), "subdir"), "File.java"); - assertEquals("subdir/File.java", Repository.stripWorkDir(db.getWorkTree(), file)); - - } - - /** - * Kick the timestamp of a local file. - *

- * We shouldn't have to make these method calls. The cache is using file - * system timestamps, and on many systems unit tests run faster than the - * modification clock. Dumping the cache after we make an edit behind - * RefDirectory's back allows the tests to pass. - * - * @param name - * the file in the repository to force a time change on. - */ - private void BUG_WorkAroundRacyGitIssues(String name) { - File path = new File(db.getDirectory(), name); - long old = path.lastModified(); - long set = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009 - path.setLastModified(set); - assertTrue("time changed", old != path.lastModified()); - } -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java deleted file mode 100644 index 54816f6f5e..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2008, Imran M Yousuf - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.File; -import java.io.IOException; - -import org.eclipse.jgit.util.JGitTestUtil; - -public class T0004_PackReader extends SampleDataRepositoryTestCase { - private static final String PACK_NAME = "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f"; - private static final File TEST_PACK = JGitTestUtil.getTestResourceFile(PACK_NAME + ".pack"); - private static final File TEST_IDX = JGitTestUtil.getTestResourceFile(PACK_NAME + ".idx"); - - public void test003_lookupCompressedObject() throws IOException { - final PackFile pr; - final ObjectId id; - final PackedObjectLoader or; - - id = ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"); - pr = new PackFile(TEST_IDX, TEST_PACK); - or = pr.get(new WindowCursor(null), id); - assertNotNull(or); - assertEquals(Constants.OBJ_TREE, or.getType()); - assertEquals(35, or.getSize()); - assertEquals(7736, or.getObjectOffset()); - pr.close(); - } - - public void test004_lookupDeltifiedObject() throws IOException { - final ObjectId id; - final ObjectLoader or; - - id = ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"); - or = db.openObject(id); - assertNotNull(or); - assertTrue(or instanceof PackedObjectLoader); - assertEquals(Constants.OBJ_BLOB, or.getType()); - assertEquals(18009, or.getSize()); - assertEquals(516, ((PackedObjectLoader) or).getObjectOffset()); - } -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheGetTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheGetTest.java deleted file mode 100644 index 8ff022ddc4..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheGetTest.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2009, Google Inc. - * 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.lib; - -import java.io.BufferedReader; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.List; - -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.util.JGitTestUtil; -import org.eclipse.jgit.util.MutableInteger; - -public class WindowCacheGetTest extends SampleDataRepositoryTestCase { - private List toLoad; - - @Override - public void setUp() throws Exception { - super.setUp(); - - toLoad = new ArrayList(); - final BufferedReader br = new BufferedReader(new InputStreamReader( - new FileInputStream(JGitTestUtil - .getTestResourceFile("all_packed_objects.txt")), - Constants.CHARSET)); - try { - String line; - while ((line = br.readLine()) != null) { - final String[] parts = line.split(" {1,}"); - final TestObject o = new TestObject(); - o.id = ObjectId.fromString(parts[0]); - o.setType(parts[1]); - o.rawSize = Integer.parseInt(parts[2]); - // parts[3] is the size-in-pack - o.offset = Long.parseLong(parts[4]); - toLoad.add(o); - } - } finally { - br.close(); - } - assertEquals(96, toLoad.size()); - } - - public void testCache_Defaults() throws IOException { - final WindowCacheConfig cfg = new WindowCacheConfig(); - WindowCache.reconfigure(cfg); - doCacheTests(); - checkLimits(cfg); - - final WindowCache cache = WindowCache.getInstance(); - assertEquals(6, cache.getOpenFiles()); - assertEquals(17346, cache.getOpenBytes()); - } - - public void testCache_TooFewFiles() throws IOException { - final WindowCacheConfig cfg = new WindowCacheConfig(); - cfg.setPackedGitOpenFiles(2); - WindowCache.reconfigure(cfg); - doCacheTests(); - checkLimits(cfg); - } - - public void testCache_TooSmallLimit() throws IOException { - final WindowCacheConfig cfg = new WindowCacheConfig(); - cfg.setPackedGitWindowSize(4096); - cfg.setPackedGitLimit(4096); - WindowCache.reconfigure(cfg); - doCacheTests(); - checkLimits(cfg); - } - - private void checkLimits(final WindowCacheConfig cfg) { - final WindowCache cache = WindowCache.getInstance(); - assertTrue(cache.getOpenFiles() <= cfg.getPackedGitOpenFiles()); - assertTrue(cache.getOpenBytes() <= cfg.getPackedGitLimit()); - assertTrue(0 < cache.getOpenFiles()); - assertTrue(0 < cache.getOpenBytes()); - } - - private void doCacheTests() throws IOException { - for (final TestObject o : toLoad) { - final ObjectLoader or = db.openObject(o.id); - assertNotNull(or); - assertTrue(or instanceof PackedObjectLoader); - assertEquals(o.type, or.getType()); - assertEquals(o.rawSize, or.getRawSize()); - assertEquals(o.offset, ((PackedObjectLoader) or).getObjectOffset()); - } - } - - private class TestObject { - ObjectId id; - - int type; - - int rawSize; - - long offset; - - void setType(final String typeStr) throws CorruptObjectException { - final byte[] typeRaw = Constants.encode(typeStr + " "); - final MutableInteger ptr = new MutableInteger(); - type = Constants.decodeTypeString(id, typeRaw, (byte) ' ', ptr); - } - } -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheReconfigureTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheReconfigureTest.java deleted file mode 100644 index 9e093c85bd..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheReconfigureTest.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2009, Google Inc. - * 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.lib; - -public class WindowCacheReconfigureTest extends RepositoryTestCase { - public void testConfigureCache_PackedGitLimit_0() { - try { - final WindowCacheConfig cfg = new WindowCacheConfig(); - cfg.setPackedGitLimit(0); - WindowCache.reconfigure(cfg); - fail("incorrectly permitted PackedGitLimit = 0"); - } catch (IllegalArgumentException e) { - // - } - } - - public void testConfigureCache_PackedGitWindowSize_0() { - try { - final WindowCacheConfig cfg = new WindowCacheConfig(); - cfg.setPackedGitWindowSize(0); - WindowCache.reconfigure(cfg); - fail("incorrectly permitted PackedGitWindowSize = 0"); - } catch (IllegalArgumentException e) { - assertEquals("Invalid window size", e.getMessage()); - } - } - - public void testConfigureCache_PackedGitWindowSize_512() { - try { - final WindowCacheConfig cfg = new WindowCacheConfig(); - cfg.setPackedGitWindowSize(512); - WindowCache.reconfigure(cfg); - fail("incorrectly permitted PackedGitWindowSize = 512"); - } catch (IllegalArgumentException e) { - assertEquals("Invalid window size", e.getMessage()); - } - } - - public void testConfigureCache_PackedGitWindowSize_4097() { - try { - final WindowCacheConfig cfg = new WindowCacheConfig(); - cfg.setPackedGitWindowSize(4097); - WindowCache.reconfigure(cfg); - fail("incorrectly permitted PackedGitWindowSize = 4097"); - } catch (IllegalArgumentException e) { - assertEquals("Window size must be power of 2", e.getMessage()); - } - } - - public void testConfigureCache_PackedGitOpenFiles_0() { - try { - final WindowCacheConfig cfg = new WindowCacheConfig(); - cfg.setPackedGitOpenFiles(0); - WindowCache.reconfigure(cfg); - fail("incorrectly permitted PackedGitOpenFiles = 0"); - } catch (IllegalArgumentException e) { - assertEquals("Open files must be >= 1", e.getMessage()); - } - } - - public void testConfigureCache_PackedGitWindowSizeAbovePackedGitLimit() { - try { - final WindowCacheConfig cfg = new WindowCacheConfig(); - cfg.setPackedGitLimit(1024); - cfg.setPackedGitWindowSize(8192); - WindowCache.reconfigure(cfg); - fail("incorrectly permitted PackedGitWindowSize > PackedGitLimit"); - } catch (IllegalArgumentException e) { - assertEquals("Window size must be < limit", e.getMessage()); - } - } - - public void testConfigureCache_Limits1() { - // This test is just to force coverage over some lower bounds for - // the table. We don't want the table to wind up with too small - // of a size. This is highly dependent upon the table allocation - // algorithm actually implemented in WindowCache. - // - final WindowCacheConfig cfg = new WindowCacheConfig(); - cfg.setPackedGitLimit(6 * 4096 / 5); - cfg.setPackedGitWindowSize(4096); - WindowCache.reconfigure(cfg); - } -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/XInputStream.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/XInputStream.java deleted file mode 100644 index eef32b9276..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/XInputStream.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.BufferedInputStream; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; - -import org.eclipse.jgit.util.NB; - -class XInputStream extends BufferedInputStream { - private final byte[] intbuf = new byte[8]; - - XInputStream(final InputStream s) { - super(s); - } - - synchronized byte[] readFully(final int len) throws IOException { - final byte[] b = new byte[len]; - readFully(b, 0, len); - return b; - } - - synchronized void readFully(final byte[] b, int o, int len) - throws IOException { - int r; - while (len > 0 && (r = read(b, o, len)) > 0) { - o += r; - len -= r; - } - if (len > 0) - throw new EOFException(); - } - - int readUInt8() throws IOException { - final int r = read(); - if (r < 0) - throw new EOFException(); - return r; - } - - long readUInt32() throws IOException { - readFully(intbuf, 0, 4); - return NB.decodeUInt32(intbuf, 0); - } -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java new file mode 100644 index 0000000000..db770f777d --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2009-2010, Google Inc. + * Copyright (C) 2009, Robin Rosenberg + * 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.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; + +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectWriter; +import org.eclipse.jgit.lib.PackWriter; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevWalk; + +public class ConcurrentRepackTest extends RepositoryTestCase { + public void setUp() throws Exception { + WindowCacheConfig windowCacheConfig = new WindowCacheConfig(); + windowCacheConfig.setPackedGitOpenFiles(1); + WindowCache.reconfigure(windowCacheConfig); + super.setUp(); + } + + protected void tearDown() throws Exception { + super.tearDown(); + WindowCacheConfig windowCacheConfig = new WindowCacheConfig(); + WindowCache.reconfigure(windowCacheConfig); + } + + public void testObjectInNewPack() throws IncorrectObjectTypeException, + IOException { + // Create a new object in a new pack, and test that it is present. + // + final Repository eden = createBareRepository(); + final RevObject o1 = writeBlob(eden, "o1"); + pack(eden, o1); + assertEquals(o1.name(), parse(o1).name()); + } + + public void testObjectMovedToNewPack1() + throws IncorrectObjectTypeException, IOException { + // Create an object and pack it. Then remove that pack and put the + // object into a different pack file, with some other object. We + // still should be able to access the objects. + // + final Repository eden = createBareRepository(); + final RevObject o1 = writeBlob(eden, "o1"); + final File[] out1 = pack(eden, o1); + assertEquals(o1.name(), parse(o1).name()); + + final RevObject o2 = writeBlob(eden, "o2"); + pack(eden, o2, o1); + + // Force close, and then delete, the old pack. + // + whackCache(); + delete(out1); + + // Now here is the interesting thing. Will git figure the new + // object exists in the new pack, and not the old one. + // + assertEquals(o2.name(), parse(o2).name()); + assertEquals(o1.name(), parse(o1).name()); + } + + public void testObjectMovedWithinPack() + throws IncorrectObjectTypeException, IOException { + // Create an object and pack it. + // + final Repository eden = createBareRepository(); + final RevObject o1 = writeBlob(eden, "o1"); + final File[] out1 = pack(eden, o1); + assertEquals(o1.name(), parse(o1).name()); + + // Force close the old pack. + // + whackCache(); + + // Now overwrite the old pack in place. This method of creating a + // different pack under the same file name is partially broken. We + // should also have a different file name because the list of objects + // within the pack has been modified. + // + final RevObject o2 = writeBlob(eden, "o2"); + final PackWriter pw = new PackWriter(eden, NullProgressMonitor.INSTANCE); + pw.addObject(o2); + pw.addObject(o1); + write(out1, pw); + + // Try the old name, then the new name. The old name should cause the + // pack to reload when it opens and the index and pack mismatch. + // + assertEquals(o1.name(), parse(o1).name()); + assertEquals(o2.name(), parse(o2).name()); + } + + public void testObjectMovedToNewPack2() + throws IncorrectObjectTypeException, IOException { + // Create an object and pack it. Then remove that pack and put the + // object into a different pack file, with some other object. We + // still should be able to access the objects. + // + final Repository eden = createBareRepository(); + final RevObject o1 = writeBlob(eden, "o1"); + final File[] out1 = pack(eden, o1); + assertEquals(o1.name(), parse(o1).name()); + + final ObjectLoader load1 = db.openBlob(o1); + assertNotNull(load1); + + final RevObject o2 = writeBlob(eden, "o2"); + pack(eden, o2, o1); + + // Force close, and then delete, the old pack. + // + whackCache(); + delete(out1); + + // Now here is the interesting thing... can the loader we made + // earlier still resolve the object, even though its underlying + // pack is gone, but the object still exists. + // + final ObjectLoader load2 = db.openBlob(o1); + assertNotNull(load2); + assertNotSame(load1, load2); + + final byte[] data2 = load2.getCachedBytes(); + final byte[] data1 = load1.getCachedBytes(); + assertNotNull(data2); + assertNotNull(data1); + assertNotSame(data1, data2); // cache should be per-pack, not per object + assertTrue(Arrays.equals(data1, data2)); + assertEquals(load2.getType(), load1.getType()); + } + + private static void whackCache() { + final WindowCacheConfig config = new WindowCacheConfig(); + config.setPackedGitOpenFiles(1); + WindowCache.reconfigure(config); + } + + private RevObject parse(final AnyObjectId id) + throws MissingObjectException, IOException { + return new RevWalk(db).parseAny(id); + } + + private File[] pack(final Repository src, final RevObject... list) + throws IOException { + final PackWriter pw = new PackWriter(src, NullProgressMonitor.INSTANCE); + for (final RevObject o : list) { + pw.addObject(o); + } + + final ObjectId name = pw.computeName(); + final File packFile = fullPackFileName(name, ".pack"); + final File idxFile = fullPackFileName(name, ".idx"); + final File[] files = new File[] { packFile, idxFile }; + write(files, pw); + return files; + } + + private static void write(final File[] files, final PackWriter pw) + throws IOException { + final long begin = files[0].getParentFile().lastModified(); + OutputStream out; + + out = new BufferedOutputStream(new FileOutputStream(files[0])); + try { + pw.writePack(out); + } finally { + out.close(); + } + + out = new BufferedOutputStream(new FileOutputStream(files[1])); + try { + pw.writeIndex(out); + } finally { + out.close(); + } + + touch(begin, files[0].getParentFile()); + } + + private static void delete(final File[] list) { + final long begin = list[0].getParentFile().lastModified(); + for (final File f : list) { + f.delete(); + assertFalse(f + " was removed", f.exists()); + } + touch(begin, list[0].getParentFile()); + } + + private static void touch(final long begin, final File dir) { + while (begin >= dir.lastModified()) { + try { + Thread.sleep(25); + } catch (InterruptedException ie) { + // + } + dir.setLastModified(System.currentTimeMillis()); + } + } + + private File fullPackFileName(final ObjectId name, final String suffix) { + final File packdir = new File(db.getObjectDatabase().getDirectory(), "pack"); + return new File(packdir, "pack-" + name.name() + suffix); + } + + private RevObject writeBlob(final Repository repo, final String data) + throws IOException { + final RevWalk revWalk = new RevWalk(repo); + final byte[] bytes = Constants.encode(data); + final ObjectWriter ow = new ObjectWriter(repo); + final ObjectId id = ow.writeBlob(bytes); + try { + parse(id); + fail("Object " + id.name() + " should not exist in test repository"); + } catch (MissingObjectException e) { + // Ok + } + return revWalk.lookupBlob(id); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexTestCase.java new file mode 100644 index 0000000000..9884142e5c --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexTestCase.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * 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.util.Iterator; +import java.util.NoSuchElementException; + +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.storage.file.PackIndex.MutableEntry; + +public abstract class PackIndexTestCase extends RepositoryTestCase { + + PackIndex smallIdx; + + PackIndex denseIdx; + + public void setUp() throws Exception { + super.setUp(); + smallIdx = PackIndex.open(getFileForPack34be9032()); + denseIdx = PackIndex.open(getFileForPackdf2982f28()); + } + + /** + * Return file with appropriate index version for prepared pack. + * + * @return file with index + */ + public abstract File getFileForPack34be9032(); + + /** + * Return file with appropriate index version for prepared pack. + * + * @return file with index + */ + public abstract File getFileForPackdf2982f28(); + + /** + * Verify CRC32 support. + * + * @throws MissingObjectException + * @throws UnsupportedOperationException + */ + public abstract void testCRC32() throws MissingObjectException, + UnsupportedOperationException; + + /** + * Test contracts of Iterator methods and this implementation remove() + * limitations. + */ + public void testIteratorMethodsContract() { + Iterator iter = smallIdx.iterator(); + while (iter.hasNext()) { + iter.next(); + } + + try { + iter.next(); + fail("next() unexpectedly returned element"); + } catch (NoSuchElementException x) { + // expected + } + + try { + iter.remove(); + fail("remove() shouldn't be implemented"); + } catch (UnsupportedOperationException x) { + // expected + } + } + + /** + * Test results of iterator comparing to content of well-known (prepared) + * small index. + */ + public void testIteratorReturnedValues1() { + Iterator iter = smallIdx.iterator(); + assertEquals("4b825dc642cb6eb9a060e54bf8d69288fbee4904", iter.next() + .name()); + assertEquals("540a36d136cf413e4b064c2b0e0a4db60f77feab", iter.next() + .name()); + assertEquals("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259", iter.next() + .name()); + assertEquals("6ff87c4664981e4397625791c8ea3bbb5f2279a3", iter.next() + .name()); + assertEquals("82c6b885ff600be425b4ea96dee75dca255b69e7", iter.next() + .name()); + assertEquals("902d5476fa249b7abc9d84c611577a81381f0327", iter.next() + .name()); + assertEquals("aabf2ffaec9b497f0950352b3e582d73035c2035", iter.next() + .name()); + assertEquals("c59759f143fb1fe21c197981df75a7ee00290799", iter.next() + .name()); + assertFalse(iter.hasNext()); + } + + /** + * Compare offset from iterator entries with output of findOffset() method. + */ + public void testCompareEntriesOffsetsWithFindOffsets() { + for (MutableEntry me : smallIdx) { + assertEquals(smallIdx.findOffset(me.toObjectId()), me.getOffset()); + } + for (MutableEntry me : denseIdx) { + assertEquals(denseIdx.findOffset(me.toObjectId()), me.getOffset()); + } + } + + /** + * Test partial results of iterator comparing to content of well-known + * (prepared) dense index, that may need multi-level indexing. + */ + public void testIteratorReturnedValues2() { + Iterator iter = denseIdx.iterator(); + while (!iter.next().name().equals( + "0a3d7772488b6b106fb62813c4d6d627918d9181")) { + // just iterating + } + assertEquals("1004d0d7ac26fbf63050a234c9b88a46075719d3", iter.next() + .name()); // same level-1 + assertEquals("10da5895682013006950e7da534b705252b03be6", iter.next() + .name()); // same level-1 + assertEquals("1203b03dc816ccbb67773f28b3c19318654b0bc8", iter.next() + .name()); + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV1Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV1Test.java new file mode 100644 index 0000000000..303eeff72d --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV1Test.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2008, Imran M Yousuf + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2009, Matthias Sohn + * 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 org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.util.JGitTestUtil; + +public class PackIndexV1Test extends PackIndexTestCase { + @Override + public File getFileForPack34be9032() { + return JGitTestUtil.getTestResourceFile( + "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idx"); + } + + @Override + public File getFileForPackdf2982f28() { + return JGitTestUtil.getTestResourceFile( + "pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.idx"); + } + + /** + * Verify CRC32 - V1 should not index anything. + * + * @throws MissingObjectException + */ + @Override + public void testCRC32() throws MissingObjectException { + assertFalse(smallIdx.hasCRC32Support()); + try { + smallIdx.findCRC32(ObjectId + .fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904")); + fail("index V1 shouldn't support CRC"); + } catch (UnsupportedOperationException x) { + // expected + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV2Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV2Test.java new file mode 100644 index 0000000000..2d3ec7b729 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV2Test.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2008, Imran M Yousuf + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2009, Matthias Sohn + * 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 org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.util.JGitTestUtil; + +public class PackIndexV2Test extends PackIndexTestCase { + @Override + public File getFileForPack34be9032() { + return JGitTestUtil.getTestResourceFile( + "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idxV2"); + } + + @Override + public File getFileForPackdf2982f28() { + return JGitTestUtil.getTestResourceFile( + "pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.idxV2"); + } + + /** + * Verify CRC32 indexing. + * + * @throws UnsupportedOperationException + * @throws MissingObjectException + */ + @Override + public void testCRC32() throws MissingObjectException, + UnsupportedOperationException { + assertTrue(smallIdx.hasCRC32Support()); + assertEquals(0x00000000C2B64258l, smallIdx.findCRC32(ObjectId + .fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904"))); + assertEquals(0x0000000072AD57C2l, smallIdx.findCRC32(ObjectId + .fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"))); + assertEquals(0x00000000FF10A479l, smallIdx.findCRC32(ObjectId + .fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"))); + assertEquals(0x0000000034B27DDCl, smallIdx.findCRC32(ObjectId + .fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3"))); + assertEquals(0x000000004743F1E4l, smallIdx.findCRC32(ObjectId + .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"))); + assertEquals(0x00000000640B358Bl, smallIdx.findCRC32(ObjectId + .fromString("902d5476fa249b7abc9d84c611577a81381f0327"))); + assertEquals(0x000000002A17CB5El, smallIdx.findCRC32(ObjectId + .fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"))); + assertEquals(0x000000000B3B5BA6l, smallIdx.findCRC32(ObjectId + .fromString("c59759f143fb1fe21c197981df75a7ee00290799"))); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackReverseIndexTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackReverseIndexTest.java new file mode 100644 index 0000000000..07a40a425f --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackReverseIndexTest.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2008, Imran M Yousuf + * Copyright (C) 2008, Marek Zawirski + * 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 org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.storage.file.PackIndex.MutableEntry; +import org.eclipse.jgit.util.JGitTestUtil; + +public class PackReverseIndexTest extends RepositoryTestCase { + + private PackIndex idx; + + private PackReverseIndex reverseIdx; + + /** + * Set up tested class instance, test constructor by the way. + */ + public void setUp() throws Exception { + super.setUp(); + // index with both small (< 2^31) and big offsets + idx = PackIndex.open(JGitTestUtil.getTestResourceFile( + "pack-huge.idx")); + reverseIdx = new PackReverseIndex(idx); + } + + /** + * Test findObject() for all index entries. + */ + public void testFindObject() { + for (MutableEntry me : idx) + assertEquals(me.toObjectId(), reverseIdx.findObject(me.getOffset())); + } + + /** + * Test findObject() with illegal argument. + */ + public void testFindObjectWrongOffset() { + assertNull(reverseIdx.findObject(0)); + } + + /** + * Test findNextOffset() for all index entries. + * + * @throws CorruptObjectException + */ + public void testFindNextOffset() throws CorruptObjectException { + long offset = findFirstOffset(); + assertTrue(offset > 0); + for (int i = 0; i < idx.getObjectCount(); i++) { + long newOffset = reverseIdx.findNextOffset(offset, Long.MAX_VALUE); + assertTrue(newOffset > offset); + if (i == idx.getObjectCount() - 1) + assertEquals(newOffset, Long.MAX_VALUE); + else + assertEquals(newOffset, idx.findOffset(reverseIdx + .findObject(newOffset))); + offset = newOffset; + } + } + + /** + * Test findNextOffset() with wrong illegal argument as offset. + */ + public void testFindNextOffsetWrongOffset() { + try { + reverseIdx.findNextOffset(0, Long.MAX_VALUE); + fail("findNextOffset() should throw exception"); + } catch (CorruptObjectException x) { + // expected + } + } + + private long findFirstOffset() { + long min = Long.MAX_VALUE; + for (MutableEntry me : idx) + min = Math.min(min, me.getOffset()); + return min; + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java new file mode 100644 index 0000000000..cf0d7eb6a3 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * 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.ByteArrayInputStream; +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; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PackWriter; +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.transport.IndexPack; +import org.eclipse.jgit.util.JGitTestUtil; + +public class PackWriterTest extends SampleDataRepositoryTestCase { + + private static final List EMPTY_LIST_OBJECT = Collections + . emptyList(); + + private static final List EMPTY_LIST_REVS = Collections + . emptyList(); + + private PackWriter writer; + + private ByteArrayOutputStream os; + + private File packBase; + + private File packFile; + + private File indexFile; + + private PackFile pack; + + 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"); + writer = new PackWriter(db, new TextProgressMonitor()); + } + + /** + * Test constructor for exceptions, default settings, initialization. + */ + public void testContructor() { + assertEquals(false, writer.isDeltaBaseAsOffset()); + assertEquals(true, writer.isReuseDeltas()); + assertEquals(true, writer.isReuseObjects()); + assertEquals(0, writer.getObjectsNumber()); + } + + /** + * Change default settings and verify them. + */ + public void testModifySettings() { + writer.setDeltaBaseAsOffset(true); + writer.setReuseDeltas(false); + writer.setReuseObjects(false); + + assertEquals(true, writer.isDeltaBaseAsOffset()); + assertEquals(false, writer.isReuseDeltas()); + assertEquals(false, writer.isReuseObjects()); + } + + /** + * Write empty pack by providing empty sets of interesting/uninteresting + * objects and check for correct format. + * + * @throws IOException + */ + public void testWriteEmptyPack1() throws IOException { + createVerifyOpenPack(EMPTY_LIST_OBJECT, EMPTY_LIST_OBJECT, false, false); + + assertEquals(0, writer.getObjectsNumber()); + assertEquals(0, pack.getObjectCount()); + assertEquals("da39a3ee5e6b4b0d3255bfef95601890afd80709", writer + .computeName().name()); + } + + /** + * Write empty pack by providing empty iterator of objects to write and + * check for correct format. + * + * @throws IOException + */ + public void testWriteEmptyPack2() throws IOException { + createVerifyOpenPack(EMPTY_LIST_REVS.iterator()); + + assertEquals(0, writer.getObjectsNumber()); + assertEquals(0, pack.getObjectCount()); + } + + /** + * Try to pass non-existing object as uninteresting, with non-ignoring + * setting. + * + * @throws IOException + */ + public void testNotIgnoreNonExistingObjects() throws IOException { + final ObjectId nonExisting = ObjectId + .fromString("0000000000000000000000000000000000000001"); + try { + createVerifyOpenPack(EMPTY_LIST_OBJECT, Collections.nCopies(1, + nonExisting), false, false); + fail("Should have thrown MissingObjectException"); + } catch (MissingObjectException x) { + // expected + } + } + + /** + * Try to pass non-existing object as uninteresting, with ignoring setting. + * + * @throws IOException + */ + public void testIgnoreNonExistingObjects() throws IOException { + final ObjectId nonExisting = ObjectId + .fromString("0000000000000000000000000000000000000001"); + createVerifyOpenPack(EMPTY_LIST_OBJECT, Collections.nCopies(1, + nonExisting), false, true); + // shouldn't throw anything + } + + /** + * Create pack basing on only interesting objects, then precisely verify + * content. No delta reuse here. + * + * @throws IOException + */ + public void testWritePack1() throws IOException { + writer.setReuseDeltas(false); + writeVerifyPack1(); + } + + /** + * Test writing pack without object reuse. Pack content/preparation as in + * {@link #testWritePack1()}. + * + * @throws IOException + */ + public void testWritePack1NoObjectReuse() throws IOException { + writer.setReuseDeltas(false); + writer.setReuseObjects(false); + writeVerifyPack1(); + } + + /** + * Create pack basing on both interesting and uninteresting objects, then + * precisely verify content. No delta reuse here. + * + * @throws IOException + */ + public void testWritePack2() throws IOException { + writeVerifyPack2(false); + } + + /** + * Test pack writing with deltas reuse, delta-base first rule. Pack + * content/preparation as in {@link #testWritePack2()}. + * + * @throws IOException + */ + public void testWritePack2DeltasReuseRefs() throws IOException { + writeVerifyPack2(true); + } + + /** + * Test pack writing with delta reuse. Delta bases referred as offsets. Pack + * configuration as in {@link #testWritePack2DeltasReuseRefs()}. + * + * @throws IOException + */ + public void testWritePack2DeltasReuseOffsets() throws IOException { + writer.setDeltaBaseAsOffset(true); + writeVerifyPack2(true); + } + + /** + * Test pack writing with delta reuse. Raw-data copy (reuse) is made on a + * pack with CRC32 index. Pack configuration as in + * {@link #testWritePack2DeltasReuseRefs()}. + * + * @throws IOException + */ + public void testWritePack2DeltasCRC32Copy() throws IOException { + final File packDir = new File(db.getObjectDatabase().getDirectory(), "pack"); + final File crc32Pack = new File(packDir, + "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.pack"); + final File crc32Idx = new File(packDir, + "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idx"); + copyFile(JGitTestUtil.getTestResourceFile( + "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idxV2"), + crc32Idx); + db.openPack(crc32Pack, crc32Idx); + + writeVerifyPack2(true); + } + + /** + * Create pack basing on fixed objects list, then precisely verify content. + * No delta reuse here. + * + * @throws IOException + * @throws MissingObjectException + * + */ + public void testWritePack3() throws MissingObjectException, IOException { + writer.setReuseDeltas(false); + final ObjectId forcedOrder[] = new ObjectId[] { + ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"), + ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"), + ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), + ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"), + ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"), + ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") }; + final RevWalk parser = new RevWalk(db); + final RevObject forcedOrderRevs[] = new RevObject[forcedOrder.length]; + for (int i = 0; i < forcedOrder.length; i++) + forcedOrderRevs[i] = parser.parseAny(forcedOrder[i]); + + createVerifyOpenPack(Arrays.asList(forcedOrderRevs).iterator()); + + assertEquals(forcedOrder.length, writer.getObjectsNumber()); + verifyObjectsOrder(forcedOrder); + assertEquals("ed3f96b8327c7c66b0f8f70056129f0769323d86", writer + .computeName().name()); + } + + /** + * Another pack creation: basing on both interesting and uninteresting + * objects. No delta reuse possible here, as this is a specific case when we + * write only 1 commit, associated with 1 tree, 1 blob. + * + * @throws IOException + */ + public void testWritePack4() throws IOException { + writeVerifyPack4(false); + } + + /** + * Test thin pack writing: 1 blob delta base is on objects edge. Pack + * configuration as in {@link #testWritePack4()}. + * + * @throws IOException + */ + public void testWritePack4ThinPack() throws IOException { + writeVerifyPack4(true); + } + + /** + * Compare sizes of packs created using {@link #testWritePack2()} and + * {@link #testWritePack2DeltasReuseRefs()}. The pack using deltas should + * be smaller. + * + * @throws Exception + */ + public void testWritePack2SizeDeltasVsNoDeltas() throws Exception { + testWritePack2(); + final long sizePack2NoDeltas = os.size(); + tearDown(); + setUp(); + testWritePack2DeltasReuseRefs(); + final long sizePack2DeltasRefs = os.size(); + + assertTrue(sizePack2NoDeltas > sizePack2DeltasRefs); + } + + /** + * Compare sizes of packs created using + * {@link #testWritePack2DeltasReuseRefs()} and + * {@link #testWritePack2DeltasReuseOffsets()}. The pack with delta bases + * written as offsets should be smaller. + * + * @throws Exception + */ + public void testWritePack2SizeOffsetsVsRefs() throws Exception { + testWritePack2DeltasReuseRefs(); + final long sizePack2DeltasRefs = os.size(); + tearDown(); + setUp(); + testWritePack2DeltasReuseOffsets(); + final long sizePack2DeltasOffsets = os.size(); + + assertTrue(sizePack2DeltasRefs > sizePack2DeltasOffsets); + } + + /** + * Compare sizes of packs created using {@link #testWritePack4()} and + * {@link #testWritePack4ThinPack()}. Obviously, the thin pack should be + * smaller. + * + * @throws Exception + */ + public void testWritePack4SizeThinVsNoThin() throws Exception { + testWritePack4(); + final long sizePack4 = os.size(); + tearDown(); + setUp(); + testWritePack4ThinPack(); + final long sizePack4Thin = os.size(); + + assertTrue(sizePack4 > sizePack4Thin); + } + + public void testWriteIndex() throws Exception { + writer.setIndexVersion(2); + writeVerifyPack4(false); + + // Validate that IndexPack came up with the right CRC32 value. + final PackIndex idx1 = PackIndex.open(indexFile); + assertTrue(idx1 instanceof PackIndexV2); + assertEquals(0x4743F1E4L, idx1.findCRC32(ObjectId + .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"))); + + // Validate that an index written by PackWriter is the same. + final File idx2File = new File(indexFile.getAbsolutePath() + ".2"); + final FileOutputStream is = new FileOutputStream(idx2File); + try { + writer.writeIndex(is); + } finally { + is.close(); + } + final PackIndex idx2 = PackIndex.open(idx2File); + assertTrue(idx2 instanceof PackIndexV2); + assertEquals(idx1.getObjectCount(), idx2.getObjectCount()); + assertEquals(idx1.getOffset64Count(), idx2.getOffset64Count()); + + for (int i = 0; i < idx1.getObjectCount(); i++) { + final ObjectId id = idx1.getObjectId(i); + assertEquals(id, idx2.getObjectId(i)); + assertEquals(idx1.findOffset(id), idx2.findOffset(id)); + assertEquals(idx1.findCRC32(id), idx2.findCRC32(id)); + } + } + + // TODO: testWritePackDeltasCycle() + // TODO: testWritePackDeltasDepth() + + private void writeVerifyPack1() throws IOException { + final LinkedList interestings = new LinkedList(); + interestings.add(ObjectId + .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")); + createVerifyOpenPack(interestings, EMPTY_LIST_OBJECT, false, false); + + final ObjectId expectedOrder[] = new ObjectId[] { + ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"), + ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"), + ObjectId.fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"), + ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), + ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"), + ObjectId.fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904"), + ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"), + ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") }; + + assertEquals(expectedOrder.length, writer.getObjectsNumber()); + verifyObjectsOrder(expectedOrder); + assertEquals("34be9032ac282b11fa9babdc2b2a93ca996c9c2f", writer + .computeName().name()); + } + + private void writeVerifyPack2(boolean deltaReuse) throws IOException { + writer.setReuseDeltas(deltaReuse); + final LinkedList interestings = new LinkedList(); + interestings.add(ObjectId + .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")); + final LinkedList uninterestings = new LinkedList(); + uninterestings.add(ObjectId + .fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab")); + createVerifyOpenPack(interestings, uninterestings, false, false); + + final ObjectId expectedOrder[] = new ObjectId[] { + ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"), + ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"), + ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), + ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"), + ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"), + ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") }; + if (deltaReuse) { + // objects order influenced (swapped) by delta-base first rule + ObjectId temp = expectedOrder[4]; + expectedOrder[4] = expectedOrder[5]; + expectedOrder[5] = temp; + } + assertEquals(expectedOrder.length, writer.getObjectsNumber()); + verifyObjectsOrder(expectedOrder); + assertEquals("ed3f96b8327c7c66b0f8f70056129f0769323d86", writer + .computeName().name()); + } + + private void writeVerifyPack4(final boolean thin) throws IOException { + final LinkedList interestings = new LinkedList(); + interestings.add(ObjectId + .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")); + final LinkedList uninterestings = new LinkedList(); + uninterestings.add(ObjectId + .fromString("c59759f143fb1fe21c197981df75a7ee00290799")); + createVerifyOpenPack(interestings, uninterestings, thin, false); + + final ObjectId writtenObjects[] = new ObjectId[] { + ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"), + ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), + ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") }; + assertEquals(writtenObjects.length, writer.getObjectsNumber()); + ObjectId expectedObjects[]; + if (thin) { + expectedObjects = new ObjectId[4]; + System.arraycopy(writtenObjects, 0, expectedObjects, 0, + writtenObjects.length); + expectedObjects[3] = ObjectId + .fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3"); + + } else { + expectedObjects = writtenObjects; + } + verifyObjectsOrder(expectedObjects); + assertEquals("cded4b74176b4456afa456768b2b5aafb41c44fc", writer + .computeName().name()); + } + + private void createVerifyOpenPack(final Collection interestings, + final Collection uninterestings, final boolean thin, + final boolean ignoreMissingUninteresting) + throws MissingObjectException, IOException { + writer.setThin(thin); + writer.setIgnoreMissingUninteresting(ignoreMissingUninteresting); + writer.preparePack(interestings, uninterestings); + writer.writePack(os); + verifyOpenPack(thin); + } + + private void createVerifyOpenPack(final Iterator objectSource) + throws MissingObjectException, IOException { + writer.preparePack(objectSource); + writer.writePack(os); + verifyOpenPack(false); + } + + private void verifyOpenPack(final boolean thin) throws IOException { + if (thin) { + final InputStream is = new ByteArrayInputStream(os.toByteArray()); + final IndexPack indexer = new IndexPack(db, is, packBase); + try { + indexer.index(new TextProgressMonitor()); + 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); + } + + private void verifyObjectsOrder(final ObjectId objectsOrder[]) { + final List entries = new ArrayList(); + + for (MutableEntry me : pack) { + entries.add(me.cloneEntry()); + } + Collections.sort(entries, new Comparator() { + public int compare(MutableEntry o1, MutableEntry o2) { + return Long.signum(o1.getOffset() - o2.getOffset()); + } + }); + + int i = 0; + for (MutableEntry me : entries) { + assertEquals(objectsOrder[i++].toObjectId(), me.toObjectId()); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefDirectoryTest.java new file mode 100644 index 0000000000..6e98541603 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefDirectoryTest.java @@ -0,0 +1,1028 @@ +/* + * Copyright (C) 2010, Google Inc. + * 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 static org.eclipse.jgit.lib.Constants.HEAD; +import static org.eclipse.jgit.lib.Constants.R_HEADS; +import static org.eclipse.jgit.lib.Constants.R_TAGS; +import static org.eclipse.jgit.lib.Ref.Storage.LOOSE; +import static org.eclipse.jgit.lib.Ref.Storage.NEW; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTag; + +public class RefDirectoryTest extends LocalDiskRepositoryTestCase { + private Repository diskRepo; + + private TestRepository repo; + + private RefDirectory refdir; + + private RevCommit A; + + private RevCommit B; + + private RevTag v1_0; + + protected void setUp() throws Exception { + super.setUp(); + + diskRepo = createBareRepository(); + refdir = (RefDirectory) diskRepo.getRefDatabase(); + + repo = new TestRepository(diskRepo); + A = repo.commit().create(); + B = repo.commit(repo.getRevWalk().parseCommit(A)); + v1_0 = repo.tag("v1_0", B); + repo.getRevWalk().parseBody(v1_0); + } + + public void testCreate() throws IOException { + // setUp above created the directory. We just have to test it. + File d = diskRepo.getDirectory(); + assertSame(diskRepo, refdir.getRepository()); + + assertTrue(new File(d, "refs").isDirectory()); + assertTrue(new File(d, "logs").isDirectory()); + assertTrue(new File(d, "logs/refs").isDirectory()); + assertFalse(new File(d, "packed-refs").exists()); + + assertTrue(new File(d, "refs/heads").isDirectory()); + assertTrue(new File(d, "refs/tags").isDirectory()); + assertEquals(2, new File(d, "refs").list().length); + assertEquals(0, new File(d, "refs/heads").list().length); + assertEquals(0, new File(d, "refs/tags").list().length); + + assertTrue(new File(d, "logs/refs/heads").isDirectory()); + assertFalse(new File(d, "logs/HEAD").exists()); + assertEquals(0, new File(d, "logs/refs/heads").list().length); + + assertEquals("ref: refs/heads/master\n", read(new File(d, HEAD))); + } + + public void testGetRefs_EmptyDatabase() throws IOException { + Map all; + + all = refdir.getRefs(RefDatabase.ALL); + assertTrue("no references", all.isEmpty()); + + all = refdir.getRefs(R_HEADS); + assertTrue("no references", all.isEmpty()); + + all = refdir.getRefs(R_TAGS); + assertTrue("no references", all.isEmpty()); + } + + public void testGetRefs_HeadOnOneBranch() throws IOException { + Map all; + Ref head, master; + + writeLooseRef("refs/heads/master", A); + + all = refdir.getRefs(RefDatabase.ALL); + assertEquals(2, all.size()); + assertTrue("has HEAD", all.containsKey(HEAD)); + assertTrue("has master", all.containsKey("refs/heads/master")); + + head = all.get(HEAD); + master = all.get("refs/heads/master"); + + assertEquals(HEAD, head.getName()); + assertTrue(head.isSymbolic()); + assertSame(LOOSE, head.getStorage()); + assertSame("uses same ref as target", master, head.getTarget()); + + assertEquals("refs/heads/master", master.getName()); + assertFalse(master.isSymbolic()); + assertSame(LOOSE, master.getStorage()); + assertEquals(A, master.getObjectId()); + } + + public void testGetRefs_DeatchedHead1() throws IOException { + Map all; + Ref head; + + writeLooseRef(HEAD, A); + BUG_WorkAroundRacyGitIssues(HEAD); + + all = refdir.getRefs(RefDatabase.ALL); + assertEquals(1, all.size()); + assertTrue("has HEAD", all.containsKey(HEAD)); + + head = all.get(HEAD); + + assertEquals(HEAD, head.getName()); + assertFalse(head.isSymbolic()); + assertSame(LOOSE, head.getStorage()); + assertEquals(A, head.getObjectId()); + } + + public void testGetRefs_DeatchedHead2() throws IOException { + Map all; + Ref head, master; + + writeLooseRef(HEAD, A); + writeLooseRef("refs/heads/master", B); + BUG_WorkAroundRacyGitIssues(HEAD); + + all = refdir.getRefs(RefDatabase.ALL); + assertEquals(2, all.size()); + + head = all.get(HEAD); + master = all.get("refs/heads/master"); + + assertEquals(HEAD, head.getName()); + assertFalse(head.isSymbolic()); + assertSame(LOOSE, head.getStorage()); + assertEquals(A, head.getObjectId()); + + assertEquals("refs/heads/master", master.getName()); + assertFalse(master.isSymbolic()); + assertSame(LOOSE, master.getStorage()); + assertEquals(B, master.getObjectId()); + } + + public void testGetRefs_DeeplyNestedBranch() throws IOException { + String name = "refs/heads/a/b/c/d/e/f/g/h/i/j/k"; + Map all; + Ref r; + + writeLooseRef(name, A); + + all = refdir.getRefs(RefDatabase.ALL); + assertEquals(1, all.size()); + + r = all.get(name); + assertEquals(name, r.getName()); + assertFalse(r.isSymbolic()); + assertSame(LOOSE, r.getStorage()); + assertEquals(A, r.getObjectId()); + } + + public void testGetRefs_HeadBranchNotBorn() throws IOException { + Map all; + Ref a, b; + + writeLooseRef("refs/heads/A", A); + writeLooseRef("refs/heads/B", B); + + all = refdir.getRefs(RefDatabase.ALL); + assertEquals(2, all.size()); + assertFalse("no HEAD", all.containsKey(HEAD)); + + a = all.get("refs/heads/A"); + b = all.get("refs/heads/B"); + + assertEquals(A, a.getObjectId()); + assertEquals(B, b.getObjectId()); + + assertEquals("refs/heads/A", a.getName()); + assertEquals("refs/heads/B", b.getName()); + } + + public void testGetRefs_LooseOverridesPacked() throws IOException { + Map heads; + Ref a; + + writeLooseRef("refs/heads/master", B); + writePackedRef("refs/heads/master", A); + + heads = refdir.getRefs(R_HEADS); + assertEquals(1, heads.size()); + + a = heads.get("master"); + assertEquals("refs/heads/master", a.getName()); + assertEquals(B, a.getObjectId()); + } + + public void testGetRefs_IgnoresGarbageRef1() throws IOException { + Map heads; + Ref a; + + writeLooseRef("refs/heads/A", A); + write(new File(diskRepo.getDirectory(), "refs/heads/bad"), "FAIL\n"); + + heads = refdir.getRefs(RefDatabase.ALL); + assertEquals(1, heads.size()); + + a = heads.get("refs/heads/A"); + assertEquals("refs/heads/A", a.getName()); + assertEquals(A, a.getObjectId()); + } + + public void testGetRefs_IgnoresGarbageRef2() throws IOException { + Map heads; + Ref a; + + writeLooseRef("refs/heads/A", A); + write(new File(diskRepo.getDirectory(), "refs/heads/bad"), ""); + + heads = refdir.getRefs(RefDatabase.ALL); + assertEquals(1, heads.size()); + + a = heads.get("refs/heads/A"); + assertEquals("refs/heads/A", a.getName()); + assertEquals(A, a.getObjectId()); + } + + public void testGetRefs_IgnoresGarbageRef3() throws IOException { + Map heads; + Ref a; + + writeLooseRef("refs/heads/A", A); + write(new File(diskRepo.getDirectory(), "refs/heads/bad"), "\n"); + + heads = refdir.getRefs(RefDatabase.ALL); + assertEquals(1, heads.size()); + + a = heads.get("refs/heads/A"); + assertEquals("refs/heads/A", a.getName()); + assertEquals(A, a.getObjectId()); + } + + public void testGetRefs_IgnoresGarbageRef4() throws IOException { + Map heads; + Ref a, b, c; + + writeLooseRef("refs/heads/A", A); + writeLooseRef("refs/heads/B", B); + writeLooseRef("refs/heads/C", A); + heads = refdir.getRefs(RefDatabase.ALL); + assertEquals(3, heads.size()); + assertTrue(heads.containsKey("refs/heads/A")); + assertTrue(heads.containsKey("refs/heads/B")); + assertTrue(heads.containsKey("refs/heads/C")); + + writeLooseRef("refs/heads/B", "FAIL\n"); + BUG_WorkAroundRacyGitIssues("refs/heads/B"); + + heads = refdir.getRefs(RefDatabase.ALL); + assertEquals(2, heads.size()); + + a = heads.get("refs/heads/A"); + b = heads.get("refs/heads/B"); + c = heads.get("refs/heads/C"); + + assertEquals("refs/heads/A", a.getName()); + assertEquals(A, a.getObjectId()); + + assertNull("no refs/heads/B", b); + + assertEquals("refs/heads/C", c.getName()); + assertEquals(A, c.getObjectId()); + } + + public void testGetRefs_InvalidName() throws IOException { + writeLooseRef("refs/heads/A", A); + + assertTrue("empty refs/heads", refdir.getRefs("refs/heads").isEmpty()); + assertTrue("empty objects", refdir.getRefs("objects").isEmpty()); + assertTrue("empty objects/", refdir.getRefs("objects/").isEmpty()); + } + + public void testGetRefs_HeadsOnly_AllLoose() throws IOException { + Map heads; + Ref a, b; + + writeLooseRef("refs/heads/A", A); + writeLooseRef("refs/heads/B", B); + writeLooseRef("refs/tags/v1.0", v1_0); + + heads = refdir.getRefs(R_HEADS); + assertEquals(2, heads.size()); + + a = heads.get("A"); + b = heads.get("B"); + + assertEquals("refs/heads/A", a.getName()); + assertEquals("refs/heads/B", b.getName()); + + assertEquals(A, a.getObjectId()); + assertEquals(B, b.getObjectId()); + } + + public void testGetRefs_HeadsOnly_AllPacked1() throws IOException { + Map heads; + Ref a; + + deleteLooseRef(HEAD); + writePackedRef("refs/heads/A", A); + + heads = refdir.getRefs(R_HEADS); + assertEquals(1, heads.size()); + + a = heads.get("A"); + + assertEquals("refs/heads/A", a.getName()); + assertEquals(A, a.getObjectId()); + } + + public void testGetRefs_HeadsOnly_SymrefToPacked() throws IOException { + Map heads; + Ref master, other; + + writeLooseRef("refs/heads/other", "ref: refs/heads/master\n"); + writePackedRef("refs/heads/master", A); + + heads = refdir.getRefs(R_HEADS); + assertEquals(2, heads.size()); + + master = heads.get("master"); + other = heads.get("other"); + + assertEquals("refs/heads/master", master.getName()); + assertEquals(A, master.getObjectId()); + + assertEquals("refs/heads/other", other.getName()); + assertEquals(A, other.getObjectId()); + assertSame(master, other.getTarget()); + } + + public void testGetRefs_HeadsOnly_Mixed() throws IOException { + Map heads; + Ref a, b; + + writeLooseRef("refs/heads/A", A); + writeLooseRef("refs/heads/B", B); + writePackedRef("refs/tags/v1.0", v1_0); + + heads = refdir.getRefs(R_HEADS); + assertEquals(2, heads.size()); + + a = heads.get("A"); + b = heads.get("B"); + + assertEquals("refs/heads/A", a.getName()); + assertEquals("refs/heads/B", b.getName()); + + assertEquals(A, a.getObjectId()); + assertEquals(B, b.getObjectId()); + } + + public void testGetRefs_TagsOnly_AllLoose() throws IOException { + Map tags; + Ref a; + + writeLooseRef("refs/heads/A", A); + writeLooseRef("refs/tags/v1.0", v1_0); + + tags = refdir.getRefs(R_TAGS); + assertEquals(1, tags.size()); + + a = tags.get("v1.0"); + + assertEquals("refs/tags/v1.0", a.getName()); + assertEquals(v1_0, a.getObjectId()); + } + + public void testGetRefs_TagsOnly_AllPacked() throws IOException { + Map tags; + Ref a; + + deleteLooseRef(HEAD); + writePackedRef("refs/tags/v1.0", v1_0); + + tags = refdir.getRefs(R_TAGS); + assertEquals(1, tags.size()); + + a = tags.get("v1.0"); + + assertEquals("refs/tags/v1.0", a.getName()); + assertEquals(v1_0, a.getObjectId()); + } + + public void testGetRefs_DiscoversNewLoose1() throws IOException { + Map orig, next; + Ref orig_r, next_r; + + writeLooseRef("refs/heads/master", A); + orig = refdir.getRefs(RefDatabase.ALL); + + writeLooseRef("refs/heads/next", B); + next = refdir.getRefs(RefDatabase.ALL); + + assertEquals(2, orig.size()); + assertEquals(3, next.size()); + + assertFalse(orig.containsKey("refs/heads/next")); + assertTrue(next.containsKey("refs/heads/next")); + + orig_r = orig.get("refs/heads/master"); + next_r = next.get("refs/heads/master"); + assertEquals(A, orig_r.getObjectId()); + assertSame("uses cached instance", orig_r, next_r); + assertSame("same HEAD", orig_r, orig.get(HEAD).getTarget()); + assertSame("same HEAD", orig_r, next.get(HEAD).getTarget()); + + next_r = next.get("refs/heads/next"); + assertSame(LOOSE, next_r.getStorage()); + assertEquals(B, next_r.getObjectId()); + } + + public void testGetRefs_DiscoversNewLoose2() throws IOException { + Map orig, next, news; + + writeLooseRef("refs/heads/pu", A); + orig = refdir.getRefs(RefDatabase.ALL); + + writeLooseRef("refs/heads/new/B", B); + news = refdir.getRefs("refs/heads/new/"); + next = refdir.getRefs(RefDatabase.ALL); + + assertEquals(1, orig.size()); + assertEquals(2, next.size()); + assertEquals(1, news.size()); + + assertTrue(orig.containsKey("refs/heads/pu")); + assertTrue(next.containsKey("refs/heads/pu")); + assertFalse(news.containsKey("refs/heads/pu")); + + assertFalse(orig.containsKey("refs/heads/new/B")); + assertTrue(next.containsKey("refs/heads/new/B")); + assertTrue(news.containsKey("B")); + } + + public void testGetRefs_DiscoversModifiedLoose() throws IOException { + Map all; + + writeLooseRef("refs/heads/master", A); + all = refdir.getRefs(RefDatabase.ALL); + assertEquals(A, all.get(HEAD).getObjectId()); + + writeLooseRef("refs/heads/master", B); + BUG_WorkAroundRacyGitIssues("refs/heads/master"); + all = refdir.getRefs(RefDatabase.ALL); + assertEquals(B, all.get(HEAD).getObjectId()); + } + + public void testGetRef_DiscoversModifiedLoose() throws IOException { + Map all; + + writeLooseRef("refs/heads/master", A); + all = refdir.getRefs(RefDatabase.ALL); + assertEquals(A, all.get(HEAD).getObjectId()); + + writeLooseRef("refs/heads/master", B); + BUG_WorkAroundRacyGitIssues("refs/heads/master"); + + Ref master = refdir.getRef("refs/heads/master"); + assertEquals(B, master.getObjectId()); + } + + public void testGetRefs_DiscoversDeletedLoose1() throws IOException { + Map orig, next; + Ref orig_r, next_r; + + writeLooseRef("refs/heads/B", B); + writeLooseRef("refs/heads/master", A); + orig = refdir.getRefs(RefDatabase.ALL); + + deleteLooseRef("refs/heads/B"); + next = refdir.getRefs(RefDatabase.ALL); + + assertEquals(3, orig.size()); + assertEquals(2, next.size()); + + assertTrue(orig.containsKey("refs/heads/B")); + assertFalse(next.containsKey("refs/heads/B")); + + orig_r = orig.get("refs/heads/master"); + next_r = next.get("refs/heads/master"); + assertEquals(A, orig_r.getObjectId()); + assertSame("uses cached instance", orig_r, next_r); + assertSame("same HEAD", orig_r, orig.get(HEAD).getTarget()); + assertSame("same HEAD", orig_r, next.get(HEAD).getTarget()); + + orig_r = orig.get("refs/heads/B"); + assertSame(LOOSE, orig_r.getStorage()); + assertEquals(B, orig_r.getObjectId()); + } + + public void testGetRef_DiscoversDeletedLoose() throws IOException { + Map all; + + writeLooseRef("refs/heads/master", A); + all = refdir.getRefs(RefDatabase.ALL); + assertEquals(A, all.get(HEAD).getObjectId()); + + deleteLooseRef("refs/heads/master"); + assertNull(refdir.getRef("refs/heads/master")); + assertTrue(refdir.getRefs(RefDatabase.ALL).isEmpty()); + } + + public void testGetRefs_DiscoversDeletedLoose2() throws IOException { + Map orig, next; + + writeLooseRef("refs/heads/master", A); + writeLooseRef("refs/heads/pu", B); + orig = refdir.getRefs(RefDatabase.ALL); + + deleteLooseRef("refs/heads/pu"); + next = refdir.getRefs(RefDatabase.ALL); + + assertEquals(3, orig.size()); + assertEquals(2, next.size()); + + assertTrue(orig.containsKey("refs/heads/pu")); + assertFalse(next.containsKey("refs/heads/pu")); + } + + public void testGetRefs_DiscoversDeletedLoose3() throws IOException { + Map orig, next; + + writeLooseRef("refs/heads/master", A); + writeLooseRef("refs/heads/next", B); + writeLooseRef("refs/heads/pu", B); + writeLooseRef("refs/tags/v1.0", v1_0); + orig = refdir.getRefs(RefDatabase.ALL); + + deleteLooseRef("refs/heads/pu"); + deleteLooseRef("refs/heads/next"); + next = refdir.getRefs(RefDatabase.ALL); + + assertEquals(5, orig.size()); + assertEquals(3, next.size()); + + assertTrue(orig.containsKey("refs/heads/pu")); + assertTrue(orig.containsKey("refs/heads/next")); + assertFalse(next.containsKey("refs/heads/pu")); + assertFalse(next.containsKey("refs/heads/next")); + } + + public void testGetRefs_DiscoversDeletedLoose4() throws IOException { + Map orig, next; + Ref orig_r, next_r; + + writeLooseRef("refs/heads/B", B); + writeLooseRef("refs/heads/master", A); + orig = refdir.getRefs(RefDatabase.ALL); + + deleteLooseRef("refs/heads/master"); + next = refdir.getRefs("refs/heads/"); + + assertEquals(3, orig.size()); + assertEquals(1, next.size()); + + assertTrue(orig.containsKey("refs/heads/B")); + assertTrue(orig.containsKey("refs/heads/master")); + assertTrue(next.containsKey("B")); + assertFalse(next.containsKey("master")); + + orig_r = orig.get("refs/heads/B"); + next_r = next.get("B"); + assertEquals(B, orig_r.getObjectId()); + assertSame("uses cached instance", orig_r, next_r); + } + + public void testGetRefs_DiscoversDeletedLoose5() throws IOException { + Map orig, next; + + writeLooseRef("refs/heads/master", A); + writeLooseRef("refs/heads/pu", B); + orig = refdir.getRefs(RefDatabase.ALL); + + deleteLooseRef("refs/heads/pu"); + writeLooseRef("refs/tags/v1.0", v1_0); + next = refdir.getRefs(RefDatabase.ALL); + + assertEquals(3, orig.size()); + assertEquals(3, next.size()); + + assertTrue(orig.containsKey("refs/heads/pu")); + assertFalse(orig.containsKey("refs/tags/v1.0")); + assertFalse(next.containsKey("refs/heads/pu")); + assertTrue(next.containsKey("refs/tags/v1.0")); + } + + public void testGetRefs_SkipsLockFiles() throws IOException { + Map all; + + writeLooseRef("refs/heads/master", A); + writeLooseRef("refs/heads/pu.lock", B); + all = refdir.getRefs(RefDatabase.ALL); + + assertEquals(2, all.size()); + + assertTrue(all.containsKey(HEAD)); + assertTrue(all.containsKey("refs/heads/master")); + assertFalse(all.containsKey("refs/heads/pu.lock")); + } + + public void testGetRefs_CycleInSymbolicRef() throws IOException { + Map all; + Ref r; + + writeLooseRef("refs/1", "ref: refs/2\n"); + writeLooseRef("refs/2", "ref: refs/3\n"); + writeLooseRef("refs/3", "ref: refs/4\n"); + writeLooseRef("refs/4", "ref: refs/5\n"); + writeLooseRef("refs/5", "ref: refs/end\n"); + writeLooseRef("refs/end", A); + + all = refdir.getRefs(RefDatabase.ALL); + r = all.get("refs/1"); + assertNotNull("has 1", r); + + assertEquals("refs/1", r.getName()); + assertEquals(A, r.getObjectId()); + assertTrue(r.isSymbolic()); + + r = r.getTarget(); + assertEquals("refs/2", r.getName()); + assertEquals(A, r.getObjectId()); + assertTrue(r.isSymbolic()); + + r = r.getTarget(); + assertEquals("refs/3", r.getName()); + assertEquals(A, r.getObjectId()); + assertTrue(r.isSymbolic()); + + r = r.getTarget(); + assertEquals("refs/4", r.getName()); + assertEquals(A, r.getObjectId()); + assertTrue(r.isSymbolic()); + + r = r.getTarget(); + assertEquals("refs/5", r.getName()); + assertEquals(A, r.getObjectId()); + assertTrue(r.isSymbolic()); + + r = r.getTarget(); + assertEquals("refs/end", r.getName()); + assertEquals(A, r.getObjectId()); + assertFalse(r.isSymbolic()); + + writeLooseRef("refs/5", "ref: refs/6\n"); + writeLooseRef("refs/6", "ref: refs/end\n"); + BUG_WorkAroundRacyGitIssues("refs/5"); + all = refdir.getRefs(RefDatabase.ALL); + r = all.get("refs/1"); + assertNull("mising 1 due to cycle", r); + } + + public void testGetRefs_PackedNotPeeled_Sorted() throws IOException { + Map all; + + writePackedRefs("" + // + A.name() + " refs/heads/master\n" + // + B.name() + " refs/heads/other\n" + // + v1_0.name() + " refs/tags/v1.0\n"); + all = refdir.getRefs(RefDatabase.ALL); + + assertEquals(4, all.size()); + final Ref head = all.get(HEAD); + final Ref master = all.get("refs/heads/master"); + final Ref other = all.get("refs/heads/other"); + final Ref tag = all.get("refs/tags/v1.0"); + + assertEquals(A, master.getObjectId()); + assertFalse(master.isPeeled()); + assertNull(master.getPeeledObjectId()); + + assertEquals(B, other.getObjectId()); + assertFalse(other.isPeeled()); + assertNull(other.getPeeledObjectId()); + + assertSame(master, head.getTarget()); + assertEquals(A, head.getObjectId()); + assertFalse(head.isPeeled()); + assertNull(head.getPeeledObjectId()); + + assertEquals(v1_0, tag.getObjectId()); + assertFalse(tag.isPeeled()); + assertNull(tag.getPeeledObjectId()); + } + + public void testGetRef_PackedNotPeeled_WrongSort() throws IOException { + writePackedRefs("" + // + v1_0.name() + " refs/tags/v1.0\n" + // + B.name() + " refs/heads/other\n" + // + A.name() + " refs/heads/master\n"); + + final Ref head = refdir.getRef(HEAD); + final Ref master = refdir.getRef("refs/heads/master"); + final Ref other = refdir.getRef("refs/heads/other"); + final Ref tag = refdir.getRef("refs/tags/v1.0"); + + assertEquals(A, master.getObjectId()); + assertFalse(master.isPeeled()); + assertNull(master.getPeeledObjectId()); + + assertEquals(B, other.getObjectId()); + assertFalse(other.isPeeled()); + assertNull(other.getPeeledObjectId()); + + assertSame(master, head.getTarget()); + assertEquals(A, head.getObjectId()); + assertFalse(head.isPeeled()); + assertNull(head.getPeeledObjectId()); + + assertEquals(v1_0, tag.getObjectId()); + assertFalse(tag.isPeeled()); + assertNull(tag.getPeeledObjectId()); + } + + public void testGetRefs_PackedWithPeeled() throws IOException { + Map all; + + writePackedRefs("# pack-refs with: peeled \n" + // + A.name() + " refs/heads/master\n" + // + B.name() + " refs/heads/other\n" + // + v1_0.name() + " refs/tags/v1.0\n" + // + "^" + v1_0.getObject().name() + "\n"); + all = refdir.getRefs(RefDatabase.ALL); + + assertEquals(4, all.size()); + final Ref head = all.get(HEAD); + final Ref master = all.get("refs/heads/master"); + final Ref other = all.get("refs/heads/other"); + final Ref tag = all.get("refs/tags/v1.0"); + + assertEquals(A, master.getObjectId()); + assertTrue(master.isPeeled()); + assertNull(master.getPeeledObjectId()); + + assertEquals(B, other.getObjectId()); + assertTrue(other.isPeeled()); + assertNull(other.getPeeledObjectId()); + + assertSame(master, head.getTarget()); + assertEquals(A, head.getObjectId()); + assertTrue(head.isPeeled()); + assertNull(head.getPeeledObjectId()); + + assertEquals(v1_0, tag.getObjectId()); + assertTrue(tag.isPeeled()); + assertEquals(v1_0.getObject(), tag.getPeeledObjectId()); + } + + public void testGetRef_EmptyDatabase() throws IOException { + Ref r; + + r = refdir.getRef(HEAD); + assertTrue(r.isSymbolic()); + assertSame(LOOSE, r.getStorage()); + assertEquals("refs/heads/master", r.getTarget().getName()); + assertSame(NEW, r.getTarget().getStorage()); + assertNull(r.getTarget().getObjectId()); + + assertNull(refdir.getRef("refs/heads/master")); + assertNull(refdir.getRef("refs/tags/v1.0")); + assertNull(refdir.getRef("FETCH_HEAD")); + assertNull(refdir.getRef("NOT.A.REF.NAME")); + assertNull(refdir.getRef("master")); + assertNull(refdir.getRef("v1.0")); + } + + public void testGetRef_FetchHead() throws IOException { + // This is an odd special case where we need to make sure we read + // exactly the first 40 bytes of the file and nothing further on + // that line, or the remainder of the file. + write(new File(diskRepo.getDirectory(), "FETCH_HEAD"), A.name() + + "\tnot-for-merge" + + "\tbranch 'master' of git://egit.eclipse.org/jgit\n"); + + Ref r = refdir.getRef("FETCH_HEAD"); + assertFalse(r.isSymbolic()); + assertEquals(A, r.getObjectId()); + assertEquals("FETCH_HEAD", r.getName()); + assertFalse(r.isPeeled()); + assertNull(r.getPeeledObjectId()); + } + + public void testGetRef_AnyHeadWithGarbage() throws IOException { + write(new File(diskRepo.getDirectory(), "refs/heads/A"), A.name() + + "012345 . this is not a standard reference\n" + + "#and even more junk\n"); + + Ref r = refdir.getRef("refs/heads/A"); + assertFalse(r.isSymbolic()); + assertEquals(A, r.getObjectId()); + assertEquals("refs/heads/A", r.getName()); + assertFalse(r.isPeeled()); + assertNull(r.getPeeledObjectId()); + } + + public void testGetRefs_CorruptSymbolicReference() throws IOException { + String name = "refs/heads/A"; + writeLooseRef(name, "ref: \n"); + assertTrue(refdir.getRefs(RefDatabase.ALL).isEmpty()); + } + + public void testGetRef_CorruptSymbolicReference() throws IOException { + String name = "refs/heads/A"; + writeLooseRef(name, "ref: \n"); + try { + refdir.getRef(name); + fail("read an invalid reference"); + } catch (IOException err) { + String msg = err.getMessage(); + assertEquals("Not a ref: " + name + ": ref:", msg); + } + } + + public void testGetRefs_CorruptObjectIdReference() throws IOException { + String name = "refs/heads/A"; + String content = "zoo" + A.name(); + writeLooseRef(name, content + "\n"); + assertTrue(refdir.getRefs(RefDatabase.ALL).isEmpty()); + } + + public void testGetRef_CorruptObjectIdReference() throws IOException { + String name = "refs/heads/A"; + String content = "zoo" + A.name(); + writeLooseRef(name, content + "\n"); + try { + refdir.getRef(name); + fail("read an invalid reference"); + } catch (IOException err) { + String msg = err.getMessage(); + assertEquals("Not a ref: " + name + ": " + content, msg); + } + } + + public void testIsNameConflicting() throws IOException { + writeLooseRef("refs/heads/a/b", A); + writePackedRef("refs/heads/q", B); + + // new references cannot replace an existing container + assertTrue(refdir.isNameConflicting("refs")); + assertTrue(refdir.isNameConflicting("refs/heads")); + assertTrue(refdir.isNameConflicting("refs/heads/a")); + + // existing reference is not conflicting + assertFalse(refdir.isNameConflicting("refs/heads/a/b")); + + // new references are not conflicting + assertFalse(refdir.isNameConflicting("refs/heads/a/d")); + assertFalse(refdir.isNameConflicting("refs/heads/master")); + + // existing reference must not be used as a container + assertTrue(refdir.isNameConflicting("refs/heads/a/b/c")); + assertTrue(refdir.isNameConflicting("refs/heads/q/master")); + } + + public void testPeelLooseTag() throws IOException { + writeLooseRef("refs/tags/v1_0", v1_0); + writeLooseRef("refs/tags/current", "ref: refs/tags/v1_0\n"); + + final Ref tag = refdir.getRef("refs/tags/v1_0"); + final Ref cur = refdir.getRef("refs/tags/current"); + + assertEquals(v1_0, tag.getObjectId()); + assertFalse(tag.isSymbolic()); + assertFalse(tag.isPeeled()); + assertNull(tag.getPeeledObjectId()); + + assertEquals(v1_0, cur.getObjectId()); + assertTrue(cur.isSymbolic()); + assertFalse(cur.isPeeled()); + assertNull(cur.getPeeledObjectId()); + + final Ref tag_p = refdir.peel(tag); + final Ref cur_p = refdir.peel(cur); + + assertNotSame(tag, tag_p); + assertFalse(tag_p.isSymbolic()); + assertTrue(tag_p.isPeeled()); + assertEquals(v1_0, tag_p.getObjectId()); + assertEquals(v1_0.getObject(), tag_p.getPeeledObjectId()); + assertSame(tag_p, refdir.peel(tag_p)); + + assertNotSame(cur, cur_p); + assertEquals("refs/tags/current", cur_p.getName()); + assertTrue(cur_p.isSymbolic()); + assertEquals("refs/tags/v1_0", cur_p.getTarget().getName()); + assertTrue(cur_p.isPeeled()); + assertEquals(v1_0, cur_p.getObjectId()); + assertEquals(v1_0.getObject(), cur_p.getPeeledObjectId()); + + // reuses cached peeling later, but not immediately due to + // the implementation so we have to fetch it once. + final Ref tag_p2 = refdir.getRef("refs/tags/v1_0"); + assertFalse(tag_p2.isSymbolic()); + assertTrue(tag_p2.isPeeled()); + assertEquals(v1_0, tag_p2.getObjectId()); + assertEquals(v1_0.getObject(), tag_p2.getPeeledObjectId()); + + assertSame(tag_p2, refdir.getRef("refs/tags/v1_0")); + assertSame(tag_p2, refdir.getRef("refs/tags/current").getTarget()); + assertSame(tag_p2, refdir.peel(tag_p2)); + } + + public void testPeelCommit() throws IOException { + writeLooseRef("refs/heads/master", A); + + Ref master = refdir.getRef("refs/heads/master"); + assertEquals(A, master.getObjectId()); + assertFalse(master.isPeeled()); + assertNull(master.getPeeledObjectId()); + + Ref master_p = refdir.peel(master); + assertNotSame(master, master_p); + assertEquals(A, master_p.getObjectId()); + assertTrue(master_p.isPeeled()); + assertNull(master_p.getPeeledObjectId()); + + // reuses cached peeling later, but not immediately due to + // the implementation so we have to fetch it once. + Ref master_p2 = refdir.getRef("refs/heads/master"); + assertNotSame(master, master_p2); + assertEquals(A, master_p2.getObjectId()); + assertTrue(master_p2.isPeeled()); + assertNull(master_p2.getPeeledObjectId()); + assertSame(master_p2, refdir.peel(master_p2)); + } + + private void writeLooseRef(String name, AnyObjectId id) throws IOException { + writeLooseRef(name, id.name() + "\n"); + } + + private void writeLooseRef(String name, String content) throws IOException { + write(new File(diskRepo.getDirectory(), name), content); + } + + private void writePackedRef(String name, AnyObjectId id) throws IOException { + writePackedRefs(id.name() + " " + name + "\n"); + } + + private void writePackedRefs(String content) throws IOException { + File pr = new File(diskRepo.getDirectory(), "packed-refs"); + write(pr, content); + } + + private void deleteLooseRef(String name) { + File path = new File(diskRepo.getDirectory(), name); + assertTrue("deleted " + name, path.delete()); + } + + /** + * Kick the timestamp of a local file. + *

+ * We shouldn't have to make these method calls. The cache is using file + * system timestamps, and on many systems unit tests run faster than the + * modification clock. Dumping the cache after we make an edit behind + * RefDirectory's back allows the tests to pass. + * + * @param name + * the file in the repository to force a time change on. + */ + private void BUG_WorkAroundRacyGitIssues(String name) { + File path = new File(diskRepo.getDirectory(), name); + long old = path.lastModified(); + long set = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009 + path.setLastModified(set); + assertTrue("time changed", old != path.lastModified()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefUpdateTest.java new file mode 100644 index 0000000000..875c2e96f5 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefUpdateTest.java @@ -0,0 +1,853 @@ +/* + * Copyright (C) 2008, Charles O'Farrell + * Copyright (C) 2009-2010, Google Inc. + * Copyright (C) 2008-2009, Robin Rosenberg + * 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.IOException; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefRename; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.SampleDataRepositoryTestCase; +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.FileRepository; +import org.eclipse.jgit.storage.file.LockFile; +import org.eclipse.jgit.storage.file.RefDirectory; +import org.eclipse.jgit.storage.file.RefDirectoryUpdate; +import org.eclipse.jgit.storage.file.ReflogReader; + +public class RefUpdateTest extends SampleDataRepositoryTestCase { + + private void writeSymref(String src, String dst) throws IOException { + RefUpdate u = db.updateRef(src); + switch (u.link(dst)) { + case NEW: + case FORCED: + case NO_CHANGE: + break; + default: + fail("link " + src + " to " + dst); + } + } + + private RefUpdate updateRef(final String name) throws IOException { + final RefUpdate ref = db.updateRef(name); + ref.setNewObjectId(db.resolve(Constants.HEAD)); + return ref; + } + + private void delete(final RefUpdate ref, final Result expected) + throws IOException { + delete(ref, expected, true, true); + } + + private void delete(final RefUpdate ref, final Result expected, + final boolean exists, final boolean removed) throws IOException { + assertEquals(exists, db.getAllRefs().containsKey(ref.getName())); + assertEquals(expected, ref.delete()); + assertEquals(!removed, db.getAllRefs().containsKey(ref.getName())); + } + + public void testNoCacheObjectIdSubclass() throws IOException { + final String newRef = "refs/heads/abc"; + final RefUpdate ru = updateRef(newRef); + final RevCommit newid = new RevCommit(ru.getNewObjectId()) { + // empty + }; + ru.setNewObjectId(newid); + Result update = ru.update(); + assertEquals(Result.NEW, update); + final Ref r = db.getAllRefs().get(newRef); + assertNotNull(r); + assertEquals(newRef, r.getName()); + assertNotNull(r.getObjectId()); + assertNotSame(newid, r.getObjectId()); + assertSame(ObjectId.class, r.getObjectId().getClass()); + assertEquals(newid.copy(), r.getObjectId()); + List reverseEntries1 = db.getReflogReader("refs/heads/abc").getReverseEntries(); + org.eclipse.jgit.storage.file.ReflogReader.Entry entry1 = reverseEntries1.get(0); + assertEquals(1, reverseEntries1.size()); + assertEquals(ObjectId.zeroId(), entry1.getOldId()); + assertEquals(r.getObjectId(), entry1.getNewId()); + assertEquals(new PersonIdent(db).toString(), entry1.getWho().toString()); + assertEquals("", entry1.getComment()); + List reverseEntries2 = db.getReflogReader("HEAD").getReverseEntries(); + assertEquals(0, reverseEntries2.size()); + } + + public void testNewNamespaceConflictWithLoosePrefixNameExists() + throws IOException { + final String newRef = "refs/heads/z"; + final RefUpdate ru = updateRef(newRef); + final RevCommit newid = new RevCommit(ru.getNewObjectId()) { + // empty + }; + ru.setNewObjectId(newid); + Result update = ru.update(); + assertEquals(Result.NEW, update); + // end setup + final String newRef2 = "refs/heads/z/a"; + final RefUpdate ru2 = updateRef(newRef2); + final RevCommit newid2 = new RevCommit(ru2.getNewObjectId()) { + // empty + }; + ru.setNewObjectId(newid2); + Result update2 = ru2.update(); + assertEquals(Result.LOCK_FAILURE, update2); + assertEquals(1, db.getReflogReader("refs/heads/z").getReverseEntries().size()); + assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); + } + + public void testNewNamespaceConflictWithPackedPrefixNameExists() + throws IOException { + final String newRef = "refs/heads/master/x"; + final RefUpdate ru = updateRef(newRef); + final RevCommit newid = new RevCommit(ru.getNewObjectId()) { + // empty + }; + ru.setNewObjectId(newid); + Result update = ru.update(); + assertEquals(Result.LOCK_FAILURE, update); + assertNull(db.getReflogReader("refs/heads/master/x")); + assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); + } + + public void testNewNamespaceConflictWithLoosePrefixOfExisting() + throws IOException { + final String newRef = "refs/heads/z/a"; + final RefUpdate ru = updateRef(newRef); + final RevCommit newid = new RevCommit(ru.getNewObjectId()) { + // empty + }; + ru.setNewObjectId(newid); + Result update = ru.update(); + assertEquals(Result.NEW, update); + // end setup + final String newRef2 = "refs/heads/z"; + final RefUpdate ru2 = updateRef(newRef2); + final RevCommit newid2 = new RevCommit(ru2.getNewObjectId()) { + // empty + }; + ru.setNewObjectId(newid2); + Result update2 = ru2.update(); + assertEquals(Result.LOCK_FAILURE, update2); + assertEquals(1, db.getReflogReader("refs/heads/z/a").getReverseEntries().size()); + assertNull(db.getReflogReader("refs/heads/z")); + assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); + } + + public void testNewNamespaceConflictWithPackedPrefixOfExisting() + throws IOException { + final String newRef = "refs/heads/prefix"; + final RefUpdate ru = updateRef(newRef); + final RevCommit newid = new RevCommit(ru.getNewObjectId()) { + // empty + }; + ru.setNewObjectId(newid); + Result update = ru.update(); + assertEquals(Result.LOCK_FAILURE, update); + assertNull(db.getReflogReader("refs/heads/prefix")); + assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); + } + + /** + * Delete a ref that is pointed to by HEAD + * + * @throws IOException + */ + public void testDeleteHEADreferencedRef() throws IOException { + ObjectId pid = db.resolve("refs/heads/master^"); + RefUpdate updateRef = db.updateRef("refs/heads/master"); + updateRef.setNewObjectId(pid); + updateRef.setForceUpdate(true); + Result update = updateRef.update(); + assertEquals(Result.FORCED, update); // internal + + RefUpdate updateRef2 = db.updateRef("refs/heads/master"); + Result delete = updateRef2.delete(); + assertEquals(Result.REJECTED_CURRENT_BRANCH, delete); + assertEquals(pid, db.resolve("refs/heads/master")); + assertEquals(1,db.getReflogReader("refs/heads/master").getReverseEntries().size()); + assertEquals(0,db.getReflogReader("HEAD").getReverseEntries().size()); + } + + public void testLooseDelete() throws IOException { + final String newRef = "refs/heads/abc"; + RefUpdate ref = updateRef(newRef); + ref.update(); // create loose ref + ref = updateRef(newRef); // refresh + delete(ref, Result.NO_CHANGE); + assertNull(db.getReflogReader("refs/heads/abc")); + } + + public void testDeleteHead() throws IOException { + final RefUpdate ref = updateRef(Constants.HEAD); + delete(ref, Result.REJECTED_CURRENT_BRANCH, true, false); + assertEquals(0, db.getReflogReader("refs/heads/master").getReverseEntries().size()); + assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); + } + + /** + * Delete a loose ref and make sure the directory in refs is deleted too, + * and the reflog dir too + * + * @throws IOException + */ + public void testDeleteLooseAndItsDirectory() throws IOException { + ObjectId pid = db.resolve("refs/heads/c^"); + RefUpdate updateRef = db.updateRef("refs/heads/z/c"); + updateRef.setNewObjectId(pid); + updateRef.setForceUpdate(true); + updateRef.setRefLogMessage("new test ref", false); + Result update = updateRef.update(); + assertEquals(Result.NEW, update); // internal + assertTrue(new File(db.getDirectory(), Constants.R_HEADS + "z") + .exists()); + assertTrue(new File(db.getDirectory(), "logs/refs/heads/z").exists()); + + // The real test here + RefUpdate updateRef2 = db.updateRef("refs/heads/z/c"); + updateRef2.setForceUpdate(true); + Result delete = updateRef2.delete(); + assertEquals(Result.FORCED, delete); + assertNull(db.resolve("refs/heads/z/c")); + assertFalse(new File(db.getDirectory(), Constants.R_HEADS + "z") + .exists()); + assertFalse(new File(db.getDirectory(), "logs/refs/heads/z").exists()); + } + + public void testDeleteNotFound() throws IOException { + final RefUpdate ref = updateRef("refs/heads/xyz"); + delete(ref, Result.NEW, false, true); + } + + public void testDeleteFastForward() throws IOException { + final RefUpdate ref = updateRef("refs/heads/a"); + delete(ref, Result.FAST_FORWARD); + } + + public void testDeleteForce() throws IOException { + final RefUpdate ref = db.updateRef("refs/heads/b"); + ref.setNewObjectId(db.resolve("refs/heads/a")); + delete(ref, Result.REJECTED, true, false); + ref.setForceUpdate(true); + delete(ref, Result.FORCED); + } + + public void testRefKeySameAsName() { + Map allRefs = db.getAllRefs(); + for (Entry e : allRefs.entrySet()) { + assertEquals(e.getKey(), e.getValue().getName()); + + } + } + + /** + * Try modify a ref forward, fast forward + * + * @throws IOException + */ + public void testUpdateRefForward() throws IOException { + ObjectId ppid = db.resolve("refs/heads/master^"); + ObjectId pid = db.resolve("refs/heads/master"); + + RefUpdate updateRef = db.updateRef("refs/heads/master"); + updateRef.setNewObjectId(ppid); + updateRef.setForceUpdate(true); + Result update = updateRef.update(); + assertEquals(Result.FORCED, update); + assertEquals(ppid, db.resolve("refs/heads/master")); + + // real test + RefUpdate updateRef2 = db.updateRef("refs/heads/master"); + updateRef2.setNewObjectId(pid); + Result update2 = updateRef2.update(); + assertEquals(Result.FAST_FORWARD, update2); + assertEquals(pid, db.resolve("refs/heads/master")); + } + + /** + * Update the HEAD ref. Only it should be changed, not what it points to. + * + * @throws Exception + */ + public void testUpdateRefDetached() throws Exception { + ObjectId pid = db.resolve("refs/heads/master"); + ObjectId ppid = db.resolve("refs/heads/master^"); + RefUpdate updateRef = db.updateRef("HEAD", true); + updateRef.setForceUpdate(true); + updateRef.setNewObjectId(ppid); + Result update = updateRef.update(); + assertEquals(Result.FORCED, update); + assertEquals(ppid, db.resolve("HEAD")); + Ref ref = db.getRef("HEAD"); + assertEquals("HEAD", ref.getName()); + assertTrue("is detached", !ref.isSymbolic()); + + // the branch HEAD referred to is left untouched + assertEquals(pid, db.resolve("refs/heads/master")); + ReflogReader reflogReader = new ReflogReader(db, "HEAD"); + org.eclipse.jgit.storage.file.ReflogReader.Entry e = reflogReader.getReverseEntries().get(0); + assertEquals(pid, e.getOldId()); + assertEquals(ppid, e.getNewId()); + assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress()); + assertEquals("GIT_COMMITTER_NAME", e.getWho().getName()); + assertEquals(1250379778000L, e.getWho().getWhen().getTime()); + } + + /** + * Update the HEAD ref when the referenced branch is unborn + * + * @throws Exception + */ + public void testUpdateRefDetachedUnbornHead() throws Exception { + ObjectId ppid = db.resolve("refs/heads/master^"); + writeSymref("HEAD", "refs/heads/unborn"); + RefUpdate updateRef = db.updateRef("HEAD", true); + updateRef.setForceUpdate(true); + updateRef.setNewObjectId(ppid); + Result update = updateRef.update(); + assertEquals(Result.NEW, update); + assertEquals(ppid, db.resolve("HEAD")); + Ref ref = db.getRef("HEAD"); + assertEquals("HEAD", ref.getName()); + assertTrue("is detached", !ref.isSymbolic()); + + // the branch HEAD referred to is left untouched + assertNull(db.resolve("refs/heads/unborn")); + ReflogReader reflogReader = new ReflogReader(db, "HEAD"); + org.eclipse.jgit.storage.file.ReflogReader.Entry e = reflogReader.getReverseEntries().get(0); + assertEquals(ObjectId.zeroId(), e.getOldId()); + assertEquals(ppid, e.getNewId()); + assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress()); + assertEquals("GIT_COMMITTER_NAME", e.getWho().getName()); + assertEquals(1250379778000L, e.getWho().getWhen().getTime()); + } + + /** + * Delete a ref that exists both as packed and loose. Make sure the ref + * cannot be resolved after delete. + * + * @throws IOException + */ + public void testDeleteLoosePacked() throws IOException { + ObjectId pid = db.resolve("refs/heads/c^"); + RefUpdate updateRef = db.updateRef("refs/heads/c"); + updateRef.setNewObjectId(pid); + updateRef.setForceUpdate(true); + Result update = updateRef.update(); + assertEquals(Result.FORCED, update); // internal + + // The real test here + RefUpdate updateRef2 = db.updateRef("refs/heads/c"); + updateRef2.setForceUpdate(true); + Result delete = updateRef2.delete(); + assertEquals(Result.FORCED, delete); + assertNull(db.resolve("refs/heads/c")); + } + + /** + * Try modify a ref to same + * + * @throws IOException + */ + public void testUpdateRefNoChange() throws IOException { + ObjectId pid = db.resolve("refs/heads/master"); + RefUpdate updateRef = db.updateRef("refs/heads/master"); + updateRef.setNewObjectId(pid); + Result update = updateRef.update(); + assertEquals(Result.NO_CHANGE, update); + assertEquals(pid, db.resolve("refs/heads/master")); + } + + /** + * Test case originating from + * bug 285991 + * + * Make sure the in memory cache is updated properly after + * update of symref. This one did not fail because the + * ref was packed due to implementation issues. + * + * @throws Exception + */ + public void testRefsCacheAfterUpdate() throws Exception { + // Do not use the defalt repo for this case. + Map allRefs = db.getAllRefs(); + ObjectId oldValue = db.resolve("HEAD"); + ObjectId newValue = db.resolve("HEAD^"); + // first make HEAD refer to loose ref + RefUpdate updateRef = db.updateRef(Constants.HEAD); + updateRef.setForceUpdate(true); + updateRef.setNewObjectId(newValue); + Result update = updateRef.update(); + assertEquals(Result.FORCED, update); + + // now update that ref + updateRef = db.updateRef(Constants.HEAD); + updateRef.setForceUpdate(true); + updateRef.setNewObjectId(oldValue); + update = updateRef.update(); + assertEquals(Result.FAST_FORWARD, update); + + allRefs = db.getAllRefs(); + Ref master = allRefs.get("refs/heads/master"); + Ref head = allRefs.get("HEAD"); + assertEquals("refs/heads/master", master.getName()); + assertEquals("HEAD", head.getName()); + assertTrue("is symbolic reference", head.isSymbolic()); + assertSame(master, head.getTarget()); + } + + /** + * Test case originating from + * bug 285991 + * + * Make sure the in memory cache is updated properly after + * update of symref. + * + * @throws Exception + */ + public void testRefsCacheAfterUpdateLooseOnly() throws Exception { + // Do not use the defalt repo for this case. + Map allRefs = db.getAllRefs(); + ObjectId oldValue = db.resolve("HEAD"); + writeSymref(Constants.HEAD, "refs/heads/newref"); + RefUpdate updateRef = db.updateRef(Constants.HEAD); + updateRef.setForceUpdate(true); + updateRef.setNewObjectId(oldValue); + Result update = updateRef.update(); + assertEquals(Result.NEW, update); + + allRefs = db.getAllRefs(); + Ref head = allRefs.get("HEAD"); + Ref newref = allRefs.get("refs/heads/newref"); + assertEquals("refs/heads/newref", newref.getName()); + assertEquals("HEAD", head.getName()); + assertTrue("is symbolic reference", head.isSymbolic()); + assertSame(newref, head.getTarget()); + } + + /** + * Try modify a ref, but get wrong expected old value + * + * @throws IOException + */ + public void testUpdateRefLockFailureWrongOldValue() throws IOException { + ObjectId pid = db.resolve("refs/heads/master"); + RefUpdate updateRef = db.updateRef("refs/heads/master"); + updateRef.setNewObjectId(pid); + updateRef.setExpectedOldObjectId(db.resolve("refs/heads/master^")); + Result update = updateRef.update(); + assertEquals(Result.LOCK_FAILURE, update); + assertEquals(pid, db.resolve("refs/heads/master")); + } + + /** + * Try modify a ref forward, fast forward, checking old value first + * + * @throws IOException + */ + public void testUpdateRefForwardWithCheck1() throws IOException { + ObjectId ppid = db.resolve("refs/heads/master^"); + ObjectId pid = db.resolve("refs/heads/master"); + + RefUpdate updateRef = db.updateRef("refs/heads/master"); + updateRef.setNewObjectId(ppid); + updateRef.setForceUpdate(true); + Result update = updateRef.update(); + assertEquals(Result.FORCED, update); + assertEquals(ppid, db.resolve("refs/heads/master")); + + // real test + RefUpdate updateRef2 = db.updateRef("refs/heads/master"); + updateRef2.setExpectedOldObjectId(ppid); + updateRef2.setNewObjectId(pid); + Result update2 = updateRef2.update(); + assertEquals(Result.FAST_FORWARD, update2); + assertEquals(pid, db.resolve("refs/heads/master")); + } + + /** + * Try modify a ref forward, fast forward, checking old commit first + * + * @throws IOException + */ + public void testUpdateRefForwardWithCheck2() throws IOException { + ObjectId ppid = db.resolve("refs/heads/master^"); + ObjectId pid = db.resolve("refs/heads/master"); + + RefUpdate updateRef = db.updateRef("refs/heads/master"); + updateRef.setNewObjectId(ppid); + updateRef.setForceUpdate(true); + Result update = updateRef.update(); + assertEquals(Result.FORCED, update); + assertEquals(ppid, db.resolve("refs/heads/master")); + + // real test + RevCommit old = new RevWalk(db).parseCommit(ppid); + RefUpdate updateRef2 = db.updateRef("refs/heads/master"); + updateRef2.setExpectedOldObjectId(old); + updateRef2.setNewObjectId(pid); + Result update2 = updateRef2.update(); + assertEquals(Result.FAST_FORWARD, update2); + assertEquals(pid, db.resolve("refs/heads/master")); + } + + /** + * Try modify a ref that is locked + * + * @throws IOException + */ + public void testUpdateRefLockFailureLocked() throws IOException { + ObjectId opid = db.resolve("refs/heads/master"); + ObjectId pid = db.resolve("refs/heads/master^"); + RefUpdate updateRef = db.updateRef("refs/heads/master"); + updateRef.setNewObjectId(pid); + LockFile lockFile1 = new LockFile(new File(db.getDirectory(),"refs/heads/master")); + try { + assertTrue(lockFile1.lock()); // precondition to test + Result update = updateRef.update(); + assertEquals(Result.LOCK_FAILURE, update); + assertEquals(opid, db.resolve("refs/heads/master")); + LockFile lockFile2 = new LockFile(new File(db.getDirectory(),"refs/heads/master")); + assertFalse(lockFile2.lock()); // was locked, still is + } finally { + lockFile1.unlock(); + } + } + + /** + * Try to delete a ref. Delete requires force. + * + * @throws IOException + */ + public void testDeleteLoosePackedRejected() throws IOException { + ObjectId pid = db.resolve("refs/heads/c^"); + ObjectId oldpid = db.resolve("refs/heads/c"); + RefUpdate updateRef = db.updateRef("refs/heads/c"); + updateRef.setNewObjectId(pid); + Result update = updateRef.update(); + assertEquals(Result.REJECTED, update); + assertEquals(oldpid, db.resolve("refs/heads/c")); + } + + public void testRenameBranchNoPreviousLog() throws IOException { + assertFalse("precondition, no log on old branchg", new File(db + .getDirectory(), "logs/refs/heads/b").exists()); + ObjectId rb = db.resolve("refs/heads/b"); + ObjectId oldHead = db.resolve(Constants.HEAD); + assertFalse(rb.equals(oldHead)); // assumption for this test + RefRename renameRef = db.renameRef("refs/heads/b", + "refs/heads/new/name"); + Result result = renameRef.rename(); + assertEquals(Result.RENAMED, result); + assertEquals(rb, db.resolve("refs/heads/new/name")); + assertNull(db.resolve("refs/heads/b")); + assertEquals(1, db.getReflogReader("new/name").getReverseEntries().size()); + assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name") + .getLastEntry().getComment()); + assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists()); + assertEquals(oldHead, db.resolve(Constants.HEAD)); // unchanged + } + + public void testRenameBranchHasPreviousLog() throws IOException { + ObjectId rb = db.resolve("refs/heads/b"); + ObjectId oldHead = db.resolve(Constants.HEAD); + assertFalse("precondition for this test, branch b != HEAD", rb + .equals(oldHead)); + writeReflog(db, rb, rb, "Just a message", "refs/heads/b"); + assertTrue("log on old branch", new File(db.getDirectory(), + "logs/refs/heads/b").exists()); + RefRename renameRef = db.renameRef("refs/heads/b", + "refs/heads/new/name"); + Result result = renameRef.rename(); + assertEquals(Result.RENAMED, result); + assertEquals(rb, db.resolve("refs/heads/new/name")); + assertNull(db.resolve("refs/heads/b")); + assertEquals(2, db.getReflogReader("new/name").getReverseEntries().size()); + assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name") + .getLastEntry().getComment()); + assertEquals("Just a message", db.getReflogReader("new/name") + .getReverseEntries().get(1).getComment()); + assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists()); + assertEquals(oldHead, db.resolve(Constants.HEAD)); // unchanged + } + + public void testRenameCurrentBranch() throws IOException { + ObjectId rb = db.resolve("refs/heads/b"); + writeSymref(Constants.HEAD, "refs/heads/b"); + ObjectId oldHead = db.resolve(Constants.HEAD); + assertTrue("internal test condition, b == HEAD", rb.equals(oldHead)); + writeReflog(db, rb, rb, "Just a message", "refs/heads/b"); + assertTrue("log on old branch", new File(db.getDirectory(), + "logs/refs/heads/b").exists()); + RefRename renameRef = db.renameRef("refs/heads/b", + "refs/heads/new/name"); + Result result = renameRef.rename(); + assertEquals(Result.RENAMED, result); + assertEquals(rb, db.resolve("refs/heads/new/name")); + assertNull(db.resolve("refs/heads/b")); + assertEquals("Branch: renamed b to new/name", db.getReflogReader( + "new/name").getLastEntry().getComment()); + assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists()); + assertEquals(rb, db.resolve(Constants.HEAD)); + assertEquals(2, db.getReflogReader("new/name").getReverseEntries().size()); + assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name").getReverseEntries().get(0).getComment()); + assertEquals("Just a message", db.getReflogReader("new/name").getReverseEntries().get(1).getComment()); + } + + public void testRenameBranchAlsoInPack() throws IOException { + ObjectId rb = db.resolve("refs/heads/b"); + ObjectId rb2 = db.resolve("refs/heads/b~1"); + assertEquals(Ref.Storage.PACKED, db.getRef("refs/heads/b").getStorage()); + RefUpdate updateRef = db.updateRef("refs/heads/b"); + updateRef.setNewObjectId(rb2); + updateRef.setForceUpdate(true); + Result update = updateRef.update(); + assertEquals("internal check new ref is loose", Result.FORCED, update); + assertEquals(Ref.Storage.LOOSE, db.getRef("refs/heads/b").getStorage()); + writeReflog(db, rb, rb, "Just a message", "refs/heads/b"); + assertTrue("log on old branch", new File(db.getDirectory(), + "logs/refs/heads/b").exists()); + RefRename renameRef = db.renameRef("refs/heads/b", + "refs/heads/new/name"); + Result result = renameRef.rename(); + assertEquals(Result.RENAMED, result); + assertEquals(rb2, db.resolve("refs/heads/new/name")); + assertNull(db.resolve("refs/heads/b")); + assertEquals("Branch: renamed b to new/name", db.getReflogReader( + "new/name").getLastEntry().getComment()); + assertEquals(3, db.getReflogReader("refs/heads/new/name").getReverseEntries().size()); + assertEquals("Branch: renamed b to new/name", db.getReflogReader("refs/heads/new/name").getReverseEntries().get(0).getComment()); + assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); + // make sure b's log file is gone too. + assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists()); + + // Create new Repository instance, to reread caches and make sure our + // assumptions are persistent. + Repository ndb = new FileRepository(db.getDirectory()); + assertEquals(rb2, ndb.resolve("refs/heads/new/name")); + assertNull(ndb.resolve("refs/heads/b")); + } + + public void tryRenameWhenLocked(String toLock, String fromName, + String toName, String headPointsTo) throws IOException { + // setup + writeSymref(Constants.HEAD, headPointsTo); + ObjectId oldfromId = db.resolve(fromName); + ObjectId oldHeadId = db.resolve(Constants.HEAD); + writeReflog(db, oldfromId, oldfromId, "Just a message", + fromName); + List oldFromLog = db + .getReflogReader(fromName).getReverseEntries(); + List oldHeadLog = oldHeadId != null ? db + .getReflogReader(Constants.HEAD).getReverseEntries() : null; + + assertTrue("internal check, we have a log", new File(db.getDirectory(), + "logs/" + fromName).exists()); + + // "someone" has branch X locked + LockFile lockFile = new LockFile(new File(db.getDirectory(), toLock)); + try { + assertTrue(lockFile.lock()); + + // Now this is our test + RefRename renameRef = db.renameRef(fromName, toName); + Result result = renameRef.rename(); + assertEquals(Result.LOCK_FAILURE, result); + + // Check that the involved refs are the same despite the failure + assertExists(false, toName); + if (!toLock.equals(toName)) + assertExists(false, toName + ".lock"); + assertExists(true, toLock + ".lock"); + if (!toLock.equals(fromName)) + assertExists(false, "logs/" + fromName + ".lock"); + assertExists(false, "logs/" + toName + ".lock"); + assertEquals(oldHeadId, db.resolve(Constants.HEAD)); + assertEquals(oldfromId, db.resolve(fromName)); + assertNull(db.resolve(toName)); + assertEquals(oldFromLog.toString(), db.getReflogReader(fromName) + .getReverseEntries().toString()); + if (oldHeadId != null) + assertEquals(oldHeadLog.toString(), db.getReflogReader( + Constants.HEAD).getReverseEntries().toString()); + } finally { + lockFile.unlock(); + } + } + + private void assertExists(boolean positive, String toName) { + assertEquals(toName + (positive ? " " : " does not ") + "exist", + positive, new File(db.getDirectory(), toName).exists()); + } + + public void testRenameBranchCannotLockAFileHEADisFromLockHEAD() + throws IOException { + tryRenameWhenLocked("HEAD", "refs/heads/b", "refs/heads/new/name", + "refs/heads/b"); + } + + public void testRenameBranchCannotLockAFileHEADisFromLockFrom() + throws IOException { + tryRenameWhenLocked("refs/heads/b", "refs/heads/b", + "refs/heads/new/name", "refs/heads/b"); + } + + public void testRenameBranchCannotLockAFileHEADisFromLockTo() + throws IOException { + tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b", + "refs/heads/new/name", "refs/heads/b"); + } + + public void testRenameBranchCannotLockAFileHEADisToLockFrom() + throws IOException { + tryRenameWhenLocked("refs/heads/b", "refs/heads/b", + "refs/heads/new/name", "refs/heads/new/name"); + } + + public void testRenameBranchCannotLockAFileHEADisToLockTo() + throws IOException { + tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b", + "refs/heads/new/name", "refs/heads/new/name"); + } + + public void testRenameBranchCannotLockAFileHEADisOtherLockFrom() + throws IOException { + tryRenameWhenLocked("refs/heads/b", "refs/heads/b", + "refs/heads/new/name", "refs/heads/a"); + } + + public void testRenameBranchCannotLockAFileHEADisOtherLockTo() + throws IOException { + tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b", + "refs/heads/new/name", "refs/heads/a"); + } + + public void testRenameRefNameColission1avoided() throws IOException { + // setup + ObjectId rb = db.resolve("refs/heads/b"); + writeSymref(Constants.HEAD, "refs/heads/a"); + RefUpdate updateRef = db.updateRef("refs/heads/a"); + updateRef.setNewObjectId(rb); + updateRef.setRefLogMessage("Setup", false); + assertEquals(Result.FAST_FORWARD, updateRef.update()); + ObjectId oldHead = db.resolve(Constants.HEAD); + assertTrue(rb.equals(oldHead)); // assumption for this test + writeReflog(db, rb, rb, "Just a message", "refs/heads/a"); + assertTrue("internal check, we have a log", new File(db.getDirectory(), + "logs/refs/heads/a").exists()); + + // Now this is our test + RefRename renameRef = db.renameRef("refs/heads/a", "refs/heads/a/b"); + Result result = renameRef.rename(); + assertEquals(Result.RENAMED, result); + assertNull(db.resolve("refs/heads/a")); + assertEquals(rb, db.resolve("refs/heads/a/b")); + assertEquals(3, db.getReflogReader("a/b").getReverseEntries().size()); + assertEquals("Branch: renamed a to a/b", db.getReflogReader("a/b") + .getReverseEntries().get(0).getComment()); + assertEquals("Just a message", db.getReflogReader("a/b") + .getReverseEntries().get(1).getComment()); + assertEquals("Setup", db.getReflogReader("a/b").getReverseEntries() + .get(2).getComment()); + // same thing was logged to HEAD + assertEquals("Branch: renamed a to a/b", db.getReflogReader("HEAD") + .getReverseEntries().get(0).getComment()); + } + + public void testRenameRefNameColission2avoided() throws IOException { + // setup + ObjectId rb = db.resolve("refs/heads/b"); + writeSymref(Constants.HEAD, "refs/heads/prefix/a"); + RefUpdate updateRef = db.updateRef("refs/heads/prefix/a"); + updateRef.setNewObjectId(rb); + updateRef.setRefLogMessage("Setup", false); + updateRef.setForceUpdate(true); + assertEquals(Result.FORCED, updateRef.update()); + ObjectId oldHead = db.resolve(Constants.HEAD); + assertTrue(rb.equals(oldHead)); // assumption for this test + writeReflog(db, rb, rb, "Just a message", + "refs/heads/prefix/a"); + assertTrue("internal check, we have a log", new File(db.getDirectory(), + "logs/refs/heads/prefix/a").exists()); + + // Now this is our test + RefRename renameRef = db.renameRef("refs/heads/prefix/a", + "refs/heads/prefix"); + Result result = renameRef.rename(); + assertEquals(Result.RENAMED, result); + + assertNull(db.resolve("refs/heads/prefix/a")); + assertEquals(rb, db.resolve("refs/heads/prefix")); + assertEquals(3, db.getReflogReader("prefix").getReverseEntries().size()); + assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader( + "prefix").getReverseEntries().get(0).getComment()); + assertEquals("Just a message", db.getReflogReader("prefix") + .getReverseEntries().get(1).getComment()); + assertEquals("Setup", db.getReflogReader("prefix").getReverseEntries() + .get(2).getComment()); + assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader( + "HEAD").getReverseEntries().get(0).getComment()); + } + + private void writeReflog(Repository db, ObjectId oldId, ObjectId newId, + String msg, String refName) throws IOException { + RefDirectory refs = (RefDirectory) db.getRefDatabase(); + RefDirectoryUpdate update = refs.newUpdate(refName, true); + update.setNewObjectId(newId); + refs.log(update, msg, true); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ReflogReaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ReflogReaderTest.java new file mode 100644 index 0000000000..1d268a4740 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ReflogReaderTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2009, Robin Rosenberg + * Copyright (C) 2009, Robin Rosenberg + * 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.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.List; + +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.SampleDataRepositoryTestCase; +import org.eclipse.jgit.storage.file.ReflogReader.Entry; + +public class ReflogReaderTest extends SampleDataRepositoryTestCase { + + static byte[] oneLine = "da85355dfc525c9f6f3927b876f379f46ccf826e 3e7549db262d1e836d9bf0af7e22355468f1717c A O Thor Too 1243028200 +0200\tcommit: Add a toString for debugging to RemoteRefUpdate\n" + .getBytes(); + + static byte[] twoLine = ("0000000000000000000000000000000000000000 c6734895958052a9dbc396cff4459dc1a25029ab A U Thor 1243028201 -0100\tbranch: Created from rr/renamebranchv4\n" + + "c6734895958052a9dbc396cff4459dc1a25029ab 54794942a18a237c57a80719afed44bb78172b10 Same A U Thor 1243028202 +0100\trebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d\n") + .getBytes(); + + static byte[] twoLineWithAppendInProgress = ("0000000000000000000000000000000000000000 c6734895958052a9dbc396cff4459dc1a25029ab A U Thor 1243028201 -0100\tbranch: Created from rr/renamebranchv4\n" + + "c6734895958052a9dbc396cff4459dc1a25029ab 54794942a18a237c57a80719afed44bb78172b10 Same A U Thor 1243028202 +0100\trebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d\n" + + "54794942a18a237c57a80719afed44bb78172b10 ") + .getBytes(); + + static byte[] aLine = "1111111111111111111111111111111111111111 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor 1243028201 -0100\tbranch: change to a\n" + .getBytes(); + + static byte[] masterLine = "2222222222222222222222222222222222222222 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor 1243028201 -0100\tbranch: change to master\n" + .getBytes(); + + static byte[] headLine = "3333333333333333333333333333333333333333 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor 1243028201 -0100\tbranch: change to HEAD\n" + .getBytes(); + + public void testReadOneLine() throws Exception { + setupReflog("logs/refs/heads/master", oneLine); + + ReflogReader reader = new ReflogReader(db, "refs/heads/master"); + Entry e = reader.getLastEntry(); + assertEquals(ObjectId + .fromString("da85355dfc525c9f6f3927b876f379f46ccf826e"), e + .getOldId()); + assertEquals(ObjectId + .fromString("3e7549db262d1e836d9bf0af7e22355468f1717c"), e + .getNewId()); + assertEquals("A O Thor Too", e.getWho().getName()); + assertEquals("authortoo@wri.tr", e.getWho().getEmailAddress()); + assertEquals(120, e.getWho().getTimeZoneOffset()); + assertEquals("2009-05-22T23:36:40", iso(e.getWho())); + assertEquals("commit: Add a toString for debugging to RemoteRefUpdate", + e.getComment()); + } + + private String iso(PersonIdent id) { + final SimpleDateFormat fmt; + fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + fmt.setTimeZone(id.getTimeZone()); + return fmt.format(id.getWhen()); + } + + public void testReadTwoLine() throws Exception { + setupReflog("logs/refs/heads/master", twoLine); + + ReflogReader reader = new ReflogReader(db, "refs/heads/master"); + List reverseEntries = reader.getReverseEntries(); + assertEquals(2, reverseEntries.size()); + Entry e = reverseEntries.get(0); + assertEquals(ObjectId + .fromString("c6734895958052a9dbc396cff4459dc1a25029ab"), e + .getOldId()); + assertEquals(ObjectId + .fromString("54794942a18a237c57a80719afed44bb78172b10"), e + .getNewId()); + assertEquals("Same A U Thor", e.getWho().getName()); + assertEquals("same.author@example.com", e.getWho().getEmailAddress()); + assertEquals(60, e.getWho().getTimeZoneOffset()); + assertEquals("2009-05-22T22:36:42", iso(e.getWho())); + assertEquals( + "rebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d", + e.getComment()); + + e = reverseEntries.get(1); + assertEquals(ObjectId + .fromString("0000000000000000000000000000000000000000"), e + .getOldId()); + assertEquals(ObjectId + .fromString("c6734895958052a9dbc396cff4459dc1a25029ab"), e + .getNewId()); + assertEquals("A U Thor", e.getWho().getName()); + assertEquals("thor@committer.au", e.getWho().getEmailAddress()); + assertEquals(-60, e.getWho().getTimeZoneOffset()); + assertEquals("2009-05-22T20:36:41", iso(e.getWho())); + assertEquals("branch: Created from rr/renamebranchv4", e.getComment()); + } + + public void testReadWhileAppendIsInProgress() throws Exception { + setupReflog("logs/refs/heads/master", twoLineWithAppendInProgress); + ReflogReader reader = new ReflogReader(db, "refs/heads/master"); + List reverseEntries = reader.getReverseEntries(); + assertEquals(2, reverseEntries.size()); + Entry e = reverseEntries.get(0); + assertEquals(ObjectId + .fromString("c6734895958052a9dbc396cff4459dc1a25029ab"), e + .getOldId()); + assertEquals(ObjectId + .fromString("54794942a18a237c57a80719afed44bb78172b10"), e + .getNewId()); + assertEquals("Same A U Thor", e.getWho().getName()); + assertEquals("same.author@example.com", e.getWho().getEmailAddress()); + assertEquals(60, e.getWho().getTimeZoneOffset()); + assertEquals("2009-05-22T22:36:42", iso(e.getWho())); + assertEquals( + "rebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d", + e.getComment()); + // while similar to testReadTwoLine, we can assume that if we get the last entry + // right, everything else is too + } + + + public void testReadRightLog() throws Exception { + setupReflog("logs/refs/heads/a", aLine); + setupReflog("logs/refs/heads/master", masterLine); + setupReflog("logs/HEAD", headLine); + assertEquals("branch: change to master", db.getReflogReader("master") + .getLastEntry().getComment()); + assertEquals("branch: change to a", db.getReflogReader("a") + .getLastEntry().getComment()); + assertEquals("branch: change to HEAD", db.getReflogReader("HEAD") + .getLastEntry().getComment()); + } + + public void testNoLog() throws Exception { + assertEquals(0, db.getReflogReader("master").getReverseEntries().size()); + assertNull(db.getReflogReader("master").getLastEntry()); + } + + private void setupReflog(String logName, byte[] data) + throws FileNotFoundException, IOException { + File logfile = new File(db.getDirectory(), logName); + if (!logfile.getParentFile().mkdirs() + && !logfile.getParentFile().isDirectory()) { + throw new IOException( + "oops, cannot create the directory for the test reflog file" + + logfile); + } + FileOutputStream fileOutputStream = new FileOutputStream(logfile); + try { + fileOutputStream.write(data); + } finally { + fileOutputStream.close(); + } + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java new file mode 100644 index 0000000000..caaeda2b03 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2010, Mathias Kinzler + * + * 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.IOException; + +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Repository; + +/** + * Tests for setting up the working directory when creating a Repository + */ +public class RepositorySetupWorkDirTest extends LocalDiskRepositoryTestCase { + + public void testIsBare_CreateRepositoryFromArbitraryGitDir() + throws Exception { + File gitDir = getFile("workdir"); + assertTrue(new FileRepository(gitDir).isBare()); + } + + public void testNotBare_CreateRepositoryFromDotGitGitDir() throws Exception { + File gitDir = getFile("workdir", Constants.DOT_GIT); + Repository repo = new FileRepository(gitDir); + assertFalse(repo.isBare()); + assertWorkdirPath(repo, "workdir"); + assertGitdirPath(repo, "workdir", Constants.DOT_GIT); + } + + public void testWorkdirIsParentDir_CreateRepositoryFromDotGitGitDir() + throws Exception { + File gitDir = getFile("workdir", Constants.DOT_GIT); + Repository repo = new FileRepository(gitDir); + String workdir = repo.getWorkTree().getName(); + assertEquals(workdir, "workdir"); + } + + public void testNotBare_CreateRepositoryFromWorkDirOnly() throws Exception { + File workdir = getFile("workdir", "repo"); + FileRepository repo = new FileRepositoryBuilder().setWorkTree(workdir).build(); + assertFalse(repo.isBare()); + assertWorkdirPath(repo, "workdir", "repo"); + assertGitdirPath(repo, "workdir", "repo", Constants.DOT_GIT); + } + + public void testWorkdirIsDotGit_CreateRepositoryFromWorkDirOnly() + throws Exception { + File workdir = getFile("workdir", "repo"); + FileRepository repo = new FileRepositoryBuilder().setWorkTree(workdir).build(); + assertGitdirPath(repo, "workdir", "repo", Constants.DOT_GIT); + } + + public void testNotBare_CreateRepositoryFromGitDirOnlyWithWorktreeConfig() + throws Exception { + File gitDir = getFile("workdir", "repoWithConfig"); + File workTree = getFile("workdir", "treeRoot"); + setWorkTree(gitDir, workTree); + FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); + assertFalse(repo.isBare()); + assertWorkdirPath(repo, "workdir", "treeRoot"); + assertGitdirPath(repo, "workdir", "repoWithConfig"); + } + + public void testBare_CreateRepositoryFromGitDirOnlyWithBareConfigTrue() + throws Exception { + File gitDir = getFile("workdir", "repoWithConfig"); + setBare(gitDir, true); + FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); + assertTrue(repo.isBare()); + } + + public void testWorkdirIsParent_CreateRepositoryFromGitDirOnlyWithBareConfigFalse() + throws Exception { + File gitDir = getFile("workdir", "repoWithBareConfigTrue", "child"); + setBare(gitDir, false); + FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); + assertWorkdirPath(repo, "workdir", "repoWithBareConfigTrue"); + } + + public void testNotBare_CreateRepositoryFromGitDirOnlyWithBareConfigFalse() + throws Exception { + File gitDir = getFile("workdir", "repoWithBareConfigFalse", "child"); + setBare(gitDir, false); + FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); + assertFalse(repo.isBare()); + assertWorkdirPath(repo, "workdir", "repoWithBareConfigFalse"); + assertGitdirPath(repo, "workdir", "repoWithBareConfigFalse", "child"); + } + + public void testExceptionThrown_BareRepoGetWorkDir() throws Exception { + File gitDir = getFile("workdir"); + try { + new FileRepository(gitDir).getWorkTree(); + fail("Expected IllegalStateException missing"); + } catch (IllegalStateException e) { + // expected + } + } + + public void testExceptionThrown_BareRepoGetIndex() throws Exception { + File gitDir = getFile("workdir"); + try { + new FileRepository(gitDir).getIndex(); + fail("Expected IllegalStateException missing"); + } catch (IllegalStateException e) { + // expected + } + } + + public void testExceptionThrown_BareRepoGetIndexFile() throws Exception { + File gitDir = getFile("workdir"); + try { + new FileRepository(gitDir).getIndexFile(); + fail("Expected Exception missing"); + } catch (IllegalStateException e) { + // expected + } + } + + private File getFile(String... pathComponents) { + String rootPath = new File(new File("target"), "trash").getPath(); + for (String pathComponent : pathComponents) + rootPath = rootPath + File.separatorChar + pathComponent; + File result = new File(rootPath); + result.mkdir(); + return result; + } + + private void setBare(File gitDir, boolean bare) throws IOException, + ConfigInvalidException { + FileBasedConfig cfg = configFor(gitDir); + cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_BARE, bare); + cfg.save(); + } + + private void setWorkTree(File gitDir, File workTree) throws IOException, + ConfigInvalidException { + String path = workTree.getAbsolutePath(); + FileBasedConfig cfg = configFor(gitDir); + cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_WORKTREE, path); + cfg.save(); + } + + private FileBasedConfig configFor(File gitDir) throws IOException, + ConfigInvalidException { + FileBasedConfig cfg = new FileBasedConfig(new File(gitDir, "config")); + cfg.load(); + return cfg; + } + + private void assertGitdirPath(Repository repo, String... expected) + throws IOException { + File exp = getFile(expected).getCanonicalFile(); + File act = repo.getDirectory().getCanonicalFile(); + assertEquals("Wrong Git Directory", exp, act); + } + + private void assertWorkdirPath(Repository repo, String... expected) + throws IOException { + File exp = getFile(expected).getCanonicalFile(); + File act = repo.getWorkTree().getCanonicalFile(); + assertEquals("Wrong working Directory", exp, act); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0003_Basic.java new file mode 100644 index 0000000000..c9013a6a01 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0003_Basic.java @@ -0,0 +1,769 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2007-2010, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; + +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.Commit; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileTreeEntry; +import org.eclipse.jgit.lib.ObjectDatabase; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectWriter; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.SampleDataRepositoryTestCase; +import org.eclipse.jgit.lib.Tag; +import org.eclipse.jgit.lib.Tree; +import org.eclipse.jgit.lib.TreeEntry; +import org.eclipse.jgit.lib.WriteTree; + +public class T0003_Basic extends SampleDataRepositoryTestCase { + public void test001_Initalize() { + final File gitdir = new File(trash, Constants.DOT_GIT); + final File objects = new File(gitdir, "objects"); + final File objects_pack = new File(objects, "pack"); + final File objects_info = new File(objects, "info"); + final File refs = new File(gitdir, "refs"); + final File refs_heads = new File(refs, "heads"); + final File refs_tags = new File(refs, "tags"); + final File HEAD = new File(gitdir, "HEAD"); + + assertTrue("Exists " + trash, trash.isDirectory()); + assertTrue("Exists " + objects, objects.isDirectory()); + assertTrue("Exists " + objects_pack, objects_pack.isDirectory()); + assertTrue("Exists " + objects_info, objects_info.isDirectory()); + assertEquals(2, objects.listFiles().length); + assertTrue("Exists " + refs, refs.isDirectory()); + assertTrue("Exists " + refs_heads, refs_heads.isDirectory()); + assertTrue("Exists " + refs_tags, refs_tags.isDirectory()); + assertTrue("Exists " + HEAD, HEAD.isFile()); + assertEquals(23, HEAD.length()); + } + + public void test000_openRepoBadArgs() throws IOException { + try { + new FileRepositoryBuilder().build(); + fail("Must pass either GIT_DIR or GIT_WORK_TREE"); + } catch (IllegalArgumentException e) { + assertEquals( + "Either GIT_DIR or GIT_WORK_TREE must be passed to Repository constructor", + e.getMessage()); + } + } + + /** + * Check the default rules for looking up directories and files within a + * repo when the gitDir is given. + * + * @throws IOException + */ + public void test000_openrepo_default_gitDirSet() throws IOException { + File repo1Parent = new File(trash.getParentFile(), "r1"); + Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); + repo1initial.create(); + repo1initial.close(); + + File theDir = new File(repo1Parent, Constants.DOT_GIT); + FileRepository r = new FileRepositoryBuilder().setGitDir(theDir).build(); + assertEqualsPath(theDir, r.getDirectory()); + assertEqualsPath(repo1Parent, r.getWorkTree()); + assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); + assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory()); + } + + /** + * Check that we can pass both a git directory and a work tree + * repo when the gitDir is given. + * + * @throws IOException + */ + public void test000_openrepo_default_gitDirAndWorkTreeSet() throws IOException { + File repo1Parent = new File(trash.getParentFile(), "r1"); + Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); + repo1initial.create(); + repo1initial.close(); + + File theDir = new File(repo1Parent, Constants.DOT_GIT); + FileRepository r = new FileRepositoryBuilder().setGitDir(theDir) + .setWorkTree(repo1Parent.getParentFile()).build(); + assertEqualsPath(theDir, r.getDirectory()); + assertEqualsPath(repo1Parent.getParentFile(), r.getWorkTree()); + assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); + assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory()); + } + + /** + * Check the default rules for looking up directories and files within a + * repo when the workTree is given. + * + * @throws IOException + */ + public void test000_openrepo_default_workDirSet() throws IOException { + File repo1Parent = new File(trash.getParentFile(), "r1"); + Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); + repo1initial.create(); + repo1initial.close(); + + File theDir = new File(repo1Parent, Constants.DOT_GIT); + FileRepository r = new FileRepositoryBuilder().setWorkTree(repo1Parent).build(); + assertEqualsPath(theDir, r.getDirectory()); + assertEqualsPath(repo1Parent, r.getWorkTree()); + assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); + assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory()); + } + + /** + * Check that worktree config has an effect, given absolute path. + * + * @throws IOException + */ + public void test000_openrepo_default_absolute_workdirconfig() + throws IOException { + File repo1Parent = new File(trash.getParentFile(), "r1"); + File workdir = new File(trash.getParentFile(), "rw"); + workdir.mkdir(); + FileRepository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); + repo1initial.create(); + repo1initial.getConfig().setString("core", null, "worktree", + workdir.getAbsolutePath()); + repo1initial.getConfig().save(); + repo1initial.close(); + + File theDir = new File(repo1Parent, Constants.DOT_GIT); + FileRepository r = new FileRepositoryBuilder().setGitDir(theDir).build(); + assertEqualsPath(theDir, r.getDirectory()); + assertEqualsPath(workdir, r.getWorkTree()); + assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); + assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory()); + } + + /** + * Check that worktree config has an effect, given a relative path. + * + * @throws IOException + */ + public void test000_openrepo_default_relative_workdirconfig() + throws IOException { + File repo1Parent = new File(trash.getParentFile(), "r1"); + File workdir = new File(trash.getParentFile(), "rw"); + workdir.mkdir(); + FileRepository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); + repo1initial.create(); + repo1initial.getConfig() + .setString("core", null, "worktree", "../../rw"); + repo1initial.getConfig().save(); + repo1initial.close(); + + File theDir = new File(repo1Parent, Constants.DOT_GIT); + FileRepository r = new FileRepositoryBuilder().setGitDir(theDir).build(); + assertEqualsPath(theDir, r.getDirectory()); + assertEqualsPath(workdir, r.getWorkTree()); + assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); + assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory()); + } + + /** + * Check that the given index file is honored and the alternate object + * directories too + * + * @throws IOException + */ + public void test000_openrepo_alternate_index_file_and_objdirs() + throws IOException { + File repo1Parent = new File(trash.getParentFile(), "r1"); + File indexFile = new File(trash, "idx"); + File objDir = new File(trash, "../obj"); + File altObjDir = db.getObjectDatabase().getDirectory(); + Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); + repo1initial.create(); + repo1initial.close(); + + File theDir = new File(repo1Parent, Constants.DOT_GIT); + FileRepository r = new FileRepositoryBuilder() // + .setGitDir(theDir).setObjectDirectory(objDir) // + .addAlternateObjectDirectory(altObjDir) // + .setIndexFile(indexFile) // + .build(); + assertEqualsPath(theDir, r.getDirectory()); + assertEqualsPath(theDir.getParentFile(), r.getWorkTree()); + assertEqualsPath(indexFile, r.getIndexFile()); + assertEqualsPath(objDir, r.getObjectDatabase().getDirectory()); + assertNotNull(r.mapCommit("6db9c2ebf75590eef973081736730a9ea169a0c4")); + // Must close or the default repo pack files created by this test gets + // locked via the alternate object directories on Windows. + r.close(); + } + + protected void assertEqualsPath(File expected, File actual) + throws IOException { + assertEquals(expected.getCanonicalPath(), actual.getCanonicalPath()); + } + + public void test002_WriteEmptyTree() throws IOException { + // One of our test packs contains the empty tree object. If the pack is + // open when we create it we won't write the object file out as a loose + // object (as it already exists in the pack). + // + final Repository newdb = createBareRepository(); + final Tree t = new Tree(newdb); + t.accept(new WriteTree(trash, newdb), TreeEntry.MODIFIED_ONLY); + assertEquals("4b825dc642cb6eb9a060e54bf8d69288fbee4904", t.getId() + .name()); + final File o = new File(new File(new File(newdb.getDirectory(), + "objects"), "4b"), "825dc642cb6eb9a060e54bf8d69288fbee4904"); + assertTrue("Exists " + o, o.isFile()); + assertTrue("Read-only " + o, !o.canWrite()); + } + + public void test002_WriteEmptyTree2() throws IOException { + // File shouldn't exist as it is in a test pack. + // + final Tree t = new Tree(db); + t.accept(new WriteTree(trash, db), TreeEntry.MODIFIED_ONLY); + assertEquals("4b825dc642cb6eb9a060e54bf8d69288fbee4904", t.getId() + .name()); + final File o = new File(new File( + new File(db.getDirectory(), "objects"), "4b"), + "825dc642cb6eb9a060e54bf8d69288fbee4904"); + assertFalse("Exists " + o, o.isFile()); + } + + public void test003_WriteShouldBeEmptyTree() throws IOException { + final Tree t = new Tree(db); + final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); + t.addFile("should-be-empty").setId(emptyId); + t.accept(new WriteTree(trash, db), TreeEntry.MODIFIED_ONLY); + assertEquals("7bb943559a305bdd6bdee2cef6e5df2413c3d30a", t.getId() + .name()); + + File o; + o = new File(new File(new File(db.getDirectory(), "objects"), "7b"), + "b943559a305bdd6bdee2cef6e5df2413c3d30a"); + assertTrue("Exists " + o, o.isFile()); + assertTrue("Read-only " + o, !o.canWrite()); + + o = new File(new File(new File(db.getDirectory(), "objects"), "e6"), + "9de29bb2d1d6434b8b29ae775ad8c2e48c5391"); + assertTrue("Exists " + o, o.isFile()); + assertTrue("Read-only " + o, !o.canWrite()); + } + + public void test005_ReadSimpleConfig() { + final Config c = db.getConfig(); + assertNotNull(c); + assertEquals("0", c.getString("core", null, "repositoryformatversion")); + assertEquals("0", c.getString("CoRe", null, "REPOSITORYFoRmAtVeRsIoN")); + assertEquals("true", c.getString("core", null, "filemode")); + assertEquals("true", c.getString("cOrE", null, "fIlEModE")); + assertNull(c.getString("notavalue", null, "reallyNotAValue")); + } + + public void test006_ReadUglyConfig() throws IOException, + ConfigInvalidException { + final File cfg = new File(db.getDirectory(), "config"); + final FileBasedConfig c = new FileBasedConfig(cfg); + final FileWriter pw = new FileWriter(cfg); + final String configStr = " [core];comment\n\tfilemode = yes\n" + + "[user]\n" + + " email = A U Thor # Just an example...\n" + + " name = \"A Thor \\\\ \\\"\\t \"\n" + + " defaultCheckInComment = a many line\\n\\\ncomment\\n\\\n" + + " to test\n"; + pw.write(configStr); + pw.close(); + c.load(); + assertEquals("yes", c.getString("core", null, "filemode")); + assertEquals("A U Thor ", c + .getString("user", null, "email")); + assertEquals("A Thor \\ \"\t ", c.getString("user", null, "name")); + assertEquals("a many line\ncomment\n to test", c.getString("user", + null, "defaultCheckInComment")); + c.save(); + final FileReader fr = new FileReader(cfg); + final char[] cbuf = new char[configStr.length()]; + fr.read(cbuf); + fr.close(); + assertEquals(configStr, new String(cbuf)); + } + + public void test007_Open() throws IOException { + final FileRepository db2 = new FileRepository(db.getDirectory()); + assertEquals(db.getDirectory(), db2.getDirectory()); + assertEquals(db.getObjectDatabase().getDirectory(), db2.getObjectDatabase().getDirectory()); + assertNotSame(db.getConfig(), db2.getConfig()); + } + + public void test008_FailOnWrongVersion() throws IOException { + final File cfg = new File(db.getDirectory(), "config"); + final FileWriter pw = new FileWriter(cfg); + final String badvers = "ihopethisisneveraversion"; + final String configStr = "[core]\n" + "\trepositoryFormatVersion=" + + badvers + "\n"; + pw.write(configStr); + pw.close(); + + try { + new FileRepository(db.getDirectory()); + fail("incorrectly opened a bad repository"); + } catch (IOException ioe) { + assertTrue(ioe.getMessage().indexOf("format") > 0); + assertTrue(ioe.getMessage().indexOf(badvers) > 0); + } + } + + public void test009_CreateCommitOldFormat() throws IOException { + final Tree t = new Tree(db); + final FileTreeEntry f = t.addFile("i-am-a-file"); + writeTrashFile(f.getName(), "and this is the data in me\n"); + t.accept(new WriteTree(trash, db), TreeEntry.MODIFIED_ONLY); + assertEquals(ObjectId.fromString("00b1f73724f493096d1ffa0b0f1f1482dbb8c936"), + t.getTreeId()); + + final Commit c = new Commit(db); + c.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); + c.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60)); + c.setMessage("A Commit\n"); + c.setTree(t); + assertEquals(t.getTreeId(), c.getTreeId()); + c.commit(); + final ObjectId cmtid = ObjectId.fromString( + "803aec4aba175e8ab1d666873c984c0308179099"); + assertEquals(cmtid, c.getCommitId()); + + // Verify the commit we just wrote is in the correct format. + ObjectDatabase odb = db.getObjectDatabase(); + assertTrue("is ObjectDirectory", odb instanceof ObjectDirectory); + final XInputStream xis = new XInputStream(new FileInputStream( + ((ObjectDirectory) odb).fileFor(cmtid))); + try { + assertEquals(0x78, xis.readUInt8()); + assertEquals(0x9c, xis.readUInt8()); + assertTrue(0x789c % 31 == 0); + } finally { + xis.close(); + } + + // Verify we can read it. + final Commit c2 = db.mapCommit(cmtid); + assertNotNull(c2); + assertEquals(c.getMessage(), c2.getMessage()); + assertEquals(c.getTreeId(), c2.getTreeId()); + assertEquals(c.getAuthor(), c2.getAuthor()); + assertEquals(c.getCommitter(), c2.getCommitter()); + } + + public void test012_SubtreeExternalSorting() throws IOException { + final ObjectId emptyBlob = new ObjectWriter(db).writeBlob(new byte[0]); + final Tree t = new Tree(db); + final FileTreeEntry e0 = t.addFile("a-"); + final FileTreeEntry e1 = t.addFile("a-b"); + final FileTreeEntry e2 = t.addFile("a/b"); + final FileTreeEntry e3 = t.addFile("a="); + final FileTreeEntry e4 = t.addFile("a=b"); + + e0.setId(emptyBlob); + e1.setId(emptyBlob); + e2.setId(emptyBlob); + e3.setId(emptyBlob); + e4.setId(emptyBlob); + + t.accept(new WriteTree(trash, db), TreeEntry.MODIFIED_ONLY); + assertEquals(ObjectId.fromString("b47a8f0a4190f7572e11212769090523e23eb1ea"), + t.getId()); + } + + public void test020_createBlobTag() throws IOException { + final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); + final Tag t = new Tag(db); + t.setObjId(emptyId); + t.setType("blob"); + t.setTag("test020"); + t.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); + t.setMessage("test020 tagged\n"); + t.tag(); + assertEquals("6759556b09fbb4fd8ae5e315134481cc25d46954", t.getTagId().name()); + + Tag mapTag = db.mapTag("test020"); + assertEquals("blob", mapTag.getType()); + assertEquals("test020 tagged\n", mapTag.getMessage()); + assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag.getAuthor()); + assertEquals("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", mapTag.getObjId().name()); + } + + public void test020b_createBlobPlainTag() throws IOException { + test020_createBlobTag(); + Tag t = new Tag(db); + t.setTag("test020b"); + t.setObjId(ObjectId.fromString("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")); + t.tag(); + + Tag mapTag = db.mapTag("test020b"); + assertEquals("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", mapTag.getObjId().name()); + + // We do not repeat the plain tag test for other object types + } + + public void test021_createTreeTag() throws IOException { + final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); + final Tree almostEmptyTree = new Tree(db); + almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, "empty".getBytes(), false)); + final ObjectId almostEmptyTreeId = new ObjectWriter(db).writeTree(almostEmptyTree); + final Tag t = new Tag(db); + t.setObjId(almostEmptyTreeId); + t.setType("tree"); + t.setTag("test021"); + t.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); + t.setMessage("test021 tagged\n"); + t.tag(); + assertEquals("b0517bc8dbe2096b419d42424cd7030733f4abe5", t.getTagId().name()); + + Tag mapTag = db.mapTag("test021"); + assertEquals("tree", mapTag.getType()); + assertEquals("test021 tagged\n", mapTag.getMessage()); + assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag.getAuthor()); + assertEquals("417c01c8795a35b8e835113a85a5c0c1c77f67fb", mapTag.getObjId().name()); + } + + public void test022_createCommitTag() throws IOException { + final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); + final Tree almostEmptyTree = new Tree(db); + almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, "empty".getBytes(), false)); + final ObjectId almostEmptyTreeId = new ObjectWriter(db).writeTree(almostEmptyTree); + final Commit almostEmptyCommit = new Commit(db); + almostEmptyCommit.setAuthor(new PersonIdent(author, 1154236443000L, -2 * 60)); // not exactly the same + almostEmptyCommit.setCommitter(new PersonIdent(author, 1154236443000L, -2 * 60)); + almostEmptyCommit.setMessage("test022\n"); + almostEmptyCommit.setTreeId(almostEmptyTreeId); + ObjectId almostEmptyCommitId = new ObjectWriter(db).writeCommit(almostEmptyCommit); + final Tag t = new Tag(db); + t.setObjId(almostEmptyCommitId); + t.setType("commit"); + t.setTag("test022"); + t.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); + t.setMessage("test022 tagged\n"); + t.tag(); + assertEquals("0ce2ebdb36076ef0b38adbe077a07d43b43e3807", t.getTagId().name()); + + Tag mapTag = db.mapTag("test022"); + assertEquals("commit", mapTag.getType()); + assertEquals("test022 tagged\n", mapTag.getMessage()); + assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag.getAuthor()); + assertEquals("b5d3b45a96b340441f5abb9080411705c51cc86c", mapTag.getObjId().name()); + } + + public void test023_createCommitNonAnullii() throws IOException { + final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); + final Tree almostEmptyTree = new Tree(db); + almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, "empty".getBytes(), false)); + final ObjectId almostEmptyTreeId = new ObjectWriter(db).writeTree(almostEmptyTree); + Commit commit = new Commit(db); + commit.setTreeId(almostEmptyTreeId); + commit.setAuthor(new PersonIdent("Joe H\u00e4cker","joe@example.com",4294967295000L,60)); + commit.setCommitter(new PersonIdent("Joe Hacker","joe2@example.com",4294967295000L,60)); + commit.setEncoding("UTF-8"); + commit.setMessage("\u00dcbergeeks"); + ObjectId cid = new ObjectWriter(db).writeCommit(commit); + assertEquals("4680908112778718f37e686cbebcc912730b3154", cid.name()); + Commit loadedCommit = db.mapCommit(cid); + assertNotSame(loadedCommit, commit); + assertEquals(commit.getMessage(), loadedCommit.getMessage()); + } + + public void test024_createCommitNonAscii() throws IOException { + final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); + final Tree almostEmptyTree = new Tree(db); + almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, "empty".getBytes(), false)); + final ObjectId almostEmptyTreeId = new ObjectWriter(db).writeTree(almostEmptyTree); + Commit commit = new Commit(db); + commit.setTreeId(almostEmptyTreeId); + commit.setAuthor(new PersonIdent("Joe H\u00e4cker","joe@example.com",4294967295000L,60)); + commit.setCommitter(new PersonIdent("Joe Hacker","joe2@example.com",4294967295000L,60)); + commit.setEncoding("ISO-8859-1"); + commit.setMessage("\u00dcbergeeks"); + ObjectId cid = new ObjectWriter(db).writeCommit(commit); + assertEquals("2979b39d385014b33287054b87f77bcb3ecb5ebf", cid.name()); + } + + public void test025_packedRefs() throws IOException { + test020_createBlobTag(); + test021_createTreeTag(); + test022_createCommitTag(); + + if (!new File(db.getDirectory(),"refs/tags/test020").delete()) throw new Error("Cannot delete unpacked tag"); + if (!new File(db.getDirectory(),"refs/tags/test021").delete()) throw new Error("Cannot delete unpacked tag"); + if (!new File(db.getDirectory(),"refs/tags/test022").delete()) throw new Error("Cannot delete unpacked tag"); + + // We cannot resolve it now, since we have no ref + Tag mapTag20missing = db.mapTag("test020"); + assertNull(mapTag20missing); + + // Construct packed refs file + PrintWriter w = new PrintWriter(new FileWriter(new File(db.getDirectory(), "packed-refs"))); + w.println("# packed-refs with: peeled"); + w.println("6759556b09fbb4fd8ae5e315134481cc25d46954 refs/tags/test020"); + w.println("^e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"); + w.println("b0517bc8dbe2096b419d42424cd7030733f4abe5 refs/tags/test021"); + w.println("^417c01c8795a35b8e835113a85a5c0c1c77f67fb"); + w.println("0ce2ebdb36076ef0b38adbe077a07d43b43e3807 refs/tags/test022"); + w.println("^b5d3b45a96b340441f5abb9080411705c51cc86c"); + w.close(); + ((RefDirectory)db.getRefDatabase()).rescan(); + + Tag mapTag20 = db.mapTag("test020"); + assertNotNull("have tag test020", mapTag20); + assertEquals("blob", mapTag20.getType()); + assertEquals("test020 tagged\n", mapTag20.getMessage()); + assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag20.getAuthor()); + assertEquals("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", mapTag20.getObjId().name()); + + Tag mapTag21 = db.mapTag("test021"); + assertEquals("tree", mapTag21.getType()); + assertEquals("test021 tagged\n", mapTag21.getMessage()); + assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag21.getAuthor()); + assertEquals("417c01c8795a35b8e835113a85a5c0c1c77f67fb", mapTag21.getObjId().name()); + + Tag mapTag22 = db.mapTag("test022"); + assertEquals("commit", mapTag22.getType()); + assertEquals("test022 tagged\n", mapTag22.getMessage()); + assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag22.getAuthor()); + assertEquals("b5d3b45a96b340441f5abb9080411705c51cc86c", mapTag22.getObjId().name()); + } + + public void test025_computeSha1NoStore() throws IOException { + byte[] data = "test025 some data, more than 16 bytes to get good coverage" + .getBytes("ISO-8859-1"); + // TODO: but we do not test legacy header writing + final ObjectId id = new ObjectWriter(db).computeBlobSha1(data.length, + new ByteArrayInputStream(data)); + assertEquals("4f561df5ecf0dfbd53a0dc0f37262fef075d9dde", id.name()); + } + + public void test026_CreateCommitMultipleparents() throws IOException { + final Tree t = new Tree(db); + final FileTreeEntry f = t.addFile("i-am-a-file"); + writeTrashFile(f.getName(), "and this is the data in me\n"); + t.accept(new WriteTree(trash, db), TreeEntry.MODIFIED_ONLY); + assertEquals(ObjectId.fromString("00b1f73724f493096d1ffa0b0f1f1482dbb8c936"), + t.getTreeId()); + + final Commit c1 = new Commit(db); + c1.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); + c1.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60)); + c1.setMessage("A Commit\n"); + c1.setTree(t); + assertEquals(t.getTreeId(), c1.getTreeId()); + c1.commit(); + final ObjectId cmtid1 = ObjectId.fromString( + "803aec4aba175e8ab1d666873c984c0308179099"); + assertEquals(cmtid1, c1.getCommitId()); + + final Commit c2 = new Commit(db); + c2.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); + c2.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60)); + c2.setMessage("A Commit 2\n"); + c2.setTree(t); + assertEquals(t.getTreeId(), c2.getTreeId()); + c2.setParentIds(new ObjectId[] { c1.getCommitId() } ); + c2.commit(); + final ObjectId cmtid2 = ObjectId.fromString( + "95d068687c91c5c044fb8c77c5154d5247901553"); + assertEquals(cmtid2, c2.getCommitId()); + + Commit rm2 = db.mapCommit(cmtid2); + assertNotSame(c2, rm2); // assert the parsed objects is not from the cache + assertEquals(c2.getAuthor(), rm2.getAuthor()); + assertEquals(c2.getCommitId(), rm2.getCommitId()); + assertEquals(c2.getMessage(), rm2.getMessage()); + assertEquals(c2.getTree().getTreeId(), rm2.getTree().getTreeId()); + assertEquals(1, rm2.getParentIds().length); + assertEquals(c1.getCommitId(), rm2.getParentIds()[0]); + + final Commit c3 = new Commit(db); + c3.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); + c3.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60)); + c3.setMessage("A Commit 3\n"); + c3.setTree(t); + assertEquals(t.getTreeId(), c3.getTreeId()); + c3.setParentIds(new ObjectId[] { c1.getCommitId(), c2.getCommitId() }); + c3.commit(); + final ObjectId cmtid3 = ObjectId.fromString( + "ce6e1ce48fbeeb15a83f628dc8dc2debefa066f4"); + assertEquals(cmtid3, c3.getCommitId()); + + Commit rm3 = db.mapCommit(cmtid3); + assertNotSame(c3, rm3); // assert the parsed objects is not from the cache + assertEquals(c3.getAuthor(), rm3.getAuthor()); + assertEquals(c3.getCommitId(), rm3.getCommitId()); + assertEquals(c3.getMessage(), rm3.getMessage()); + assertEquals(c3.getTree().getTreeId(), rm3.getTree().getTreeId()); + assertEquals(2, rm3.getParentIds().length); + assertEquals(c1.getCommitId(), rm3.getParentIds()[0]); + assertEquals(c2.getCommitId(), rm3.getParentIds()[1]); + + final Commit c4 = new Commit(db); + c4.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); + c4.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60)); + c4.setMessage("A Commit 4\n"); + c4.setTree(t); + assertEquals(t.getTreeId(), c3.getTreeId()); + c4.setParentIds(new ObjectId[] { c1.getCommitId(), c2.getCommitId(), c3.getCommitId() }); + c4.commit(); + final ObjectId cmtid4 = ObjectId.fromString( + "d1fca9fe3fef54e5212eb67902c8ed3e79736e27"); + assertEquals(cmtid4, c4.getCommitId()); + + Commit rm4 = db.mapCommit(cmtid4); + assertNotSame(c4, rm3); // assert the parsed objects is not from the cache + assertEquals(c4.getAuthor(), rm4.getAuthor()); + assertEquals(c4.getCommitId(), rm4.getCommitId()); + assertEquals(c4.getMessage(), rm4.getMessage()); + assertEquals(c4.getTree().getTreeId(), rm4.getTree().getTreeId()); + assertEquals(3, rm4.getParentIds().length); + assertEquals(c1.getCommitId(), rm4.getParentIds()[0]); + assertEquals(c2.getCommitId(), rm4.getParentIds()[1]); + assertEquals(c3.getCommitId(), rm4.getParentIds()[2]); + } + + public void test027_UnpackedRefHigherPriorityThanPacked() throws IOException { + PrintWriter writer = new PrintWriter(new FileWriter(new File(db.getDirectory(), "refs/heads/a"))); + String unpackedId = "7f822839a2fe9760f386cbbbcb3f92c5fe81def7"; + writer.print(unpackedId); + writer.print('\n'); + writer.close(); + + ObjectId resolved = db.resolve("refs/heads/a"); + assertEquals(unpackedId, resolved.name()); + } + + public void test028_LockPackedRef() throws IOException { + writeTrashFile(".git/packed-refs", "7f822839a2fe9760f386cbbbcb3f92c5fe81def7 refs/heads/foobar"); + writeTrashFile(".git/HEAD", "ref: refs/heads/foobar\n"); + BUG_WorkAroundRacyGitIssues("packed-refs"); + BUG_WorkAroundRacyGitIssues("HEAD"); + + ObjectId resolve = db.resolve("HEAD"); + assertEquals("7f822839a2fe9760f386cbbbcb3f92c5fe81def7", resolve.name()); + + RefUpdate lockRef = db.updateRef("HEAD"); + ObjectId newId = ObjectId.fromString("07f822839a2fe9760f386cbbbcb3f92c5fe81def"); + lockRef.setNewObjectId(newId); + assertEquals(RefUpdate.Result.FORCED, lockRef.forceUpdate()); + + assertTrue(new File(db.getDirectory(), "refs/heads/foobar").exists()); + assertEquals(newId, db.resolve("refs/heads/foobar")); + + // Again. The ref already exists + RefUpdate lockRef2 = db.updateRef("HEAD"); + ObjectId newId2 = ObjectId.fromString("7f822839a2fe9760f386cbbbcb3f92c5fe81def7"); + lockRef2.setNewObjectId(newId2); + assertEquals(RefUpdate.Result.FORCED, lockRef2.forceUpdate()); + + assertTrue(new File(db.getDirectory(), "refs/heads/foobar").exists()); + assertEquals(newId2, db.resolve("refs/heads/foobar")); + } + + public void test029_mapObject() throws IOException { + assertEquals(new byte[0].getClass(), db.mapObject(ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"), null).getClass()); + assertEquals(Commit.class, db.mapObject(ObjectId.fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"), null).getClass()); + assertEquals(Tree.class, db.mapObject(ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), null).getClass()); + assertEquals(Tag.class, db.mapObject(ObjectId.fromString("17768080a2318cd89bba4c8b87834401e2095703"), null).getClass()); + } + + public void test30_stripWorkDir() { + File relCwd = new File("."); + File absCwd = relCwd.getAbsoluteFile(); + File absBase = new File(new File(absCwd, "repo"), "workdir"); + File relBase = new File(new File(relCwd, "repo"), "workdir"); + assertEquals(absBase.getAbsolutePath(), relBase.getAbsolutePath()); + + File relBaseFile = new File(new File(relBase, "other"), "module.c"); + File absBaseFile = new File(new File(absBase, "other"), "module.c"); + assertEquals("other/module.c", Repository.stripWorkDir(relBase, relBaseFile)); + assertEquals("other/module.c", Repository.stripWorkDir(relBase, absBaseFile)); + assertEquals("other/module.c", Repository.stripWorkDir(absBase, relBaseFile)); + assertEquals("other/module.c", Repository.stripWorkDir(absBase, absBaseFile)); + + File relNonFile = new File(new File(relCwd, "not-repo"), ".gitignore"); + File absNonFile = new File(new File(absCwd, "not-repo"), ".gitignore"); + assertEquals("", Repository.stripWorkDir(relBase, relNonFile)); + assertEquals("", Repository.stripWorkDir(absBase, absNonFile)); + + assertEquals("", Repository.stripWorkDir(db.getWorkTree(), db.getWorkTree())); + + File file = new File(new File(db.getWorkTree(), "subdir"), "File.java"); + assertEquals("subdir/File.java", Repository.stripWorkDir(db.getWorkTree(), file)); + + } + + /** + * Kick the timestamp of a local file. + *

+ * We shouldn't have to make these method calls. The cache is using file + * system timestamps, and on many systems unit tests run faster than the + * modification clock. Dumping the cache after we make an edit behind + * RefDirectory's back allows the tests to pass. + * + * @param name + * the file in the repository to force a time change on. + */ + private void BUG_WorkAroundRacyGitIssues(String name) { + File path = new File(db.getDirectory(), name); + long old = path.lastModified(); + long set = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009 + path.setLastModified(set); + assertTrue("time changed", old != path.lastModified()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java new file mode 100644 index 0000000000..38b532726e --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Imran M Yousuf + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.IOException; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.SampleDataRepositoryTestCase; +import org.eclipse.jgit.util.JGitTestUtil; + +public class T0004_PackReader extends SampleDataRepositoryTestCase { + private static final String PACK_NAME = "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f"; + private static final File TEST_PACK = JGitTestUtil.getTestResourceFile(PACK_NAME + ".pack"); + private static final File TEST_IDX = JGitTestUtil.getTestResourceFile(PACK_NAME + ".idx"); + + public void test003_lookupCompressedObject() throws IOException { + final PackFile pr; + final ObjectId id; + final PackedObjectLoader or; + + id = ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"); + pr = new PackFile(TEST_IDX, TEST_PACK); + or = pr.get(new WindowCursor(null), id); + assertNotNull(or); + assertEquals(Constants.OBJ_TREE, or.getType()); + assertEquals(35, or.getSize()); + assertEquals(7736, or.getObjectOffset()); + pr.close(); + } + + public void test004_lookupDeltifiedObject() throws IOException { + final ObjectId id; + final ObjectLoader or; + + id = ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"); + or = db.openObject(id); + assertNotNull(or); + assertTrue(or instanceof PackedObjectLoader); + assertEquals(Constants.OBJ_BLOB, or.getType()); + assertEquals(18009, or.getSize()); + assertEquals(516, ((PackedObjectLoader) or).getObjectOffset()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java new file mode 100644 index 0000000000..ec512cf710 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2009, Google Inc. + * 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.BufferedReader; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.SampleDataRepositoryTestCase; +import org.eclipse.jgit.util.JGitTestUtil; +import org.eclipse.jgit.util.MutableInteger; + +public class WindowCacheGetTest extends SampleDataRepositoryTestCase { + private List toLoad; + + @Override + public void setUp() throws Exception { + super.setUp(); + + toLoad = new ArrayList(); + final BufferedReader br = new BufferedReader(new InputStreamReader( + new FileInputStream(JGitTestUtil + .getTestResourceFile("all_packed_objects.txt")), + Constants.CHARSET)); + try { + String line; + while ((line = br.readLine()) != null) { + final String[] parts = line.split(" {1,}"); + final TestObject o = new TestObject(); + o.id = ObjectId.fromString(parts[0]); + o.setType(parts[1]); + o.rawSize = Integer.parseInt(parts[2]); + // parts[3] is the size-in-pack + o.offset = Long.parseLong(parts[4]); + toLoad.add(o); + } + } finally { + br.close(); + } + assertEquals(96, toLoad.size()); + } + + public void testCache_Defaults() throws IOException { + final WindowCacheConfig cfg = new WindowCacheConfig(); + WindowCache.reconfigure(cfg); + doCacheTests(); + checkLimits(cfg); + + final WindowCache cache = WindowCache.getInstance(); + assertEquals(6, cache.getOpenFiles()); + assertEquals(17346, cache.getOpenBytes()); + } + + public void testCache_TooFewFiles() throws IOException { + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitOpenFiles(2); + WindowCache.reconfigure(cfg); + doCacheTests(); + checkLimits(cfg); + } + + public void testCache_TooSmallLimit() throws IOException { + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitWindowSize(4096); + cfg.setPackedGitLimit(4096); + WindowCache.reconfigure(cfg); + doCacheTests(); + checkLimits(cfg); + } + + private void checkLimits(final WindowCacheConfig cfg) { + final WindowCache cache = WindowCache.getInstance(); + assertTrue(cache.getOpenFiles() <= cfg.getPackedGitOpenFiles()); + assertTrue(cache.getOpenBytes() <= cfg.getPackedGitLimit()); + assertTrue(0 < cache.getOpenFiles()); + assertTrue(0 < cache.getOpenBytes()); + } + + private void doCacheTests() throws IOException { + for (final TestObject o : toLoad) { + final ObjectLoader or = db.openObject(o.id); + assertNotNull(or); + assertTrue(or instanceof PackedObjectLoader); + assertEquals(o.type, or.getType()); + assertEquals(o.rawSize, or.getRawSize()); + assertEquals(o.offset, ((PackedObjectLoader) or).getObjectOffset()); + } + } + + private class TestObject { + ObjectId id; + + int type; + + int rawSize; + + long offset; + + void setType(final String typeStr) throws CorruptObjectException { + final byte[] typeRaw = Constants.encode(typeStr + " "); + final MutableInteger ptr = new MutableInteger(); + type = Constants.decodeTypeString(id, typeRaw, (byte) ' ', ptr); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheReconfigureTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheReconfigureTest.java new file mode 100644 index 0000000000..e52b19d925 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheReconfigureTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2009, Google Inc. + * 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 org.eclipse.jgit.lib.RepositoryTestCase; + +public class WindowCacheReconfigureTest extends RepositoryTestCase { + public void testConfigureCache_PackedGitLimit_0() { + try { + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitLimit(0); + WindowCache.reconfigure(cfg); + fail("incorrectly permitted PackedGitLimit = 0"); + } catch (IllegalArgumentException e) { + // + } + } + + public void testConfigureCache_PackedGitWindowSize_0() { + try { + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitWindowSize(0); + WindowCache.reconfigure(cfg); + fail("incorrectly permitted PackedGitWindowSize = 0"); + } catch (IllegalArgumentException e) { + assertEquals("Invalid window size", e.getMessage()); + } + } + + public void testConfigureCache_PackedGitWindowSize_512() { + try { + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitWindowSize(512); + WindowCache.reconfigure(cfg); + fail("incorrectly permitted PackedGitWindowSize = 512"); + } catch (IllegalArgumentException e) { + assertEquals("Invalid window size", e.getMessage()); + } + } + + public void testConfigureCache_PackedGitWindowSize_4097() { + try { + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitWindowSize(4097); + WindowCache.reconfigure(cfg); + fail("incorrectly permitted PackedGitWindowSize = 4097"); + } catch (IllegalArgumentException e) { + assertEquals("Window size must be power of 2", e.getMessage()); + } + } + + public void testConfigureCache_PackedGitOpenFiles_0() { + try { + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitOpenFiles(0); + WindowCache.reconfigure(cfg); + fail("incorrectly permitted PackedGitOpenFiles = 0"); + } catch (IllegalArgumentException e) { + assertEquals("Open files must be >= 1", e.getMessage()); + } + } + + public void testConfigureCache_PackedGitWindowSizeAbovePackedGitLimit() { + try { + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitLimit(1024); + cfg.setPackedGitWindowSize(8192); + WindowCache.reconfigure(cfg); + fail("incorrectly permitted PackedGitWindowSize > PackedGitLimit"); + } catch (IllegalArgumentException e) { + assertEquals("Window size must be < limit", e.getMessage()); + } + } + + public void testConfigureCache_Limits1() { + // This test is just to force coverage over some lower bounds for + // the table. We don't want the table to wind up with too small + // of a size. This is highly dependent upon the table allocation + // algorithm actually implemented in WindowCache. + // + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitLimit(6 * 4096 / 5); + cfg.setPackedGitWindowSize(4096); + WindowCache.reconfigure(cfg); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/XInputStream.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/XInputStream.java new file mode 100644 index 0000000000..9978c8e13c --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/XInputStream.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.BufferedInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.jgit.util.NB; + +class XInputStream extends BufferedInputStream { + private final byte[] intbuf = new byte[8]; + + XInputStream(final InputStream s) { + super(s); + } + + synchronized byte[] readFully(final int len) throws IOException { + final byte[] b = new byte[len]; + readFully(b, 0, len); + return b; + } + + synchronized void readFully(final byte[] b, int o, int len) + throws IOException { + int r; + while (len > 0 && (r = read(b, o, len)) > 0) { + o += r; + len -= r; + } + if (len > 0) + throw new EOFException(); + } + + int readUInt8() throws IOException { + final int r = read(); + if (r < 0) + throw new EOFException(); + return r; + } + + long readUInt32() throws IOException { + readFully(intbuf, 0, 4); + return NB.decodeUInt32(intbuf, 0); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/IndexPackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/IndexPackTest.java index e18f741ac4..110804f91a 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/IndexPackTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/IndexPackTest.java @@ -58,10 +58,10 @@ 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.PackFile; import org.eclipse.jgit.lib.RepositoryTestCase; import org.eclipse.jgit.lib.TextProgressMonitor; import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.storage.file.PackFile; import org.eclipse.jgit.util.JGitTestUtil; import org.eclipse.jgit.util.NB; import org.eclipse.jgit.util.TemporaryBuffer; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java index 03726cba09..cb1ce6f382 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java @@ -56,7 +56,6 @@ import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; -import org.eclipse.jgit.lib.ObjectDirectory; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.Ref; @@ -64,6 +63,7 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.storage.file.ObjectDirectory; import org.eclipse.jgit.util.NB; import org.eclipse.jgit.util.TemporaryBuffer; diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index 81441dbdbd..e96ba038d0 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -19,6 +19,7 @@ Export-Package: org.eclipse.jgit;version="0.9.0", org.eclipse.jgit.revplot;version="0.9.0", org.eclipse.jgit.revwalk;version="0.9.0", org.eclipse.jgit.revwalk.filter;version="0.9.0", + org.eclipse.jgit.storage.file;version="0.9.0", org.eclipse.jgit.transport;version="0.9.0", org.eclipse.jgit.treewalk;version="0.9.0", org.eclipse.jgit.treewalk.filter;version="0.9.0", diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java index 9ced005a45..6b1349adef 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java @@ -64,10 +64,10 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.UnmergedPathException; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.LockFile; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.LockFile; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.MutableInteger; import org.eclipse.jgit.util.NB; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java index f5dd7eec72..90929e721c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java @@ -21,6 +21,9 @@ import java.util.List; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.RepositoryCache.FileKey; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.storage.file.FileRepository; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.SystemReader; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java deleted file mode 100644 index 0c5c818993..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.zip.CRC32; -import java.util.zip.DataFormatException; -import java.util.zip.Inflater; - -/** - * A {@link ByteWindow} with an underlying byte array for storage. - */ -final class ByteArrayWindow extends ByteWindow { - private final byte[] array; - - ByteArrayWindow(final PackFile pack, final long o, final byte[] b) { - super(pack, o, b.length); - array = b; - } - - @Override - protected int copy(final int p, final byte[] b, final int o, int n) { - n = Math.min(array.length - p, n); - System.arraycopy(array, p, b, o, n); - return n; - } - - @Override - protected int inflate(final int pos, final byte[] b, int o, - final Inflater inf) throws DataFormatException { - while (!inf.finished()) { - if (inf.needsInput()) { - inf.setInput(array, pos, array.length - pos); - break; - } - o += inf.inflate(b, o, b.length - o); - } - while (!inf.finished() && !inf.needsInput()) - o += inf.inflate(b, o, b.length - o); - return o; - } - - void crc32(CRC32 out, long pos, int cnt) { - out.update(array, (int) (pos - start), cnt); - } - - void write(OutputStream out, long pos, int cnt) throws IOException { - out.write(array, (int) (pos - start), cnt); - } - - void check(Inflater inf, byte[] tmp, long pos, int cnt) - throws DataFormatException { - inf.setInput(array, (int) (pos - start), cnt); - while (inf.inflate(tmp, 0, tmp.length) > 0) - continue; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java deleted file mode 100644 index 794d7428e6..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.nio.ByteBuffer; -import java.util.zip.DataFormatException; -import java.util.zip.Inflater; - -/** - * A window for accessing git packs using a {@link ByteBuffer} for storage. - * - * @see ByteWindow - */ -final class ByteBufferWindow extends ByteWindow { - private final ByteBuffer buffer; - - ByteBufferWindow(final PackFile pack, final long o, final ByteBuffer b) { - super(pack, o, b.capacity()); - buffer = b; - } - - @Override - protected int copy(final int p, final byte[] b, final int o, int n) { - final ByteBuffer s = buffer.slice(); - s.position(p); - n = Math.min(s.remaining(), n); - s.get(b, o, n); - return n; - } - - @Override - protected int inflate(final int pos, final byte[] b, int o, - final Inflater inf) throws DataFormatException { - final byte[] tmp = new byte[512]; - final ByteBuffer s = buffer.slice(); - s.position(pos); - while (s.remaining() > 0 && !inf.finished()) { - if (inf.needsInput()) { - final int n = Math.min(s.remaining(), tmp.length); - s.get(tmp, 0, n); - inf.setInput(tmp, 0, n); - } - o += inf.inflate(b, o, b.length - o); - } - while (!inf.finished() && !inf.needsInput()) - o += inf.inflate(b, o, b.length - o); - return o; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java deleted file mode 100644 index 69d255c781..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.util.zip.DataFormatException; -import java.util.zip.Inflater; - -/** - * A window of data currently stored within a cache. - *

- * All bytes in the window can be assumed to be "immediately available", that is - * they are very likely already in memory, unless the operating system's memory - * is very low and has paged part of this process out to disk. Therefore copying - * bytes from a window is very inexpensive. - *

- */ -abstract class ByteWindow { - protected final PackFile pack; - - protected final long start; - - protected final long end; - - protected ByteWindow(final PackFile p, final long s, final int n) { - pack = p; - start = s; - end = start + n; - } - - final int size() { - return (int) (end - start); - } - - final boolean contains(final PackFile neededFile, final long neededPos) { - return pack == neededFile && start <= neededPos && neededPos < end; - } - - /** - * Copy bytes from the window to a caller supplied buffer. - * - * @param pos - * offset within the file to start copying from. - * @param dstbuf - * destination buffer to copy into. - * @param dstoff - * offset within dstbuf to start copying into. - * @param cnt - * number of bytes to copy. This value may exceed the number of - * bytes remaining in the window starting at offset - * pos. - * @return number of bytes actually copied; this may be less than - * cnt if cnt exceeded the number of - * bytes available. - */ - final int copy(long pos, byte[] dstbuf, int dstoff, int cnt) { - return copy((int) (pos - start), dstbuf, dstoff, cnt); - } - - /** - * Copy bytes from the window to a caller supplied buffer. - * - * @param pos - * offset within the window to start copying from. - * @param dstbuf - * destination buffer to copy into. - * @param dstoff - * offset within dstbuf to start copying into. - * @param cnt - * number of bytes to copy. This value may exceed the number of - * bytes remaining in the window starting at offset - * pos. - * @return number of bytes actually copied; this may be less than - * cnt if cnt exceeded the number of - * bytes available. - */ - protected abstract int copy(int pos, byte[] dstbuf, int dstoff, int cnt); - - /** - * Pump bytes into the supplied inflater as input. - * - * @param pos - * offset within the file to start supplying input from. - * @param dstbuf - * destination buffer the inflater should output decompressed - * data to. - * @param dstoff - * current offset within dstbuf to inflate into. - * @param inf - * the inflater to feed input to. The caller is responsible for - * initializing the inflater as multiple windows may need to - * supply data to the same inflater to completely decompress - * something. - * @return updated dstoff based on the number of bytes - * successfully copied into dstbuf by - * inf. If the inflater is not yet finished then - * another window's data must still be supplied as input to finish - * decompression. - * @throws DataFormatException - * the inflater encountered an invalid chunk of data. Data - * stream corruption is likely. - */ - final int inflate(long pos, byte[] dstbuf, int dstoff, Inflater inf) - throws DataFormatException { - return inflate((int) (pos - start), dstbuf, dstoff, inf); - } - - /** - * Pump bytes into the supplied inflater as input. - * - * @param pos - * offset within the window to start supplying input from. - * @param dstbuf - * destination buffer the inflater should output decompressed - * data to. - * @param dstoff - * current offset within dstbuf to inflate into. - * @param inf - * the inflater to feed input to. The caller is responsible for - * initializing the inflater as multiple windows may need to - * supply data to the same inflater to completely decompress - * something. - * @return updated dstoff based on the number of bytes - * successfully copied into dstbuf by - * inf. If the inflater is not yet finished then - * another window's data must still be supplied as input to finish - * decompression. - * @throws DataFormatException - * the inflater encountered an invalid chunk of data. Data - * stream corruption is likely. - */ - protected abstract int inflate(int pos, byte[] dstbuf, int dstoff, - Inflater inf) throws DataFormatException; -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java deleted file mode 100644 index a32571e606..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2010, Constantine Plotnikov - * Copyright (C) 2010, JetBrains s.r.o. - * 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.lib; - -import java.io.File; -import java.io.IOException; - -/** - * The cached instance of an {@link ObjectDirectory}. - *

- * This class caches the list of loose objects in memory, so the file system is - * not queried with stat calls. - */ -class CachedObjectDirectory extends FileObjectDatabase { - /** - * The set that contains unpacked objects identifiers, it is created when - * the cached instance is created. - */ - private final ObjectIdSubclassMap unpackedObjects = new ObjectIdSubclassMap(); - - private final ObjectDirectory wrapped; - - private AlternateHandle[] alts; - - /** - * The constructor - * - * @param wrapped - * the wrapped database - */ - CachedObjectDirectory(ObjectDirectory wrapped) { - this.wrapped = wrapped; - - File objects = wrapped.getDirectory(); - String[] fanout = objects.list(); - if (fanout == null) - fanout = new String[0]; - for (String d : fanout) { - if (d.length() != 2) - continue; - String[] entries = new File(objects, d).list(); - if (entries == null) - continue; - for (String e : entries) { - if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2) - continue; - try { - unpackedObjects.add(ObjectId.fromString(d + e)); - } catch (IllegalArgumentException notAnObject) { - // ignoring the file that does not represent loose object - } - } - } - } - - @Override - public void close() { - // Don't close anything. - } - - @Override - public ObjectInserter newInserter() { - return wrapped.newInserter(); - } - - @Override - public ObjectDatabase newCachedDatabase() { - return this; - } - - @Override - FileObjectDatabase newCachedFileObjectDatabase() { - return this; - } - - @Override - File getDirectory() { - return wrapped.getDirectory(); - } - - @Override - AlternateHandle[] myAlternates() { - if (alts == null) { - AlternateHandle[] src = wrapped.myAlternates(); - alts = new AlternateHandle[src.length]; - for (int i = 0; i < alts.length; i++) { - FileObjectDatabase s = src[i].db; - alts[i] = new AlternateHandle(s.newCachedFileObjectDatabase()); - } - } - return alts; - } - - @Override - boolean tryAgain1() { - return wrapped.tryAgain1(); - } - - @Override - public boolean hasObject(final AnyObjectId objectId) { - return hasObjectImpl1(objectId); - } - - @Override - boolean hasObject1(AnyObjectId objectId) { - return unpackedObjects.contains(objectId) - || wrapped.hasObject1(objectId); - } - - @Override - ObjectLoader openObject(final WindowCursor curs, - final AnyObjectId objectId) throws IOException { - return openObjectImpl1(curs, objectId); - } - - @Override - ObjectLoader openObject1(WindowCursor curs, AnyObjectId objectId) - throws IOException { - if (unpackedObjects.contains(objectId)) - return wrapped.openObject2(curs, objectId.name(), objectId); - return wrapped.openObject1(curs, objectId); - } - - @Override - boolean hasObject2(String objectId) { - // This method should never be invoked. - throw new UnsupportedOperationException(); - } - - @Override - ObjectLoader openObject2(WindowCursor curs, String objectName, - AnyObjectId objectId) throws IOException { - // This method should never be invoked. - throw new UnsupportedOperationException(); - } - - @Override - void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, - WindowCursor curs) throws IOException { - wrapped.selectObjectRepresentation(packer, otp, curs); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java deleted file mode 100644 index d0e98a2a92..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.CorruptObjectException; - -/** Reads a deltified object which uses an offset to find its base. */ -class DeltaOfsPackedObjectLoader extends DeltaPackedObjectLoader { - private final long deltaBase; - - DeltaOfsPackedObjectLoader(final PackFile pr, final long objectOffset, - final int headerSz, final int deltaSz, final long base) { - super(pr, objectOffset, headerSz, deltaSz); - deltaBase = base; - } - - protected PackedObjectLoader getBaseLoader(final WindowCursor curs) - throws IOException { - return pack.resolveBase(curs, deltaBase); - } - - @Override - public int getRawType() { - return Constants.OBJ_OFS_DELTA; - } - - @Override - public ObjectId getDeltaBase() throws IOException { - final ObjectId id = pack.findObjectForOffset(deltaBase); - if (id == null) - throw new CorruptObjectException( - JGitText.get().offsetWrittenDeltaBaseForObjectNotFoundInAPack); - return id; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java deleted file mode 100644 index bbc1c62a8a..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.text.MessageFormat; -import java.util.zip.DataFormatException; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.CorruptObjectException; - -/** Reader for a deltified object stored in a pack file. */ -abstract class DeltaPackedObjectLoader extends PackedObjectLoader { - private static final int OBJ_COMMIT = Constants.OBJ_COMMIT; - - private final int deltaSize; - - DeltaPackedObjectLoader(final PackFile pr, final long objectOffset, - final int headerSize, final int deltaSz) { - super(pr, objectOffset, headerSize); - objectType = -1; - deltaSize = deltaSz; - } - - @Override - public void materialize(final WindowCursor curs) throws IOException { - if (cachedBytes != null) { - return; - } - - if (objectType != OBJ_COMMIT) { - UnpackedObjectCache.Entry cache = pack.readCache(objectOffset); - if (cache != null) { - curs.release(); - objectType = cache.type; - objectSize = cache.data.length; - cachedBytes = cache.data; - return; - } - } - - try { - final PackedObjectLoader baseLoader = getBaseLoader(curs); - baseLoader.materialize(curs); - cachedBytes = BinaryDelta.apply(baseLoader.getCachedBytes(), pack - .decompress(objectOffset + headerSize, deltaSize, curs)); - curs.release(); - objectType = baseLoader.getType(); - objectSize = cachedBytes.length; - if (objectType != OBJ_COMMIT) - pack.saveCache(objectOffset, cachedBytes, objectType); - } catch (DataFormatException dfe) { - final CorruptObjectException coe; - coe = new CorruptObjectException(MessageFormat.format(JGitText.get().objectAtHasBadZlibStream, - objectOffset, pack.getPackFile())); - coe.initCause(dfe); - throw coe; - } - } - - @Override - public long getRawSize() { - return deltaSize; - } - - /** - * @param curs - * temporary thread storage during data access. - * @return the object loader for the base object - * @throws IOException - */ - protected abstract PackedObjectLoader getBaseLoader(WindowCursor curs) - throws IOException; -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java deleted file mode 100644 index 9f7589c291..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; - -import org.eclipse.jgit.errors.MissingObjectException; - -/** Reads a deltified object which uses an {@link ObjectId} to find its base. */ -class DeltaRefPackedObjectLoader extends DeltaPackedObjectLoader { - private final ObjectId deltaBase; - - DeltaRefPackedObjectLoader(final PackFile pr, final long objectOffset, - final int headerSz, final int deltaSz, final ObjectId base) { - super(pr, objectOffset, headerSz, deltaSz); - deltaBase = base; - } - - protected PackedObjectLoader getBaseLoader(final WindowCursor curs) - throws IOException { - final PackedObjectLoader or = pack.get(curs, deltaBase); - if (or == null) - throw new MissingObjectException(deltaBase, "delta base"); - return or; - } - - @Override - public int getRawType() { - return Constants.OBJ_REF_DELTA; - } - - @Override - public ObjectId getDeltaBase() throws IOException { - return deltaBase; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java deleted file mode 100644 index eb00917917..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2009, Constantine Plotnikov - * Copyright (C) 2007, Dave Watson - * Copyright (C) 2009, Google Inc. - * Copyright (C) 2009, JetBrains s.r.o. - * Copyright (C) 2008-2009, Robin Rosenberg - * Copyright (C) 2008, Shawn O. Pearce - * Copyright (C) 2008, Thad Hughes - * 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.lib; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.text.MessageFormat; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.util.IO; -import org.eclipse.jgit.util.RawParseUtils; - -/** - * The configuration file that is stored in the file of the file system. - */ -public class FileBasedConfig extends Config { - private final File configFile; - private volatile long lastModified; - - /** - * Create a configuration with no default fallback. - * - * @param cfgLocation - * the location of the configuration file on the file system - */ - public FileBasedConfig(File cfgLocation) { - this(null, cfgLocation); - } - - /** - * The constructor - * - * @param base - * the base configuration file - * @param cfgLocation - * the location of the configuration file on the file system - */ - public FileBasedConfig(Config base, File cfgLocation) { - super(base); - configFile = cfgLocation; - } - - /** @return location of the configuration file on disk */ - public final File getFile() { - return configFile; - } - - /** - * Load the configuration as a Git text style configuration file. - *

- * If the file does not exist, this configuration is cleared, and thus - * behaves the same as though the file exists, but is empty. - * - * @throws IOException - * the file could not be read (but does exist). - * @throws ConfigInvalidException - * the file is not a properly formatted configuration file. - */ - public void load() throws IOException, ConfigInvalidException { - lastModified = getFile().lastModified(); - try { - fromText(RawParseUtils.decode(IO.readFully(getFile()))); - } catch (FileNotFoundException noFile) { - clear(); - } catch (IOException e) { - final IOException e2 = new IOException(MessageFormat.format(JGitText.get().cannotReadFile, getFile())); - e2.initCause(e); - throw e2; - } catch (ConfigInvalidException e) { - throw new ConfigInvalidException(MessageFormat.format(JGitText.get().cannotReadFile, getFile()), e); - } - } - - /** - * Save the configuration as a Git text style configuration file. - *

- * Warning: Although this method uses the traditional Git file - * locking approach to protect against concurrent writes of the - * configuration file, it does not ensure that the file has not been - * modified since the last read, which means updates performed by other - * objects accessing the same backing file may be lost. - * - * @throws IOException - * the file could not be written. - */ - public void save() throws IOException { - final byte[] out = Constants.encode(toText()); - final LockFile lf = new LockFile(getFile()); - if (!lf.lock()) - throw new IOException(MessageFormat.format(JGitText.get().cannotLockFile, getFile())); - try { - lf.setNeedStatInformation(true); - lf.write(out); - if (!lf.commit()) - throw new IOException(MessageFormat.format(JGitText.get().cannotCommitWriteTo, getFile())); - } finally { - lf.unlock(); - } - lastModified = lf.getCommitLastModified(); - } - - @Override - public String toString() { - return getClass().getSimpleName() + "[" + getFile().getPath() + "]"; - } - - /** - * @return returns true if the currently loaded configuration file is older - * than the file on disk - */ - public boolean isOutdated() { - return getFile().lastModified() != lastModified; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java deleted file mode 100644 index 5328b327ec..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2010, Google Inc. - * 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.lib; - -import java.io.File; -import java.io.IOException; - -abstract class FileObjectDatabase extends ObjectDatabase { - @Override - public ObjectReader newReader() { - return new WindowCursor(this); - } - - /** - * Does the requested object exist in this database? - *

- * Alternates (if present) are searched automatically. - * - * @param objectId - * identity of the object to test for existence of. - * @return true if the specified object is stored in this database, or any - * of the alternate databases. - */ - public boolean hasObject(final AnyObjectId objectId) { - return hasObjectImpl1(objectId) || hasObjectImpl2(objectId.name()); - } - - final boolean hasObjectImpl1(final AnyObjectId objectId) { - if (hasObject1(objectId)) - return true; - - for (final AlternateHandle alt : myAlternates()) { - if (alt.db.hasObjectImpl1(objectId)) - return true; - } - - return tryAgain1() && hasObject1(objectId); - } - - final boolean hasObjectImpl2(final String objectId) { - if (hasObject2(objectId)) - return true; - - for (final AlternateHandle alt : myAlternates()) { - if (alt.db.hasObjectImpl2(objectId)) - return true; - } - - return false; - } - - /** - * Open an object from this database. - *

- * Alternates (if present) are searched automatically. - * - * @param curs - * temporary working space associated with the calling thread. - * @param objectId - * identity of the object to open. - * @return a {@link ObjectLoader} for accessing the data of the named - * object, or null if the object does not exist. - * @throws IOException - */ - ObjectLoader openObject(final WindowCursor curs, final AnyObjectId objectId) - throws IOException { - ObjectLoader ldr; - - ldr = openObjectImpl1(curs, objectId); - if (ldr != null) - return ldr; - - ldr = openObjectImpl2(curs, objectId.name(), objectId); - if (ldr != null) - return ldr; - - return null; - } - - final ObjectLoader openObjectImpl1(final WindowCursor curs, - final AnyObjectId objectId) throws IOException { - ObjectLoader ldr; - - ldr = openObject1(curs, objectId); - if (ldr != null) - return ldr; - - for (final AlternateHandle alt : myAlternates()) { - ldr = alt.db.openObjectImpl1(curs, objectId); - if (ldr != null) - return ldr; - } - - if (tryAgain1()) { - ldr = openObject1(curs, objectId); - if (ldr != null) - return ldr; - } - - return null; - } - - final ObjectLoader openObjectImpl2(final WindowCursor curs, - final String objectName, final AnyObjectId objectId) - throws IOException { - ObjectLoader ldr; - - ldr = openObject2(curs, objectName, objectId); - if (ldr != null) - return ldr; - - for (final AlternateHandle alt : myAlternates()) { - ldr = alt.db.openObjectImpl2(curs, objectName, objectId); - if (ldr != null) - return ldr; - } - - return null; - } - - abstract void selectObjectRepresentation(PackWriter packer, - ObjectToPack otp, WindowCursor curs) throws IOException; - - abstract File getDirectory(); - - abstract AlternateHandle[] myAlternates(); - - abstract boolean tryAgain1(); - - abstract boolean hasObject1(AnyObjectId objectId); - - abstract boolean hasObject2(String objectId); - - abstract ObjectLoader openObject1(WindowCursor curs, AnyObjectId objectId) - throws IOException; - - abstract ObjectLoader openObject2(WindowCursor curs, String objectName, - AnyObjectId objectId) throws IOException; - - abstract FileObjectDatabase newCachedFileObjectDatabase(); - - static class AlternateHandle { - final FileObjectDatabase db; - - AlternateHandle(FileObjectDatabase db) { - this.db = db; - } - - void close() { - db.close(); - } - } - - static class AlternateRepository extends AlternateHandle { - final FileRepository repository; - - AlternateRepository(FileRepository r) { - super(r.getObjectDatabase()); - repository = r; - } - - void close() { - repository.close(); - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java deleted file mode 100644 index f45ffdf149..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright (C) 2007, Dave Watson - * Copyright (C) 2008-2010, Google Inc. - * Copyright (C) 2006-2010, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.File; -import java.io.IOException; -import java.text.MessageFormat; -import java.util.HashSet; -import java.util.Set; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.lib.FileObjectDatabase.AlternateHandle; -import org.eclipse.jgit.lib.FileObjectDatabase.AlternateRepository; -import org.eclipse.jgit.util.SystemReader; - -/** - * Represents a Git repository. A repository holds all objects and refs used for - * managing source code (could by any type of file, but source code is what - * SCM's are typically used for). - * - * In Git terms all data is stored in GIT_DIR, typically a directory called - * .git. A work tree is maintained unless the repository is a bare repository. - * Typically the .git directory is located at the root of the work dir. - * - *

    - *
  • GIT_DIR - *
      - *
    • objects/ - objects
    • - *
    • refs/ - tags and heads
    • - *
    • config - configuration
    • - *
    • info/ - more configurations
    • - *
    - *
  • - *
- *

- * This class is thread-safe. - *

- * This implementation only handles a subtly undocumented subset of git features. - * - */ -public class FileRepository extends Repository { - private final FileBasedConfig userConfig; - - private final FileBasedConfig repoConfig; - - private final RefDatabase refs; - - private final ObjectDirectory objectDatabase; - - /** - * Construct a representation of a Git repository. - *

- * The work tree, object directory, alternate object directories and index - * file locations are deduced from the given git directory and the default - * rules by running {@link FileRepositoryBuilder}. This constructor is the - * same as saying: - * - *

-	 * new FileRepositoryBuilder().setGitDir(gitDir).build()
-	 * 
- * - * @param gitDir - * GIT_DIR (the location of the repository metadata). - * @throws IOException - * the repository appears to already exist but cannot be - * accessed. - * @see FileRepositoryBuilder - */ - public FileRepository(final File gitDir) throws IOException { - this(new FileRepositoryBuilder().setGitDir(gitDir).setup()); - } - - FileRepository(final BaseRepositoryBuilder options) throws IOException { - super(options); - - userConfig = SystemReader.getInstance().openUserConfig(getFS()); - repoConfig = new FileBasedConfig(userConfig, getFS().resolve(getDirectory(), "config")); - - loadUserConfig(); - loadRepoConfig(); - - refs = new RefDirectory(this); - objectDatabase = new ObjectDirectory(repoConfig, // - options.getObjectDirectory(), // - options.getAlternateObjectDirectories(), // - getFS()); - - if (objectDatabase.exists()) { - final String repositoryFormatVersion = getConfig().getString( - ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION); - if (!"0".equals(repositoryFormatVersion)) { - throw new IOException(MessageFormat.format( - JGitText.get().unknownRepositoryFormat2, - repositoryFormatVersion)); - } - } - } - - private void loadUserConfig() throws IOException { - try { - userConfig.load(); - } catch (ConfigInvalidException e1) { - IOException e2 = new IOException(MessageFormat.format(JGitText - .get().userConfigFileInvalid, userConfig.getFile() - .getAbsolutePath(), e1)); - e2.initCause(e1); - throw e2; - } - } - - private void loadRepoConfig() throws IOException { - try { - repoConfig.load(); - } catch (ConfigInvalidException e1) { - IOException e2 = new IOException(JGitText.get().unknownRepositoryFormat); - e2.initCause(e1); - throw e2; - } - } - - /** - * Create a new Git repository initializing the necessary files and - * directories. - * - * @param bare - * if true, a bare repository is created. - * - * @throws IOException - * in case of IO problem - */ - public void create(boolean bare) throws IOException { - final FileBasedConfig cfg = getConfig(); - if (cfg.getFile().exists()) { - throw new IllegalStateException(MessageFormat.format( - JGitText.get().repositoryAlreadyExists, getDirectory())); - } - getDirectory().mkdirs(); - refs.create(); - objectDatabase.create(); - - new File(getDirectory(), "branches").mkdir(); - - RefUpdate head = updateRef(Constants.HEAD); - head.disableRefLog(); - head.link(Constants.R_HEADS + Constants.MASTER); - - cfg.setInt(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0); - cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_FILEMODE, true); - if (bare) - cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_BARE, true); - cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, !bare); - cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_AUTOCRLF, false); - cfg.save(); - } - - /** - * @return the directory containing the objects owned by this repository. - */ - public File getObjectsDirectory() { - return objectDatabase.getDirectory(); - } - - /** - * @return the object database which stores this repository's data. - */ - public ObjectDirectory getObjectDatabase() { - return objectDatabase; - } - - /** @return the reference database which stores the reference namespace. */ - public RefDatabase getRefDatabase() { - return refs; - } - - /** - * @return the configuration of this repository - */ - public FileBasedConfig getConfig() { - if (userConfig.isOutdated()) { - try { - loadUserConfig(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - if (repoConfig.isOutdated()) { - try { - loadRepoConfig(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - return repoConfig; - } - - /** - * Objects known to exist but not expressed by {@link #getAllRefs()}. - *

- * When a repository borrows objects from another repository, it can - * advertise that it safely has that other repository's references, without - * exposing any other details about the other repository. This may help - * a client trying to push changes avoid pushing more than it needs to. - * - * @return unmodifiable collection of other known objects. - */ - public Set getAdditionalHaves() { - HashSet r = new HashSet(); - for (AlternateHandle d : objectDatabase. myAlternates()) { - if (d instanceof AlternateRepository) { - Repository repo; - - repo = ((AlternateRepository) d).repository; - for (Ref ref : repo.getAllRefs().values()) - r.add(ref.getObjectId()); - r.addAll(repo.getAdditionalHaves()); - } - } - return r; - } - - /** - * Add a single existing pack to the list of available pack files. - * - * @param pack - * path of the pack file to open. - * @param idx - * path of the corresponding index file. - * @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 { - objectDatabase.openPack(pack, idx); - } - - /** - * Force a scan for changed refs. - * - * @throws IOException - */ - public void scanForRepoChanges() throws IOException { - getAllRefs(); // This will look for changes to refs - if (!isBare()) - getIndex(); // This will detect changes in the index - } - - /** - * @param refName - * @return a {@link ReflogReader} for the supplied refname, or null if the - * named ref does not exist. - * @throws IOException the ref could not be accessed. - */ - public ReflogReader getReflogReader(String refName) throws IOException { - Ref ref = getRef(refName); - if (ref != null) - return new ReflogReader(this, ref.getName()); - return null; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepositoryBuilder.java deleted file mode 100644 index c0220d979e..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepositoryBuilder.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2010, Google Inc. - * 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.lib; - -import java.io.File; -import java.io.IOException; - -/** - * Constructs a {@link FileRepository}. - *

- * Applications must set one of {@link #setGitDir(File)} or - * {@link #setWorkTree(File)}, or use {@link #readEnvironment()} or - * {@link #findGitDir()} in order to configure the minimum property set - * necessary to open a repository. - *

- * Single repository applications trying to be compatible with other Git - * implementations are encouraged to use a model such as: - * - *

- * new FileRepositoryBuilder() //
- * 		.setGitDir(gitDirArgument) // --git-dir if supplied, no-op if null
- * 		.readEnviroment() // scan environment GIT_* variables
- * 		.findGitDir() // scan up the file system tree
- * 		.build()
- * 
- */ -public class FileRepositoryBuilder extends - BaseRepositoryBuilder { - /** - * Create a repository matching the configuration in this builder. - *

- * If an option was not set, the build method will try to default the option - * based on other options. If insufficient information is available, an - * exception is thrown to the caller. - * - * @return a repository matching this configuration. - * @throws IllegalArgumentException - * insufficient parameters were set. - * @throws IOException - * the repository could not be accessed to configure the rest of - * the builder's parameters. - */ - @Override - public FileRepository build() throws IOException { - return new FileRepository(setup()); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectRepresentation.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectRepresentation.java deleted file mode 100644 index f8535bdf2d..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectRepresentation.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2010, Google Inc. - * 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.lib; - -import java.io.IOException; - -class LocalObjectRepresentation extends StoredObjectRepresentation { - final PackedObjectLoader ldr; - - LocalObjectRepresentation(PackedObjectLoader ldr) { - this.ldr = ldr; - } - - @Override - public int getFormat() { - if (ldr instanceof DeltaPackedObjectLoader) - return PACK_DELTA; - if (ldr instanceof WholePackedObjectLoader) - return PACK_WHOLE; - return FORMAT_OTHER; - } - - @Override - public int getWeight() { - long sz = ldr.getRawSize(); - if (Integer.MAX_VALUE < sz) - return WEIGHT_UNKNOWN; - return (int) sz; - } - - @Override - public ObjectId getDeltaBase() { - try { - return ldr.getDeltaBase(); - } catch (IOException e) { - return null; - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java deleted file mode 100644 index 8db58707e8..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2010, Google Inc. - * 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.lib; - -import org.eclipse.jgit.revwalk.RevObject; - -/** {@link ObjectToPack} for {@link ObjectDirectory}. */ -class LocalObjectToPack extends ObjectToPack { - /** Pack to reuse compressed data from, otherwise null. */ - PackFile copyFromPack; - - /** Offset of the object's header in {@link #copyFromPack}. */ - long copyOffset; - - LocalObjectToPack(RevObject obj) { - super(obj); - } - - @Override - public void select(StoredObjectRepresentation ref) { - LocalObjectRepresentation ptr = (LocalObjectRepresentation)ref; - this.copyFromPack = ptr.ldr.pack; - this.copyOffset = ptr.ldr.objectOffset; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java deleted file mode 100644 index 13f158dedf..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java +++ /dev/null @@ -1,448 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.channels.FileLock; -import java.nio.channels.OverlappingFileLockException; -import java.text.MessageFormat; - -import org.eclipse.jgit.JGitText; - -/** - * Git style file locking and replacement. - *

- * To modify a ref file Git tries to use an atomic update approach: we write the - * new data into a brand new file, then rename it in place over the old name. - * This way we can just delete the temporary file if anything goes wrong, and - * nothing has been damaged. To coordinate access from multiple processes at - * once Git tries to atomically create the new temporary file under a well-known - * name. - */ -public class LockFile { - static final String SUFFIX = ".lock"; //$NON-NLS-1$ - - /** Filter to skip over active lock files when listing a directory. */ - static final FilenameFilter FILTER = new FilenameFilter() { - public boolean accept(File dir, String name) { - return !name.endsWith(SUFFIX); - } - }; - - private final File ref; - - private final File lck; - - private FileLock fLck; - - private boolean haveLck; - - private FileOutputStream os; - - private boolean needStatInformation; - - private long commitLastModified; - - /** - * Create a new lock for any file. - * - * @param f - * the file that will be locked. - */ - public LockFile(final File f) { - ref = f; - lck = new File(ref.getParentFile(), ref.getName() + SUFFIX); - } - - /** - * Try to establish the lock. - * - * @return true if the lock is now held by the caller; false if it is held - * by someone else. - * @throws IOException - * the temporary output file could not be created. The caller - * does not hold the lock. - */ - public boolean lock() throws IOException { - lck.getParentFile().mkdirs(); - if (lck.createNewFile()) { - haveLck = true; - try { - os = new FileOutputStream(lck); - try { - fLck = os.getChannel().tryLock(); - if (fLck == null) - throw new OverlappingFileLockException(); - } catch (OverlappingFileLockException ofle) { - // We cannot use unlock() here as this file is not - // held by us, but we thought we created it. We must - // not delete it, as it belongs to some other process. - // - haveLck = false; - try { - os.close(); - } catch (IOException ioe) { - // Fail by returning haveLck = false. - } - os = null; - } - } catch (IOException ioe) { - unlock(); - throw ioe; - } - } - return haveLck; - } - - /** - * Try to establish the lock for appending. - * - * @return true if the lock is now held by the caller; false if it is held - * by someone else. - * @throws IOException - * the temporary output file could not be created. The caller - * does not hold the lock. - */ - public boolean lockForAppend() throws IOException { - if (!lock()) - return false; - copyCurrentContent(); - return true; - } - - /** - * Copy the current file content into the temporary file. - *

- * This method saves the current file content by inserting it into the - * temporary file, so that the caller can safely append rather than replace - * the primary file. - *

- * This method does nothing if the current file does not exist, or exists - * but is empty. - * - * @throws IOException - * the temporary file could not be written, or a read error - * occurred while reading from the current file. The lock is - * released before throwing the underlying IO exception to the - * caller. - * @throws RuntimeException - * the temporary file could not be written. The lock is released - * before throwing the underlying exception to the caller. - */ - public void copyCurrentContent() throws IOException { - requireLock(); - try { - final FileInputStream fis = new FileInputStream(ref); - try { - final byte[] buf = new byte[2048]; - int r; - while ((r = fis.read(buf)) >= 0) - os.write(buf, 0, r); - } finally { - fis.close(); - } - } catch (FileNotFoundException fnfe) { - // Don't worry about a file that doesn't exist yet, it - // conceptually has no current content to copy. - // - } catch (IOException ioe) { - unlock(); - throw ioe; - } catch (RuntimeException ioe) { - unlock(); - throw ioe; - } catch (Error ioe) { - unlock(); - throw ioe; - } - } - - /** - * Write an ObjectId and LF to the temporary file. - * - * @param id - * the id to store in the file. The id will be written in hex, - * followed by a sole LF. - * @throws IOException - * the temporary file could not be written. The lock is released - * before throwing the underlying IO exception to the caller. - * @throws RuntimeException - * the temporary file could not be written. The lock is released - * before throwing the underlying exception to the caller. - */ - public void write(final ObjectId id) throws IOException { - requireLock(); - try { - final BufferedOutputStream b; - b = new BufferedOutputStream(os, Constants.OBJECT_ID_STRING_LENGTH + 1); - id.copyTo(b); - b.write('\n'); - b.flush(); - fLck.release(); - b.close(); - os = null; - } catch (IOException ioe) { - unlock(); - throw ioe; - } catch (RuntimeException ioe) { - unlock(); - throw ioe; - } catch (Error ioe) { - unlock(); - throw ioe; - } - } - - /** - * Write arbitrary data to the temporary file. - * - * @param content - * the bytes to store in the temporary file. No additional bytes - * are added, so if the file must end with an LF it must appear - * at the end of the byte array. - * @throws IOException - * the temporary file could not be written. The lock is released - * before throwing the underlying IO exception to the caller. - * @throws RuntimeException - * the temporary file could not be written. The lock is released - * before throwing the underlying exception to the caller. - */ - public void write(final byte[] content) throws IOException { - requireLock(); - try { - os.write(content); - os.flush(); - fLck.release(); - os.close(); - os = null; - } catch (IOException ioe) { - unlock(); - throw ioe; - } catch (RuntimeException ioe) { - unlock(); - throw ioe; - } catch (Error ioe) { - unlock(); - throw ioe; - } - } - - /** - * Obtain the direct output stream for this lock. - *

- * The stream may only be accessed once, and only after {@link #lock()} has - * been successfully invoked and returned true. Callers must close the - * stream prior to calling {@link #commit()} to commit the change. - * - * @return a stream to write to the new file. The stream is unbuffered. - */ - public OutputStream getOutputStream() { - requireLock(); - return new OutputStream() { - @Override - public void write(final byte[] b, final int o, final int n) - throws IOException { - os.write(b, o, n); - } - - @Override - public void write(final byte[] b) throws IOException { - os.write(b); - } - - @Override - public void write(final int b) throws IOException { - os.write(b); - } - - @Override - public void flush() throws IOException { - os.flush(); - } - - @Override - public void close() throws IOException { - try { - os.flush(); - fLck.release(); - os.close(); - os = null; - } catch (IOException ioe) { - unlock(); - throw ioe; - } catch (RuntimeException ioe) { - unlock(); - throw ioe; - } catch (Error ioe) { - unlock(); - throw ioe; - } - } - }; - } - - private void requireLock() { - if (os == null) { - unlock(); - throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotHeld, ref)); - } - } - - /** - * Request that {@link #commit()} remember modification time. - * - * @param on - * true if the commit method must remember the modification time. - */ - public void setNeedStatInformation(final boolean on) { - needStatInformation = on; - } - - /** - * Wait until the lock file information differs from the old file. - *

- * This method tests both the length and the last modification date. If both - * are the same, this method sleeps until it can force the new lock file's - * modification date to be later than the target file. - * - * @throws InterruptedException - * the thread was interrupted before the last modified date of - * the lock file was different from the last modified date of - * the target file. - */ - public void waitForStatChange() throws InterruptedException { - if (ref.length() == lck.length()) { - long otime = ref.lastModified(); - long ntime = lck.lastModified(); - while (otime == ntime) { - Thread.sleep(25 /* milliseconds */); - lck.setLastModified(System.currentTimeMillis()); - ntime = lck.lastModified(); - } - } - } - - /** - * Commit this change and release the lock. - *

- * If this method fails (returns false) the lock is still released. - * - * @return true if the commit was successful and the file contains the new - * data; false if the commit failed and the file remains with the - * old data. - * @throws IllegalStateException - * the lock is not held. - */ - public boolean commit() { - if (os != null) { - unlock(); - throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotClosed, ref)); - } - - saveStatInformation(); - if (lck.renameTo(ref)) - return true; - if (!ref.exists() || ref.delete()) - if (lck.renameTo(ref)) - return true; - unlock(); - return false; - } - - private void saveStatInformation() { - if (needStatInformation) - commitLastModified = lck.lastModified(); - } - - /** - * Get the modification time of the output file when it was committed. - * - * @return modification time of the lock file right before we committed it. - */ - public long getCommitLastModified() { - return commitLastModified; - } - - /** - * Unlock this file and abort this change. - *

- * The temporary file (if created) is deleted before returning. - */ - public void unlock() { - if (os != null) { - if (fLck != null) { - try { - fLck.release(); - } catch (IOException ioe) { - // Huh? - } - fLck = null; - } - try { - os.close(); - } catch (IOException ioe) { - // Ignore this - } - os = null; - } - - if (haveLck) { - haveLck = false; - lck.delete(); - } - } - - @Override - public String toString() { - return "LockFile[" + lck + ", haveLck=" + haveLck + "]"; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java deleted file mode 100644 index bf8f323341..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java +++ /dev/null @@ -1,621 +0,0 @@ -/* - * Copyright (C) 2009, Google Inc. - * 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.lib; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.PackMismatchException; -import org.eclipse.jgit.lib.RepositoryCache.FileKey; -import org.eclipse.jgit.util.FS; - -/** - * Traditional file system based {@link ObjectDatabase}. - *

- * This is the classical object database representation for a Git repository, - * where objects are stored loose by hashing them into directories by their - * {@link ObjectId}, or are stored in compressed containers known as - * {@link PackFile}s. - *

- * Optionally an object database can reference one or more alternates; other - * ObjectDatabase instances that are searched in addition to the current - * database. - *

- * Databases are divided into two halves: a half that is considered to be fast - * to search (the {@code PackFile}s), and a half that is considered to be slow - * to search (loose objects). When alternates are present the fast half is fully - * searched (recursively through all alternates) before the slow half is - * considered. - */ -public class ObjectDirectory extends FileObjectDatabase { - private static final PackList NO_PACKS = new PackList(-1, -1, new PackFile[0]); - - private final Config config; - - private final File objects; - - private final File infoDirectory; - - private final File packDirectory; - - private final File alternatesFile; - - private final AtomicReference packList; - - private final FS fs; - - private final AtomicReference alternates; - - /** - * Initialize a reference to an on-disk object directory. - * - * @param cfg - * configuration this directory consults for write settings. - * @param dir - * the location of the objects directory. - * @param alternatePaths - * a list of alternate object directories - * @param fs - * the file system abstraction which will be necessary to perform - * certain file system operations. - * @throws IOException - * an alternate object cannot be opened. - */ - public ObjectDirectory(final Config cfg, final File dir, - File[] alternatePaths, FS fs) throws IOException { - config = cfg; - objects = dir; - infoDirectory = new File(objects, "info"); - packDirectory = new File(objects, "pack"); - alternatesFile = new File(infoDirectory, "alternates"); - packList = new AtomicReference(NO_PACKS); - this.fs = fs; - - alternates = new AtomicReference(); - if (alternatePaths != null) { - AlternateHandle[] alt; - - alt = new AlternateHandle[alternatePaths.length]; - for (int i = 0; i < alternatePaths.length; i++) - alt[i] = openAlternate(alternatePaths[i]); - alternates.set(alt); - } - } - - /** - * @return the location of the objects directory. - */ - public final File getDirectory() { - return objects; - } - - @Override - public boolean exists() { - return objects.exists(); - } - - @Override - public void create() throws IOException { - objects.mkdirs(); - infoDirectory.mkdir(); - packDirectory.mkdir(); - } - - @Override - public ObjectInserter newInserter() { - return new ObjectDirectoryInserter(this, config); - } - - @Override - public void close() { - final PackList packs = packList.get(); - packList.set(NO_PACKS); - for (final PackFile p : packs.packs) - p.close(); - - // Fully close all loaded alternates and clear the alternate list. - AlternateHandle[] alt = alternates.get(); - if (alt != null) { - alternates.set(null); - for(final AlternateHandle od : alt) - od.close(); - } - } - - /** - * Compute the location of a loose object file. - * - * @param objectId - * identity of the loose object to map to the directory. - * @return location of the object, if it were to exist as a loose object. - */ - public File fileFor(final AnyObjectId objectId) { - return fileFor(objectId.name()); - } - - private File fileFor(final String objectName) { - final String d = objectName.substring(0, 2); - final String f = objectName.substring(2); - return new File(new File(objects, d), f); - } - - /** - * @return unmodifiable collection of all known pack files local to this - * directory. Most recent packs are presented first. Packs most - * likely to contain more recent objects appear before packs - * containing objects referenced by commits further back in the - * history of the repository. - */ - public Collection getPacks() { - final PackFile[] packs = packList.get().packs; - return Collections.unmodifiableCollection(Arrays.asList(packs)); - } - - /** - * Add a single existing pack to the list of available pack files. - * - * @param pack - * path of the pack file to open. - * @param idx - * path of the corresponding index file. - * @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 { - final String p = pack.getName(); - final String i = idx.getName(); - - if (p.length() != 50 || !p.startsWith("pack-") || !p.endsWith(".pack")) - throw new IOException(MessageFormat.format(JGitText.get().notAValidPack, pack)); - - if (i.length() != 49 || !i.startsWith("pack-") || !i.endsWith(".idx")) - throw new IOException(MessageFormat.format(JGitText.get().notAValidPack, idx)); - - if (!p.substring(0, 45).equals(i.substring(0, 45))) - throw new IOException(MessageFormat.format(JGitText.get().packDoesNotMatchIndex, pack)); - - insertPack(new PackFile(idx, pack)); - } - - @Override - public String toString() { - return "ObjectDirectory[" + getDirectory() + "]"; - } - - boolean hasObject1(final AnyObjectId objectId) { - for (final PackFile p : packList.get().packs) { - try { - if (p.hasObject(objectId)) { - return true; - } - } catch (IOException e) { - // The hasObject call should have only touched the index, - // so any failure here indicates the index is unreadable - // by this process, and the pack is likewise not readable. - // - removePack(p); - continue; - } - } - return false; - } - - ObjectLoader openObject1(final WindowCursor curs, - final AnyObjectId objectId) throws IOException { - PackList pList = packList.get(); - SEARCH: for (;;) { - for (final PackFile p : pList.packs) { - try { - final PackedObjectLoader ldr = p.get(curs, objectId); - if (ldr != null) { - ldr.materialize(curs); - return ldr; - } - } catch (PackMismatchException e) { - // Pack was modified; refresh the entire pack list. - // - pList = scanPacks(pList); - continue SEARCH; - } catch (IOException e) { - // Assume the pack is corrupted. - // - removePack(p); - } - } - return null; - } - } - - @Override - void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, - WindowCursor curs) throws IOException { - PackList pList = packList.get(); - SEARCH: for (;;) { - for (final PackFile p : pList.packs) { - try { - PackedObjectLoader ldr = p.get(curs, otp); - if (ldr != null) - packer.select(otp, new LocalObjectRepresentation(ldr)); - } catch (PackMismatchException e) { - // Pack was modified; refresh the entire pack list. - // - pList = scanPacks(pList); - continue SEARCH; - } catch (IOException e) { - // Assume the pack is corrupted. - // - removePack(p); - } - } - break SEARCH; - } - - for (AlternateHandle h : myAlternates()) - h.db.selectObjectRepresentation(packer, otp, curs); - } - - boolean hasObject2(final String objectName) { - return fileFor(objectName).exists(); - } - - ObjectLoader openObject2(final WindowCursor curs, - final String objectName, final AnyObjectId objectId) - throws IOException { - try { - return new UnpackedObjectLoader(fileFor(objectName), objectId); - } catch (FileNotFoundException noFile) { - return null; - } - } - - boolean tryAgain1() { - final PackList old = packList.get(); - if (old.tryAgain(packDirectory.lastModified())) - return old != scanPacks(old); - return false; - } - - private void insertPack(final PackFile pf) { - PackList o, n; - do { - o = packList.get(); - - // If the pack in question is already present in the list - // (picked up by a concurrent thread that did a scan?) we - // do not want to insert it a second time. - // - final PackFile[] oldList = o.packs; - final String name = pf.getPackFile().getName(); - for (PackFile p : oldList) { - if (PackFile.SORT.compare(pf, p) < 0) - break; - if (name.equals(p.getPackFile().getName())) - return; - } - - final PackFile[] newList = new PackFile[1 + oldList.length]; - newList[0] = pf; - System.arraycopy(oldList, 0, newList, 1, oldList.length); - n = new PackList(o.lastRead, o.lastModified, newList); - } while (!packList.compareAndSet(o, n)); - } - - private void removePack(final PackFile deadPack) { - PackList o, n; - do { - o = packList.get(); - - final PackFile[] oldList = o.packs; - final int j = indexOf(oldList, deadPack); - if (j < 0) - break; - - final PackFile[] newList = new PackFile[oldList.length - 1]; - System.arraycopy(oldList, 0, newList, 0, j); - System.arraycopy(oldList, j + 1, newList, j, newList.length - j); - n = new PackList(o.lastRead, o.lastModified, newList); - } while (!packList.compareAndSet(o, n)); - deadPack.close(); - } - - private static int indexOf(final PackFile[] list, final PackFile pack) { - for (int i = 0; i < list.length; i++) { - if (list[i] == pack) - return i; - } - return -1; - } - - private PackList scanPacks(final PackList original) { - synchronized (packList) { - PackList o, n; - do { - o = packList.get(); - if (o != original) { - // Another thread did the scan for us, while we - // were blocked on the monitor above. - // - return o; - } - n = scanPacksImpl(o); - if (n == o) - return n; - } while (!packList.compareAndSet(o, n)); - return n; - } - } - - private PackList scanPacksImpl(final PackList old) { - final Map forReuse = reuseMap(old); - final long lastRead = System.currentTimeMillis(); - final long lastModified = packDirectory.lastModified(); - final Set names = listPackDirectory(); - final List list = new ArrayList(names.size() >> 2); - boolean foundNew = false; - for (final String indexName : names) { - // Must match "pack-[0-9a-f]{40}.idx" to be an index. - // - if (indexName.length() != 49 || !indexName.endsWith(".idx")) - continue; - - final String base = indexName.substring(0, indexName.length() - 4); - final String packName = base + ".pack"; - if (!names.contains(packName)) { - // Sometimes C Git's HTTP fetch transport leaves a - // .idx file behind and does not download the .pack. - // We have to skip over such useless indexes. - // - continue; - } - - final PackFile oldPack = forReuse.remove(packName); - if (oldPack != null) { - list.add(oldPack); - continue; - } - - final File packFile = new File(packDirectory, packName); - final File idxFile = new File(packDirectory, indexName); - list.add(new PackFile(idxFile, packFile)); - foundNew = true; - } - - // If we did not discover any new files, the modification time was not - // changed, and we did not remove any files, then the set of files is - // the same as the set we were given. Instead of building a new object - // return the same collection. - // - if (!foundNew && lastModified == old.lastModified && forReuse.isEmpty()) - return old.updateLastRead(lastRead); - - for (final PackFile p : forReuse.values()) { - p.close(); - } - - if (list.isEmpty()) - return new PackList(lastRead, lastModified, NO_PACKS.packs); - - final PackFile[] r = list.toArray(new PackFile[list.size()]); - Arrays.sort(r, PackFile.SORT); - return new PackList(lastRead, lastModified, r); - } - - private static Map reuseMap(final PackList old) { - final Map forReuse = new HashMap(); - for (final PackFile p : old.packs) { - if (p.invalid()) { - // The pack instance is corrupted, and cannot be safely used - // again. Do not include it in our reuse map. - // - p.close(); - continue; - } - - final PackFile prior = forReuse.put(p.getPackFile().getName(), p); - if (prior != null) { - // This should never occur. It should be impossible for us - // to have two pack files with the same name, as all of them - // came out of the same directory. If it does, we promised to - // close any PackFiles we did not reuse, so close the second, - // readers are likely to be actively using the first. - // - forReuse.put(prior.getPackFile().getName(), prior); - p.close(); - } - } - return forReuse; - } - - private Set listPackDirectory() { - final String[] nameList = packDirectory.list(); - if (nameList == null) - return Collections.emptySet(); - final Set nameSet = new HashSet(nameList.length << 1); - for (final String name : nameList) { - if (name.startsWith("pack-")) - nameSet.add(name); - } - return nameSet; - } - - AlternateHandle[] myAlternates() { - AlternateHandle[] alt = alternates.get(); - if (alt == null) { - synchronized (alternates) { - alt = alternates.get(); - if (alt == null) { - try { - alt = loadAlternates(); - } catch (IOException e) { - alt = new AlternateHandle[0]; - } - alternates.set(alt); - } - } - } - return alt; - } - - private AlternateHandle[] loadAlternates() throws IOException { - final List l = new ArrayList(4); - final BufferedReader br = open(alternatesFile); - try { - String line; - while ((line = br.readLine()) != null) { - l.add(openAlternate(line)); - } - } finally { - br.close(); - } - return l.toArray(new AlternateHandle[l.size()]); - } - - private static BufferedReader open(final File f) - throws FileNotFoundException { - return new BufferedReader(new FileReader(f)); - } - - private AlternateHandle openAlternate(final String location) - throws IOException { - final File objdir = fs.resolve(objects, location); - return openAlternate(objdir); - } - - private AlternateHandle openAlternate(File objdir) throws IOException { - final File parent = objdir.getParentFile(); - if (FileKey.isGitRepository(parent, fs)) { - FileKey key = FileKey.exact(parent, fs); - FileRepository db = (FileRepository) RepositoryCache.open(key); - return new AlternateRepository(db); - } - - ObjectDirectory db = new ObjectDirectory(config, objdir, null, fs); - return new AlternateHandle(db); - } - - private static final class PackList { - /** Last wall-clock time the directory was read. */ - volatile long lastRead; - - /** Last modification time of {@link ObjectDirectory#packDirectory}. */ - final long lastModified; - - /** All known packs, sorted by {@link PackFile#SORT}. */ - final PackFile[] packs; - - private boolean cannotBeRacilyClean; - - PackList(final long lastRead, final long lastModified, - final PackFile[] packs) { - this.lastRead = lastRead; - this.lastModified = lastModified; - this.packs = packs; - this.cannotBeRacilyClean = notRacyClean(lastRead); - } - - private boolean notRacyClean(final long read) { - return read - lastModified > 2 * 60 * 1000L; - } - - PackList updateLastRead(final long now) { - if (notRacyClean(now)) - cannotBeRacilyClean = true; - lastRead = now; - return this; - } - - boolean tryAgain(final long currLastModified) { - // Any difference indicates the directory was modified. - // - if (lastModified != currLastModified) - return true; - - // We have already determined the last read was far enough - // after the last modification that any new modifications - // are certain to change the last modified time. - // - if (cannotBeRacilyClean) - return false; - - if (notRacyClean(lastRead)) { - // Our last read should have marked cannotBeRacilyClean, - // but this thread may not have seen the change. The read - // of the volatile field lastRead should have fixed that. - // - return false; - } - - // We last read this directory too close to its last observed - // modification time. We may have missed a modification. Scan - // the directory again, to ensure we still see the same state. - // - return true; - } - } - - @Override - public ObjectDatabase newCachedDatabase() { - return newCachedFileObjectDatabase(); - } - - FileObjectDatabase newCachedFileObjectDatabase() { - return new CachedObjectDirectory(this); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java deleted file mode 100644 index 146d2d6250..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2008, Shawn O. Pearce - * Copyright (C) 2009, Google Inc. - * 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.lib; - -import java.io.EOFException; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.DigestOutputStream; -import java.security.MessageDigest; -import java.util.zip.Deflater; -import java.util.zip.DeflaterOutputStream; - -import org.eclipse.jgit.errors.ObjectWritingException; - -/** Creates loose objects in a {@link ObjectDirectory}. */ -class ObjectDirectoryInserter extends ObjectInserter { - private final ObjectDirectory db; - - private final Config config; - - private Deflater deflate; - - ObjectDirectoryInserter(final ObjectDirectory dest, final Config cfg) { - db = dest; - config = cfg; - } - - @Override - public ObjectId insert(final int type, long len, final InputStream is) - throws IOException { - final MessageDigest md = digest(); - final File tmp = toTemp(md, type, len, is); - final ObjectId id = ObjectId.fromRaw(md.digest()); - if (db.hasObject(id)) { - // Object is already in the repository, remove temporary file. - // - tmp.delete(); - return id; - } - - final File dst = db.fileFor(id); - if (tmp.renameTo(dst)) - return id; - - // 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. - // - dst.getParentFile().mkdir(); - if (tmp.renameTo(dst)) - return id; - - if (db.hasObject(id)) { - tmp.delete(); - return id; - } - - // The object failed to be renamed into its proper - // location and it doesn't exist in the repository - // either. We really don't know what went wrong, so - // fail. - // - tmp.delete(); - throw new ObjectWritingException("Unable to create new object: " + dst); - } - - @Override - public void flush() throws IOException { - // Do nothing. Objects are immediately visible. - } - - @Override - public void release() { - if (deflate != null) { - try { - deflate.end(); - } finally { - deflate = null; - } - } - } - - private File toTemp(final MessageDigest md, final int type, long len, - final InputStream is) throws IOException, FileNotFoundException, - Error { - boolean delete = true; - File tmp = File.createTempFile("noz", null, db.getDirectory()); - try { - DigestOutputStream dOut = new DigestOutputStream( - compress(new FileOutputStream(tmp)), md); - try { - dOut.write(Constants.encodedTypeString(type)); - dOut.write((byte) ' '); - dOut.write(Constants.encodeASCII(len)); - dOut.write((byte) 0); - - final byte[] buf = buffer(); - while (len > 0) { - int n = is.read(buf, 0, (int) Math.min(len, buf.length)); - if (n <= 0) - throw shortInput(len); - dOut.write(buf, 0, n); - len -= n; - } - } finally { - dOut.close(); - } - - tmp.setReadOnly(); - delete = false; - return tmp; - } finally { - if (delete) - tmp.delete(); - } - } - - private DeflaterOutputStream compress(final OutputStream out) { - if (deflate == null) - deflate = new Deflater(config.get(CoreConfig.KEY).getCompression()); - else - deflate.reset(); - return new DeflaterOutputStream(out, deflate); - } - - private static EOFException shortInput(long missing) { - return new EOFException("Input did not match supplied length. " - + missing + " bytes are missing."); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java deleted file mode 100644 index ab25f61889..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java +++ /dev/null @@ -1,695 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.EOFException; -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel.MapMode; -import java.text.MessageFormat; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.zip.CRC32; -import java.util.zip.DataFormatException; -import java.util.zip.Inflater; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.errors.PackInvalidException; -import org.eclipse.jgit.errors.PackMismatchException; -import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; -import org.eclipse.jgit.util.LongList; -import org.eclipse.jgit.util.NB; -import org.eclipse.jgit.util.RawParseUtils; - -/** - * A Git version 2 pack file representation. A pack file contains Git objects in - * delta packed format yielding high compression of lots of object where some - * objects are similar. - */ -public class PackFile implements Iterable { - /** Sorts PackFiles to be most recently created to least recently created. */ - public static Comparator SORT = new Comparator() { - public int compare(final PackFile a, final PackFile b) { - return b.packLastModified - a.packLastModified; - } - }; - - private final File idxFile; - - private final File packFile; - - final int hash; - - private RandomAccessFile fd; - - /** Serializes reads performed against {@link #fd}. */ - private final Object readLock = new Object(); - - long length; - - private int activeWindows; - - private int activeCopyRawData; - - private int packLastModified; - - private volatile boolean invalid; - - private byte[] packChecksum; - - private PackIndex loadedIdx; - - private PackReverseIndex reverseIdx; - - /** - * Objects we have tried to read, and discovered to be corrupt. - *

- * The list is allocated after the first corruption is found, and filled in - * as more entries are discovered. Typically this list is never used, as - * pack files do not usually contain corrupt objects. - */ - private volatile LongList corruptObjects; - - /** - * Construct a reader for an existing, pre-indexed packfile. - * - * @param idxFile - * path of the .idx file listing the contents. - * @param packFile - * path of the .pack file holding the data. - */ - public PackFile(final File idxFile, final File packFile) { - this.idxFile = idxFile; - this.packFile = packFile; - this.packLastModified = (int) (packFile.lastModified() >> 10); - - // Multiply by 31 here so we can more directly combine with another - // value in WindowCache.hash(), without doing the multiply there. - // - hash = System.identityHashCode(this) * 31; - length = Long.MAX_VALUE; - } - - private synchronized PackIndex idx() throws IOException { - if (loadedIdx == null) { - if (invalid) - throw new PackInvalidException(packFile); - - try { - final PackIndex idx = PackIndex.open(idxFile); - - if (packChecksum == null) - packChecksum = idx.packChecksum; - else if (!Arrays.equals(packChecksum, idx.packChecksum)) - throw new PackMismatchException(JGitText.get().packChecksumMismatch); - - loadedIdx = idx; - } catch (IOException e) { - invalid = true; - throw e; - } - } - return loadedIdx; - } - - final PackedObjectLoader resolveBase(final WindowCursor curs, final long ofs) - throws IOException { - if (isCorrupt(ofs)) { - throw new CorruptObjectException(MessageFormat.format(JGitText - .get().objectAtHasBadZlibStream, ofs, getPackFile())); - } - return reader(curs, ofs); - } - - /** @return the File object which locates this pack on disk. */ - public File getPackFile() { - return packFile; - } - - /** - * Determine if an object is contained within the pack file. - *

- * For performance reasons only the index file is searched; the main pack - * content is ignored entirely. - *

- * - * @param id - * the object to look for. Must not be null. - * @return true if the object is in this pack; false otherwise. - * @throws IOException - * the index file cannot be loaded into memory. - */ - public boolean hasObject(final AnyObjectId id) throws IOException { - final long offset = idx().findOffset(id); - return 0 < offset && !isCorrupt(offset); - } - - /** - * Get an object from this pack. - * - * @param curs - * temporary working space associated with the calling thread. - * @param id - * the object to obtain from the pack. Must not be null. - * @return the object loader for the requested object if it is contained in - * this pack; null if the object was not found. - * @throws IOException - * the pack file or the index could not be read. - */ - public PackedObjectLoader get(final WindowCursor curs, final AnyObjectId id) - throws IOException { - final long offset = idx().findOffset(id); - return 0 < offset && !isCorrupt(offset) ? reader(curs, offset) : null; - } - - /** - * Close the resources utilized by this repository - */ - public void close() { - UnpackedObjectCache.purge(this); - WindowCache.purge(this); - synchronized (this) { - loadedIdx = null; - reverseIdx = null; - } - } - - /** - * Provide iterator over entries in associated pack index, that should also - * exist in this pack file. Objects returned by such iterator are mutable - * during iteration. - *

- * Iterator returns objects in SHA-1 lexicographical order. - *

- * - * @return iterator over entries of associated pack index - * - * @see PackIndex#iterator() - */ - public Iterator iterator() { - try { - return idx().iterator(); - } catch (IOException e) { - return Collections. emptyList().iterator(); - } - } - - /** - * Obtain the total number of objects available in this pack. This method - * relies on pack index, giving number of effectively available objects. - * - * @return number of objects in index of this pack, likewise in this pack - * @throws IOException - * the index file cannot be loaded into memory. - */ - long getObjectCount() throws IOException { - return idx().getObjectCount(); - } - - /** - * Search for object id with the specified start offset in associated pack - * (reverse) index. - * - * @param offset - * start offset of object to find - * @return object id for this offset, or null if no object was found - * @throws IOException - * the index file cannot be loaded into memory. - */ - ObjectId findObjectForOffset(final long offset) throws IOException { - return getReverseIdx().findObject(offset); - } - - final UnpackedObjectCache.Entry readCache(final long position) { - return UnpackedObjectCache.get(this, position); - } - - final void saveCache(final long position, final byte[] data, final int type) { - UnpackedObjectCache.store(this, position, data, type); - } - - final byte[] decompress(final long position, final int totalSize, - final WindowCursor curs) throws DataFormatException, IOException { - final byte[] dstbuf = new byte[totalSize]; - if (curs.inflate(this, position, dstbuf, 0) != totalSize) - throw new EOFException(MessageFormat.format(JGitText.get().shortCompressedStreamAt, position)); - return dstbuf; - } - - final void copyAsIs(PackOutputStream out, LocalObjectToPack src, - WindowCursor curs) throws IOException, - StoredObjectRepresentationNotAvailableException { - beginCopyAsIs(src); - try { - copyAsIs2(out, src, curs); - } finally { - endCopyAsIs(); - } - } - - private void copyAsIs2(PackOutputStream out, LocalObjectToPack src, - WindowCursor curs) throws IOException, - StoredObjectRepresentationNotAvailableException { - final CRC32 crc1 = new CRC32(); - final CRC32 crc2 = new CRC32(); - final byte[] buf = out.getCopyBuffer(); - - // Rip apart the header so we can discover the size. - // - readFully(src.copyOffset, buf, 0, 20, curs); - int c = buf[0] & 0xff; - final int typeCode = (c >> 4) & 7; - long inflatedLength = c & 15; - int shift = 4; - int headerCnt = 1; - while ((c & 0x80) != 0) { - c = buf[headerCnt++] & 0xff; - inflatedLength += (c & 0x7f) << shift; - shift += 7; - } - - if (typeCode == Constants.OBJ_OFS_DELTA) { - do { - c = buf[headerCnt++] & 0xff; - } while ((c & 128) != 0); - crc1.update(buf, 0, headerCnt); - crc2.update(buf, 0, headerCnt); - } else if (typeCode == Constants.OBJ_REF_DELTA) { - crc1.update(buf, 0, headerCnt); - crc2.update(buf, 0, headerCnt); - - readFully(src.copyOffset + headerCnt, buf, 0, 20, curs); - crc1.update(buf, 0, 20); - crc2.update(buf, 0, headerCnt); - headerCnt += 20; - } else { - crc1.update(buf, 0, headerCnt); - crc2.update(buf, 0, headerCnt); - } - - final long dataOffset = src.copyOffset + headerCnt; - final long dataLength; - final long expectedCRC; - final ByteArrayWindow quickCopy; - - // Verify the object isn't corrupt before sending. If it is, - // we report it missing instead. - // - try { - dataLength = findEndOffset(src.copyOffset) - dataOffset; - quickCopy = curs.quickCopy(this, dataOffset, dataLength); - - if (idx().hasCRC32Support()) { - // Index has the CRC32 code cached, validate the object. - // - expectedCRC = idx().findCRC32(src); - if (quickCopy != null) { - quickCopy.crc32(crc1, dataOffset, (int) dataLength); - } else { - long pos = dataOffset; - long cnt = dataLength; - while (cnt > 0) { - final int n = (int) Math.min(cnt, buf.length); - readFully(pos, buf, 0, n, curs); - crc1.update(buf, 0, n); - pos += n; - cnt -= n; - } - } - if (crc1.getValue() != expectedCRC) { - setCorrupt(src.copyOffset); - throw new CorruptObjectException(MessageFormat.format( - JGitText.get().objectAtHasBadZlibStream, - src.copyOffset, getPackFile())); - } - } else { - // We don't have a CRC32 code in the index, so compute it - // now while inflating the raw data to get zlib to tell us - // whether or not the data is safe. - // - Inflater inf = curs.inflater(); - byte[] tmp = new byte[1024]; - if (quickCopy != null) { - quickCopy.check(inf, tmp, dataOffset, (int) dataLength); - } else { - long pos = dataOffset; - long cnt = dataLength; - while (cnt > 0) { - final int n = (int) Math.min(cnt, buf.length); - readFully(pos, buf, 0, n, curs); - crc1.update(buf, 0, n); - inf.setInput(buf, 0, n); - while (inf.inflate(tmp, 0, tmp.length) > 0) - continue; - pos += n; - cnt -= n; - } - } - if (!inf.finished() || inf.getBytesRead() != dataLength) { - setCorrupt(src.copyOffset); - throw new EOFException(MessageFormat.format( - JGitText.get().shortCompressedStreamAt, - src.copyOffset)); - } - expectedCRC = crc1.getValue(); - } - } catch (DataFormatException dataFormat) { - setCorrupt(src.copyOffset); - - CorruptObjectException corruptObject = new CorruptObjectException( - MessageFormat.format( - JGitText.get().objectAtHasBadZlibStream, - src.copyOffset, getPackFile())); - corruptObject.initCause(dataFormat); - - StoredObjectRepresentationNotAvailableException gone; - gone = new StoredObjectRepresentationNotAvailableException(src); - gone.initCause(corruptObject); - throw gone; - - } catch (IOException ioError) { - StoredObjectRepresentationNotAvailableException gone; - gone = new StoredObjectRepresentationNotAvailableException(src); - gone.initCause(ioError); - throw gone; - } - - if (quickCopy != null) { - // The entire object fits into a single byte array window slice, - // and we have it pinned. Write this out without copying. - // - out.writeHeader(src, inflatedLength); - quickCopy.write(out, dataOffset, (int) dataLength); - - } else if (dataLength <= buf.length) { - // Tiny optimization: Lots of objects are very small deltas or - // deflated commits that are likely to fit in the copy buffer. - // - out.writeHeader(src, inflatedLength); - out.write(buf, 0, (int) dataLength); - } else { - // Now we are committed to sending the object. As we spool it out, - // check its CRC32 code to make sure there wasn't corruption between - // the verification we did above, and us actually outputting it. - // - out.writeHeader(src, inflatedLength); - long pos = dataOffset; - long cnt = dataLength; - while (cnt > 0) { - final int n = (int) Math.min(cnt, buf.length); - readFully(pos, buf, 0, n, curs); - crc2.update(buf, 0, n); - out.write(buf, 0, n); - pos += n; - cnt -= n; - } - if (crc2.getValue() != expectedCRC) { - throw new CorruptObjectException(MessageFormat.format(JGitText - .get().objectAtHasBadZlibStream, src.copyOffset, - getPackFile())); - } - } - } - - boolean invalid() { - return invalid; - } - - private void readFully(final long position, final byte[] dstbuf, - int dstoff, final int cnt, final WindowCursor curs) - throws IOException { - if (curs.copy(this, position, dstbuf, dstoff, cnt) != cnt) - throw new EOFException(); - } - - private synchronized void beginCopyAsIs(ObjectToPack otp) - throws StoredObjectRepresentationNotAvailableException { - if (++activeCopyRawData == 1 && activeWindows == 0) { - try { - doOpen(); - } catch (IOException thisPackNotValid) { - StoredObjectRepresentationNotAvailableException gone; - - gone = new StoredObjectRepresentationNotAvailableException(otp); - gone.initCause(thisPackNotValid); - throw gone; - } - } - } - - private synchronized void endCopyAsIs() { - if (--activeCopyRawData == 0 && activeWindows == 0) - doClose(); - } - - synchronized boolean beginWindowCache() throws IOException { - if (++activeWindows == 1) { - if (activeCopyRawData == 0) - doOpen(); - return true; - } - return false; - } - - synchronized boolean endWindowCache() { - final boolean r = --activeWindows == 0; - if (r && activeCopyRawData == 0) - doClose(); - return r; - } - - private void doOpen() throws IOException { - try { - if (invalid) - throw new PackInvalidException(packFile); - synchronized (readLock) { - fd = new RandomAccessFile(packFile, "r"); - length = fd.length(); - onOpenPack(); - } - } catch (IOException ioe) { - openFail(); - throw ioe; - } catch (RuntimeException re) { - openFail(); - throw re; - } catch (Error re) { - openFail(); - throw re; - } - } - - private void openFail() { - activeWindows = 0; - activeCopyRawData = 0; - invalid = true; - doClose(); - } - - private void doClose() { - synchronized (readLock) { - if (fd != null) { - try { - fd.close(); - } catch (IOException err) { - // Ignore a close event. We had it open only for reading. - // There should not be errors related to network buffers - // not flushed, etc. - } - fd = null; - } - } - } - - ByteArrayWindow read(final long pos, int size) throws IOException { - synchronized (readLock) { - if (length < pos + size) - size = (int) (length - pos); - final byte[] buf = new byte[size]; - fd.seek(pos); - fd.readFully(buf, 0, size); - return new ByteArrayWindow(this, pos, buf); - } - } - - ByteWindow mmap(final long pos, int size) throws IOException { - synchronized (readLock) { - if (length < pos + size) - size = (int) (length - pos); - - MappedByteBuffer map; - try { - map = fd.getChannel().map(MapMode.READ_ONLY, pos, size); - } catch (IOException ioe1) { - // The most likely reason this failed is the JVM has run out - // of virtual memory. We need to discard quickly, and try to - // force the GC to finalize and release any existing mappings. - // - System.gc(); - System.runFinalization(); - map = fd.getChannel().map(MapMode.READ_ONLY, pos, size); - } - - if (map.hasArray()) - return new ByteArrayWindow(this, pos, map.array()); - return new ByteBufferWindow(this, pos, map); - } - } - - private void onOpenPack() throws IOException { - final PackIndex idx = idx(); - final byte[] buf = new byte[20]; - - fd.seek(0); - fd.readFully(buf, 0, 12); - if (RawParseUtils.match(buf, 0, Constants.PACK_SIGNATURE) != 4) - throw new IOException(JGitText.get().notAPACKFile); - final long vers = NB.decodeUInt32(buf, 4); - final long packCnt = NB.decodeUInt32(buf, 8); - if (vers != 2 && vers != 3) - throw new IOException(MessageFormat.format(JGitText.get().unsupportedPackVersion, vers)); - - if (packCnt != idx.getObjectCount()) - throw new PackMismatchException(MessageFormat.format( - JGitText.get().packObjectCountMismatch, packCnt, idx.getObjectCount(), getPackFile())); - - fd.seek(length - 20); - fd.read(buf, 0, 20); - if (!Arrays.equals(buf, packChecksum)) - throw new PackMismatchException(MessageFormat.format( - JGitText.get().packObjectCountMismatch - , ObjectId.fromRaw(buf).name() - , ObjectId.fromRaw(idx.packChecksum).name() - , getPackFile())); - } - - private PackedObjectLoader reader(final WindowCursor curs, - final long objOffset) throws IOException { - int p = 0; - final byte[] ib = curs.tempId; - readFully(objOffset, ib, 0, 20, curs); - int c = ib[p++] & 0xff; - final int typeCode = (c >> 4) & 7; - long dataSize = c & 15; - int shift = 4; - while ((c & 0x80) != 0) { - c = ib[p++] & 0xff; - dataSize += (c & 0x7f) << shift; - shift += 7; - } - - switch (typeCode) { - case Constants.OBJ_COMMIT: - case Constants.OBJ_TREE: - case Constants.OBJ_BLOB: - case Constants.OBJ_TAG: - return new WholePackedObjectLoader(this, objOffset, p, typeCode, - (int) dataSize); - - case Constants.OBJ_OFS_DELTA: { - c = ib[p++] & 0xff; - long ofs = c & 127; - while ((c & 128) != 0) { - ofs += 1; - c = ib[p++] & 0xff; - ofs <<= 7; - ofs += (c & 127); - } - return new DeltaOfsPackedObjectLoader(this, objOffset, p, - (int) dataSize, objOffset - ofs); - } - case Constants.OBJ_REF_DELTA: { - readFully(objOffset + p, ib, 0, 20, curs); - return new DeltaRefPackedObjectLoader(this, objOffset, p + 20, - (int) dataSize, ObjectId.fromRaw(ib)); - } - default: - throw new IOException(MessageFormat.format(JGitText.get().unknownObjectType, typeCode)); - } - } - - private long findEndOffset(final long startOffset) - throws IOException, CorruptObjectException { - final long maxOffset = length - 20; - return getReverseIdx().findNextOffset(startOffset, maxOffset); - } - - private synchronized PackReverseIndex getReverseIdx() throws IOException { - if (reverseIdx == null) - reverseIdx = new PackReverseIndex(idx()); - return reverseIdx; - } - - private boolean isCorrupt(long offset) { - LongList list = corruptObjects; - if (list == null) - return false; - synchronized (list) { - return list.contains(offset); - } - } - - private void setCorrupt(long offset) { - LongList list = corruptObjects; - if (list == null) { - synchronized (readLock) { - list = corruptObjects; - if (list == null) { - list = new LongList(); - corruptObjects = list; - } - } - } - synchronized (list) { - list.add(offset); - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java deleted file mode 100644 index 13985e78e9..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2008, Shawn O. Pearce - * 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.lib; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.text.MessageFormat; -import java.util.Iterator; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.util.IO; -import org.eclipse.jgit.util.NB; - -/** - * Access path to locate objects by {@link ObjectId} in a {@link PackFile}. - *

- * Indexes are strictly redundant information in that we can rebuild all of the - * data held in the index file from the on disk representation of the pack file - * itself, but it is faster to access for random requests because data is stored - * by ObjectId. - *

- */ -public abstract class PackIndex implements Iterable { - /** - * Open an existing pack .idx file for reading. - *

- * The format of the file will be automatically detected and a proper access - * implementation for that format will be constructed and returned to the - * caller. The file may or may not be held open by the returned instance. - *

- * - * @param idxFile - * existing pack .idx to read. - * @return access implementation for the requested file. - * @throws FileNotFoundException - * the file does not exist. - * @throws IOException - * the file exists but could not be read due to security errors, - * unrecognized data version, or unexpected data corruption. - */ - public static PackIndex open(final File idxFile) throws IOException { - final FileInputStream fd = new FileInputStream(idxFile); - try { - final byte[] hdr = new byte[8]; - IO.readFully(fd, hdr, 0, hdr.length); - if (isTOC(hdr)) { - final int v = NB.decodeInt32(hdr, 4); - switch (v) { - case 2: - return new PackIndexV2(fd); - default: - throw new IOException(MessageFormat.format(JGitText.get().unsupportedPackIndexVersion, v)); - } - } - return new PackIndexV1(fd, hdr); - } catch (IOException ioe) { - final String path = idxFile.getAbsolutePath(); - final IOException err; - err = new IOException(MessageFormat.format(JGitText.get().unreadablePackIndex, path)); - err.initCause(ioe); - throw err; - } finally { - try { - fd.close(); - } catch (IOException err2) { - // ignore - } - } - } - - private static boolean isTOC(final byte[] h) { - final byte[] toc = PackIndexWriter.TOC; - for (int i = 0; i < toc.length; i++) - if (h[i] != toc[i]) - return false; - return true; - } - - /** Footer checksum applied on the bottom of the pack file. */ - protected byte[] packChecksum; - - /** - * Determine if an object is contained within the pack file. - * - * @param id - * the object to look for. Must not be null. - * @return true if the object is listed in this index; false otherwise. - */ - public boolean hasObject(final AnyObjectId id) { - return findOffset(id) != -1; - } - - /** - * Provide iterator that gives access to index entries. Note, that iterator - * returns reference to mutable object, the same reference in each call - - * for performance reason. If client needs immutable objects, it must copy - * returned object on its own. - *

- * Iterator returns objects in SHA-1 lexicographical order. - *

- * - * @return iterator over pack index entries - */ - public abstract Iterator iterator(); - - /** - * Obtain the total number of objects described by this index. - * - * @return number of objects in this index, and likewise in the associated - * pack that this index was generated from. - */ - abstract long getObjectCount(); - - /** - * Obtain the total number of objects needing 64 bit offsets. - * - * @return number of objects in this index using a 64 bit offset; that is an - * object positioned after the 2 GB position within the file. - */ - abstract long getOffset64Count(); - - /** - * Get ObjectId for the n-th object entry returned by {@link #iterator()}. - *

- * This method is a constant-time replacement for the following loop: - * - *

-	 * Iterator<MutableEntry> eItr = index.iterator();
-	 * int curPosition = 0;
-	 * while (eItr.hasNext() && curPosition++ < nthPosition)
-	 * 	eItr.next();
-	 * ObjectId result = eItr.next().toObjectId();
-	 * 
- * - * @param nthPosition - * position within the traversal of {@link #iterator()} that the - * caller needs the object for. The first returned - * {@link MutableEntry} is 0, the second is 1, etc. - * @return the ObjectId for the corresponding entry. - */ - abstract ObjectId getObjectId(long nthPosition); - - /** - * Get ObjectId for the n-th object entry returned by {@link #iterator()}. - *

- * This method is a constant-time replacement for the following loop: - * - *

-	 * Iterator<MutableEntry> eItr = index.iterator();
-	 * int curPosition = 0;
-	 * while (eItr.hasNext() && curPosition++ < nthPosition)
-	 * 	eItr.next();
-	 * ObjectId result = eItr.next().toObjectId();
-	 * 
- * - * @param nthPosition - * unsigned 32 bit position within the traversal of - * {@link #iterator()} that the caller needs the object for. The - * first returned {@link MutableEntry} is 0, the second is 1, - * etc. Positions past 2**31-1 are negative, but still valid. - * @return the ObjectId for the corresponding entry. - */ - final ObjectId getObjectId(final int nthPosition) { - if (nthPosition >= 0) - return getObjectId((long) nthPosition); - final int u31 = nthPosition >>> 1; - final int one = nthPosition & 1; - return getObjectId(((long) u31) << 1 | one); - } - - /** - * Locate the file offset position for the requested object. - * - * @param objId - * name of the object to locate within the pack. - * @return offset of the object's header and compressed content; -1 if the - * object does not exist in this index and is thus not stored in the - * associated pack. - */ - abstract long findOffset(AnyObjectId objId); - - /** - * Retrieve stored CRC32 checksum of the requested object raw-data - * (including header). - * - * @param objId - * id of object to look for - * @return CRC32 checksum of specified object (at 32 less significant bits) - * @throws MissingObjectException - * when requested ObjectId was not found in this index - * @throws UnsupportedOperationException - * when this index doesn't support CRC32 checksum - */ - abstract long findCRC32(AnyObjectId objId) throws MissingObjectException, - UnsupportedOperationException; - - /** - * Check whether this index supports (has) CRC32 checksums for objects. - * - * @return true if CRC32 is stored, false otherwise - */ - abstract boolean hasCRC32Support(); - - /** - * Represent mutable entry of pack index consisting of object id and offset - * in pack (both mutable). - * - */ - public static class MutableEntry { - final MutableObjectId idBuffer = new MutableObjectId(); - - long offset; - - /** - * Returns offset for this index object entry - * - * @return offset of this object in a pack file - */ - public long getOffset() { - return offset; - } - - /** @return hex string describing the object id of this entry. */ - public String name() { - ensureId(); - return idBuffer.name(); - } - - /** @return a copy of the object id. */ - public ObjectId toObjectId() { - ensureId(); - return idBuffer.toObjectId(); - } - - /** @return a complete copy of this entry, that won't modify */ - public MutableEntry cloneEntry() { - final MutableEntry r = new MutableEntry(); - ensureId(); - r.idBuffer.fromObjectId(idBuffer); - r.offset = offset; - return r; - } - - void ensureId() { - // Override in implementations. - } - } - - abstract class EntriesIterator implements Iterator { - protected final MutableEntry entry = initEntry(); - - protected long returnedNumber = 0; - - protected abstract MutableEntry initEntry(); - - public boolean hasNext() { - return returnedNumber < getObjectCount(); - } - - /** - * Implementation must update {@link #returnedNumber} before returning - * element. - */ - public abstract MutableEntry next(); - - public void remove() { - throw new UnsupportedOperationException(); - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java deleted file mode 100644 index bb7cd8b53c..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2007-2009, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.util.IO; -import org.eclipse.jgit.util.NB; - -class PackIndexV1 extends PackIndex { - private static final int IDX_HDR_LEN = 256 * 4; - - private final long[] idxHeader; - - private byte[][] idxdata; - - private long objectCnt; - - PackIndexV1(final InputStream fd, final byte[] hdr) - throws CorruptObjectException, IOException { - final byte[] fanoutTable = new byte[IDX_HDR_LEN]; - System.arraycopy(hdr, 0, fanoutTable, 0, hdr.length); - IO.readFully(fd, fanoutTable, hdr.length, IDX_HDR_LEN - hdr.length); - - idxHeader = new long[256]; // really unsigned 32-bit... - for (int k = 0; k < idxHeader.length; k++) - idxHeader[k] = NB.decodeUInt32(fanoutTable, k * 4); - idxdata = new byte[idxHeader.length][]; - for (int k = 0; k < idxHeader.length; k++) { - int n; - if (k == 0) { - n = (int) (idxHeader[k]); - } else { - n = (int) (idxHeader[k] - idxHeader[k - 1]); - } - if (n > 0) { - idxdata[k] = new byte[n * (Constants.OBJECT_ID_LENGTH + 4)]; - IO.readFully(fd, idxdata[k], 0, idxdata[k].length); - } - } - objectCnt = idxHeader[255]; - - packChecksum = new byte[20]; - IO.readFully(fd, packChecksum, 0, packChecksum.length); - } - - long getObjectCount() { - return objectCnt; - } - - @Override - long getOffset64Count() { - long n64 = 0; - for (final MutableEntry e : this) { - if (e.getOffset() >= Integer.MAX_VALUE) - n64++; - } - return n64; - } - - @Override - ObjectId getObjectId(final long nthPosition) { - int levelOne = Arrays.binarySearch(idxHeader, nthPosition + 1); - long base; - if (levelOne >= 0) { - // If we hit the bucket exactly the item is in the bucket, or - // any bucket before it which has the same object count. - // - base = idxHeader[levelOne]; - while (levelOne > 0 && base == idxHeader[levelOne - 1]) - levelOne--; - } else { - // The item is in the bucket we would insert it into. - // - levelOne = -(levelOne + 1); - } - - base = levelOne > 0 ? idxHeader[levelOne - 1] : 0; - final int p = (int) (nthPosition - base); - final int dataIdx = ((4 + Constants.OBJECT_ID_LENGTH) * p) + 4; - return ObjectId.fromRaw(idxdata[levelOne], dataIdx); - } - - long findOffset(final AnyObjectId objId) { - final int levelOne = objId.getFirstByte(); - byte[] data = idxdata[levelOne]; - if (data == null) - return -1; - int high = data.length / (4 + Constants.OBJECT_ID_LENGTH); - int low = 0; - do { - final int mid = (low + high) >>> 1; - final int pos = ((4 + Constants.OBJECT_ID_LENGTH) * mid) + 4; - final int cmp = objId.compareTo(data, pos); - if (cmp < 0) - high = mid; - else if (cmp == 0) { - int b0 = data[pos - 4] & 0xff; - int b1 = data[pos - 3] & 0xff; - int b2 = data[pos - 2] & 0xff; - int b3 = data[pos - 1] & 0xff; - return (((long) b0) << 24) | (b1 << 16) | (b2 << 8) | (b3); - } else - low = mid + 1; - } while (low < high); - return -1; - } - - @Override - long findCRC32(AnyObjectId objId) { - throw new UnsupportedOperationException(); - } - - @Override - boolean hasCRC32Support() { - return false; - } - - public Iterator iterator() { - return new IndexV1Iterator(); - } - - private class IndexV1Iterator extends EntriesIterator { - private int levelOne; - - private int levelTwo; - - @Override - protected MutableEntry initEntry() { - return new MutableEntry() { - protected void ensureId() { - idBuffer.fromRaw(idxdata[levelOne], levelTwo - - Constants.OBJECT_ID_LENGTH); - } - }; - } - - public MutableEntry next() { - for (; levelOne < idxdata.length; levelOne++) { - if (idxdata[levelOne] == null) - continue; - if (levelTwo < idxdata[levelOne].length) { - entry.offset = NB.decodeUInt32(idxdata[levelOne], levelTwo); - levelTwo += Constants.OBJECT_ID_LENGTH + 4; - returnedNumber++; - return entry; - } - levelTwo = 0; - } - throw new NoSuchElementException(); - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java deleted file mode 100644 index 128b2df8cb..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright (C) 2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.util.IO; -import org.eclipse.jgit.util.NB; - -/** Support for the pack index v2 format. */ -class PackIndexV2 extends PackIndex { - private static final long IS_O64 = 1L << 31; - - private static final int FANOUT = 256; - - private static final int[] NO_INTS = {}; - - private static final byte[] NO_BYTES = {}; - - private long objectCnt; - - private final long[] fanoutTable; - - /** 256 arrays of contiguous object names. */ - private int[][] names; - - /** 256 arrays of the 32 bit offset data, matching {@link #names}. */ - private byte[][] offset32; - - /** 256 arrays of the CRC-32 of objects, matching {@link #names}. */ - private byte[][] crc32; - - /** 64 bit offset table. */ - private byte[] offset64; - - PackIndexV2(final InputStream fd) throws IOException { - final byte[] fanoutRaw = new byte[4 * FANOUT]; - IO.readFully(fd, fanoutRaw, 0, fanoutRaw.length); - fanoutTable = new long[FANOUT]; - for (int k = 0; k < FANOUT; k++) - fanoutTable[k] = NB.decodeUInt32(fanoutRaw, k * 4); - objectCnt = fanoutTable[FANOUT - 1]; - - names = new int[FANOUT][]; - offset32 = new byte[FANOUT][]; - crc32 = new byte[FANOUT][]; - - // Object name table. The size we can permit per fan-out bucket - // is limited to Java's 2 GB per byte array limitation. That is - // no more than 107,374,182 objects per fan-out. - // - for (int k = 0; k < FANOUT; k++) { - final long bucketCnt; - if (k == 0) - bucketCnt = fanoutTable[k]; - else - bucketCnt = fanoutTable[k] - fanoutTable[k - 1]; - - if (bucketCnt == 0) { - names[k] = NO_INTS; - offset32[k] = NO_BYTES; - crc32[k] = NO_BYTES; - continue; - } - - final long nameLen = bucketCnt * Constants.OBJECT_ID_LENGTH; - if (nameLen > Integer.MAX_VALUE) - throw new IOException(JGitText.get().indexFileIsTooLargeForJgit); - - final int intNameLen = (int) nameLen; - final byte[] raw = new byte[intNameLen]; - final int[] bin = new int[intNameLen >>> 2]; - IO.readFully(fd, raw, 0, raw.length); - for (int i = 0; i < bin.length; i++) - bin[i] = NB.decodeInt32(raw, i << 2); - - names[k] = bin; - offset32[k] = new byte[(int) (bucketCnt * 4)]; - crc32[k] = new byte[(int) (bucketCnt * 4)]; - } - - // CRC32 table. - for (int k = 0; k < FANOUT; k++) - IO.readFully(fd, crc32[k], 0, crc32[k].length); - - // 32 bit offset table. Any entries with the most significant bit - // set require a 64 bit offset entry in another table. - // - int o64cnt = 0; - for (int k = 0; k < FANOUT; k++) { - final byte[] ofs = offset32[k]; - IO.readFully(fd, ofs, 0, ofs.length); - for (int p = 0; p < ofs.length; p += 4) - if (ofs[p] < 0) - o64cnt++; - } - - // 64 bit offset table. Most objects should not require an entry. - // - if (o64cnt > 0) { - offset64 = new byte[o64cnt * 8]; - IO.readFully(fd, offset64, 0, offset64.length); - } else { - offset64 = NO_BYTES; - } - - packChecksum = new byte[20]; - IO.readFully(fd, packChecksum, 0, packChecksum.length); - } - - @Override - long getObjectCount() { - return objectCnt; - } - - @Override - long getOffset64Count() { - return offset64.length / 8; - } - - @Override - ObjectId getObjectId(final long nthPosition) { - int levelOne = Arrays.binarySearch(fanoutTable, nthPosition + 1); - long base; - if (levelOne >= 0) { - // If we hit the bucket exactly the item is in the bucket, or - // any bucket before it which has the same object count. - // - base = fanoutTable[levelOne]; - while (levelOne > 0 && base == fanoutTable[levelOne - 1]) - levelOne--; - } else { - // The item is in the bucket we would insert it into. - // - levelOne = -(levelOne + 1); - } - - base = levelOne > 0 ? fanoutTable[levelOne - 1] : 0; - final int p = (int) (nthPosition - base); - final int p4 = p << 2; - return ObjectId.fromRaw(names[levelOne], p4 + p); // p * 5 - } - - @Override - long findOffset(final AnyObjectId objId) { - final int levelOne = objId.getFirstByte(); - final int levelTwo = binarySearchLevelTwo(objId, levelOne); - if (levelTwo == -1) - return -1; - final long p = NB.decodeUInt32(offset32[levelOne], levelTwo << 2); - if ((p & IS_O64) != 0) - return NB.decodeUInt64(offset64, (8 * (int) (p & ~IS_O64))); - return p; - } - - @Override - long findCRC32(AnyObjectId objId) throws MissingObjectException { - final int levelOne = objId.getFirstByte(); - final int levelTwo = binarySearchLevelTwo(objId, levelOne); - if (levelTwo == -1) - throw new MissingObjectException(objId.copy(), "unknown"); - return NB.decodeUInt32(crc32[levelOne], levelTwo << 2); - } - - @Override - boolean hasCRC32Support() { - return true; - } - - public Iterator iterator() { - return new EntriesIteratorV2(); - } - - private int binarySearchLevelTwo(final AnyObjectId objId, final int levelOne) { - final int[] data = names[levelOne]; - int high = offset32[levelOne].length >>> 2; - if (high == 0) - return -1; - int low = 0; - do { - final int mid = (low + high) >>> 1; - final int mid4 = mid << 2; - final int cmp; - - cmp = objId.compareTo(data, mid4 + mid); // mid * 5 - if (cmp < 0) - high = mid; - else if (cmp == 0) { - return mid; - } else - low = mid + 1; - } while (low < high); - return -1; - } - - private class EntriesIteratorV2 extends EntriesIterator { - private int levelOne; - - private int levelTwo; - - @Override - protected MutableEntry initEntry() { - return new MutableEntry() { - protected void ensureId() { - idBuffer.fromRaw(names[levelOne], levelTwo - - Constants.OBJECT_ID_LENGTH / 4); - } - }; - } - - public MutableEntry next() { - for (; levelOne < names.length; levelOne++) { - if (levelTwo < names[levelOne].length) { - int idx = levelTwo / (Constants.OBJECT_ID_LENGTH / 4) * 4; - long offset = NB.decodeUInt32(offset32[levelOne], idx); - if ((offset & IS_O64) != 0) { - idx = (8 * (int) (offset & ~IS_O64)); - offset = NB.decodeUInt64(offset64, idx); - } - entry.offset = offset; - - levelTwo += Constants.OBJECT_ID_LENGTH / 4; - returnedNumber++; - return entry; - } - levelTwo = 0; - } - throw new NoSuchElementException(); - } - } - -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java deleted file mode 100644 index 4d2714bc55..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (C) 2008, Robin Rosenberg - * Copyright (C) 2008, Shawn O. Pearce - * 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.lib; - -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.security.DigestOutputStream; -import java.text.MessageFormat; -import java.util.List; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.transport.PackedObjectInfo; -import org.eclipse.jgit.util.NB; - -/** - * Creates a table of contents to support random access by {@link PackFile}. - *

- * Pack index files (the .idx suffix in a pack file pair) - * provides random access to any object in the pack by associating an ObjectId - * to the byte offset within the pack where the object's data can be read. - */ -public abstract class PackIndexWriter { - /** Magic constant indicating post-version 1 format. */ - protected static final byte[] TOC = { -1, 't', 'O', 'c' }; - - /** - * Create a new writer for the oldest (most widely understood) format. - *

- * This method selects an index format that can accurate describe the - * supplied objects and that will be the most compatible format with older - * Git implementations. - *

- * Index version 1 is widely recognized by all Git implementations, but - * index version 2 (and later) is not as well recognized as it was - * introduced more than a year later. Index version 1 can only be used if - * the resulting pack file is under 4 gigabytes in size; packs larger than - * that limit must use index version 2. - * - * @param dst - * the stream the index data will be written to. If not already - * buffered it will be automatically wrapped in a buffered - * stream. Callers are always responsible for closing the stream. - * @param objs - * the objects the caller needs to store in the index. Entries - * will be examined until a format can be conclusively selected. - * @return a new writer to output an index file of the requested format to - * the supplied stream. - * @throws IllegalArgumentException - * no recognized pack index version can support the supplied - * objects. This is likely a bug in the implementation. - */ - @SuppressWarnings("fallthrough") - public static PackIndexWriter createOldestPossible(final OutputStream dst, - final List objs) { - int version = 1; - LOOP: for (final PackedObjectInfo oe : objs) { - switch (version) { - case 1: - if (PackIndexWriterV1.canStore(oe)) - continue; - version = 2; - case 2: - break LOOP; - } - } - return createVersion(dst, version); - } - - /** - * Create a new writer instance for a specific index format version. - * - * @param dst - * the stream the index data will be written to. If not already - * buffered it will be automatically wrapped in a buffered - * stream. Callers are always responsible for closing the stream. - * @param version - * index format version number required by the caller. Exactly - * this formatted version will be written. - * @return a new writer to output an index file of the requested format to - * the supplied stream. - * @throws IllegalArgumentException - * the version requested is not supported by this - * implementation. - */ - public static PackIndexWriter createVersion(final OutputStream dst, - final int version) { - switch (version) { - case 1: - return new PackIndexWriterV1(dst); - case 2: - return new PackIndexWriterV2(dst); - default: - throw new IllegalArgumentException(MessageFormat.format( - JGitText.get().unsupportedPackIndexVersion, version)); - } - } - - /** The index data stream we are responsible for creating. */ - protected final DigestOutputStream out; - - /** A temporary buffer for use during IO to {link #out}. */ - protected final byte[] tmp; - - /** The entries this writer must pack. */ - protected List entries; - - /** SHA-1 checksum for the entire pack data. */ - protected byte[] packChecksum; - - /** - * Create a new writer instance. - * - * @param dst - * the stream this instance outputs to. If not already buffered - * it will be automatically wrapped in a buffered stream. - */ - protected PackIndexWriter(final OutputStream dst) { - out = new DigestOutputStream(dst instanceof BufferedOutputStream ? dst - : new BufferedOutputStream(dst), Constants.newMessageDigest()); - tmp = new byte[4 + Constants.OBJECT_ID_LENGTH]; - } - - /** - * Write all object entries to the index stream. - *

- * After writing the stream passed to the factory is flushed but remains - * open. Callers are always responsible for closing the output stream. - * - * @param toStore - * sorted list of objects to store in the index. The caller must - * have previously sorted the list using {@link PackedObjectInfo}'s - * native {@link Comparable} implementation. - * @param packDataChecksum - * checksum signature of the entire pack data content. This is - * traditionally the last 20 bytes of the pack file's own stream. - * @throws IOException - * an error occurred while writing to the output stream, or this - * index format cannot store the object data supplied. - */ - public void write(final List toStore, - final byte[] packDataChecksum) throws IOException { - entries = toStore; - packChecksum = packDataChecksum; - writeImpl(); - out.flush(); - } - - /** - * Writes the index file to {@link #out}. - *

- * Implementations should go something like: - * - *

-	 * writeFanOutTable();
-	 * for (final PackedObjectInfo po : entries)
-	 * 	writeOneEntry(po);
-	 * writeChecksumFooter();
-	 * 
- * - *

- * Where the logic for writeOneEntry is specific to the index - * format in use. Additional headers/footers may be used if necessary and - * the {@link #entries} collection may be iterated over more than once if - * necessary. Implementors therefore have complete control over the data. - * - * @throws IOException - * an error occurred while writing to the output stream, or this - * index format cannot store the object data supplied. - */ - protected abstract void writeImpl() throws IOException; - - /** - * Output the version 2 (and later) TOC header, with version number. - *

- * Post version 1 all index files start with a TOC header that makes the - * file an invalid version 1 file, and then includes the version number. - * This header is necessary to recognize a version 1 from a version 2 - * formatted index. - * - * @param version - * version number of this index format being written. - * @throws IOException - * an error occurred while writing to the output stream. - */ - protected void writeTOC(final int version) throws IOException { - out.write(TOC); - NB.encodeInt32(tmp, 0, version); - out.write(tmp, 0, 4); - } - - /** - * Output the standard 256 entry first-level fan-out table. - *

- * The fan-out table is 4 KB in size, holding 256 32-bit unsigned integer - * counts. Each count represents the number of objects within this index - * whose {@link ObjectId#getFirstByte()} matches the count's position in the - * fan-out table. - * - * @throws IOException - * an error occurred while writing to the output stream. - */ - protected void writeFanOutTable() throws IOException { - final int[] fanout = new int[256]; - for (final PackedObjectInfo po : entries) - fanout[po.getFirstByte() & 0xff]++; - for (int i = 1; i < 256; i++) - fanout[i] += fanout[i - 1]; - for (final int n : fanout) { - NB.encodeInt32(tmp, 0, n); - out.write(tmp, 0, 4); - } - } - - /** - * Output the standard two-checksum index footer. - *

- * The standard footer contains two checksums (20 byte SHA-1 values): - *

    - *
  1. Pack data checksum - taken from the last 20 bytes of the pack file.
  2. - *
  3. Index data checksum - checksum of all index bytes written, including - * the pack data checksum above.
  4. - *
- * - * @throws IOException - * an error occurred while writing to the output stream. - */ - protected void writeChecksumFooter() throws IOException { - out.write(packChecksum); - out.on(false); - out.write(out.getMessageDigest().digest()); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java deleted file mode 100644 index eb44b3a8c7..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2008, Robin Rosenberg - * Copyright (C) 2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.io.OutputStream; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.transport.PackedObjectInfo; -import org.eclipse.jgit.util.NB; - -/** - * Creates the version 1 (old style) pack table of contents files. - * - * @see PackIndexWriter - * @see PackIndexV1 - */ -class PackIndexWriterV1 extends PackIndexWriter { - static boolean canStore(final PackedObjectInfo oe) { - // We are limited to 4 GB per pack as offset is 32 bit unsigned int. - // - return oe.getOffset() >>> 1 < Integer.MAX_VALUE; - } - - PackIndexWriterV1(final OutputStream dst) { - super(dst); - } - - @Override - protected void writeImpl() throws IOException { - writeFanOutTable(); - - for (final PackedObjectInfo oe : entries) { - if (!canStore(oe)) - throw new IOException(JGitText.get().packTooLargeForIndexVersion1); - NB.encodeInt32(tmp, 0, (int) oe.getOffset()); - oe.copyRawTo(tmp, 4); - out.write(tmp); - } - - writeChecksumFooter(); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java deleted file mode 100644 index b6ac7b89e3..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.io.OutputStream; - -import org.eclipse.jgit.transport.PackedObjectInfo; -import org.eclipse.jgit.util.NB; - -/** - * Creates the version 2 pack table of contents files. - * - * @see PackIndexWriter - * @see PackIndexV2 - */ -class PackIndexWriterV2 extends PackIndexWriter { - PackIndexWriterV2(final OutputStream dst) { - super(dst); - } - - @Override - protected void writeImpl() throws IOException { - writeTOC(2); - writeFanOutTable(); - writeObjectNames(); - writeCRCs(); - writeOffset32(); - writeOffset64(); - writeChecksumFooter(); - } - - private void writeObjectNames() throws IOException { - for (final PackedObjectInfo oe : entries) - oe.copyRawTo(out); - } - - private void writeCRCs() throws IOException { - for (final PackedObjectInfo oe : entries) { - NB.encodeInt32(tmp, 0, oe.getCRC()); - out.write(tmp, 0, 4); - } - } - - private void writeOffset32() throws IOException { - int o64 = 0; - for (final PackedObjectInfo oe : entries) { - final long o = oe.getOffset(); - if (o < Integer.MAX_VALUE) - NB.encodeInt32(tmp, 0, (int) o); - else - NB.encodeInt32(tmp, 0, (1 << 31) | o64++); - out.write(tmp, 0, 4); - } - } - - private void writeOffset64() throws IOException { - for (final PackedObjectInfo oe : entries) { - final long o = oe.getOffset(); - if (o > Integer.MAX_VALUE) { - NB.encodeInt64(tmp, 0, o); - out.write(tmp, 0, 8); - } - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java deleted file mode 100644 index de8e3fa637..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2009, Google Inc. - * 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.lib; - -import java.io.File; -import java.io.IOException; - -/** Keeps track of a {@link PackFile}'s associated .keep file. */ -public class PackLock { - private final File keepFile; - - /** - * Create a new lock for a pack file. - * - * @param packFile - * location of the pack-*.pack file. - */ - public PackLock(final File packFile) { - final File p = packFile.getParentFile(); - final String n = packFile.getName(); - keepFile = new File(p, n.substring(0, n.length() - 5) + ".keep"); - } - - /** - * Create the pack-*.keep file, with the given message. - * - * @param msg - * message to store in the file. - * @return true if the keep file was successfully written; false otherwise. - * @throws IOException - * the keep file could not be written. - */ - public boolean lock(String msg) throws IOException { - if (msg == null) - return false; - if (!msg.endsWith("\n")) - msg += "\n"; - final LockFile lf = new LockFile(keepFile); - if (!lf.lock()) - return false; - lf.write(Constants.encode(msg)); - return lf.commit(); - } - - /** Remove the .keep file that holds this pack in place. */ - public void unlock() { - keepFile.delete(); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java deleted file mode 100644 index f4f57aed43..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2008, Marek Zawirski - * 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.lib; - -import java.text.MessageFormat; -import java.util.Arrays; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.lib.PackIndex.MutableEntry; - -/** - *

- * Reverse index for forward pack index. Provides operations based on offset - * instead of object id. Such offset-based reverse lookups are performed in - * O(log n) time. - *

- * - * @see PackIndex - * @see PackFile - */ -class PackReverseIndex { - /** Index we were created from, and that has our ObjectId data. */ - private final PackIndex index; - - /** - * (offset31, truly) Offsets accommodating in 31 bits. - */ - private final int offsets32[]; - - /** - * Offsets not accommodating in 31 bits. - */ - private final long offsets64[]; - - /** Position of the corresponding {@link #offsets32} in {@link #index}. */ - private final int nth32[]; - - /** Position of the corresponding {@link #offsets64} in {@link #index}. */ - private final int nth64[]; - - /** - * Create reverse index from straight/forward pack index, by indexing all - * its entries. - * - * @param packIndex - * forward index - entries to (reverse) index. - */ - PackReverseIndex(final PackIndex packIndex) { - index = packIndex; - - final long cnt = index.getObjectCount(); - final long n64 = index.getOffset64Count(); - final long n32 = cnt - n64; - if (n32 > Integer.MAX_VALUE || n64 > Integer.MAX_VALUE - || cnt > 0xffffffffL) - throw new IllegalArgumentException( - JGitText.get().hugeIndexesAreNotSupportedByJgitYet); - - offsets32 = new int[(int) n32]; - offsets64 = new long[(int) n64]; - nth32 = new int[offsets32.length]; - nth64 = new int[offsets64.length]; - - int i32 = 0; - int i64 = 0; - for (final MutableEntry me : index) { - final long o = me.getOffset(); - if (o < Integer.MAX_VALUE) - offsets32[i32++] = (int) o; - else - offsets64[i64++] = o; - } - - Arrays.sort(offsets32); - Arrays.sort(offsets64); - - int nth = 0; - for (final MutableEntry me : index) { - final long o = me.getOffset(); - if (o < Integer.MAX_VALUE) - nth32[Arrays.binarySearch(offsets32, (int) o)] = nth++; - else - nth64[Arrays.binarySearch(offsets64, o)] = nth++; - } - } - - /** - * Search for object id with the specified start offset in this pack - * (reverse) index. - * - * @param offset - * start offset of object to find. - * @return object id for this offset, or null if no object was found. - */ - ObjectId findObject(final long offset) { - if (offset <= Integer.MAX_VALUE) { - final int i32 = Arrays.binarySearch(offsets32, (int) offset); - if (i32 < 0) - return null; - return index.getObjectId(nth32[i32]); - } else { - final int i64 = Arrays.binarySearch(offsets64, offset); - if (i64 < 0) - return null; - return index.getObjectId(nth64[i64]); - } - } - - /** - * Search for the next offset to the specified offset in this pack (reverse) - * index. - * - * @param offset - * start offset of previous object (must be valid-existing - * offset). - * @param maxOffset - * maximum offset in a pack (returned when there is no next - * offset). - * @return offset of the next object in a pack or maxOffset if provided - * offset was the last one. - * @throws CorruptObjectException - * when there is no object with the provided offset. - */ - long findNextOffset(final long offset, final long maxOffset) - throws CorruptObjectException { - if (offset <= Integer.MAX_VALUE) { - final int i32 = Arrays.binarySearch(offsets32, (int) offset); - if (i32 < 0) - throw new CorruptObjectException(MessageFormat.format( - JGitText.get().cantFindObjectInReversePackIndexForTheSpecifiedOffset - , offset)); - - if (i32 + 1 == offsets32.length) { - if (offsets64.length > 0) - return offsets64[0]; - return maxOffset; - } - return offsets32[i32 + 1]; - } else { - final int i64 = Arrays.binarySearch(offsets64, offset); - if (i64 < 0) - throw new CorruptObjectException(MessageFormat.format( - JGitText.get().cantFindObjectInReversePackIndexForTheSpecifiedOffset - , offset)); - - if (i64 + 1 == offsets64.length) - return maxOffset; - return offsets64[i64 + 1]; - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java index 80d8fff536..a6f6b25078 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java @@ -66,6 +66,7 @@ import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevSort; +import org.eclipse.jgit.storage.file.PackIndexWriter; /** *

diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java deleted file mode 100644 index 47f5e67a73..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; - -/** - * Base class for a set of object loader classes for packed objects. - */ -abstract class PackedObjectLoader extends ObjectLoader { - protected final PackFile pack; - - /** Position of the first byte of the object's header. */ - protected final long objectOffset; - - /** Bytes used to express the object header, including delta reference. */ - protected final int headerSize; - - protected int objectType; - - protected int objectSize; - - protected byte[] cachedBytes; - - PackedObjectLoader(final PackFile pr, final long objectOffset, - final int headerSize) { - pack = pr; - this.objectOffset = objectOffset; - this.headerSize = headerSize; - } - - /** - * Force this object to be loaded into memory and pinned in this loader. - *

- * Once materialized, subsequent get operations for the following methods - * will always succeed without raising an exception, as all information is - * pinned in memory by this loader instance. - *

    - *
  • {@link #getType()}
  • - *
  • {@link #getSize()}
  • - *
  • {@link #getBytes()}, {@link #getCachedBytes}
  • - *
  • {@link #getRawSize()}
  • - *
  • {@link #getRawType()}
  • - *
- * - * @param curs - * temporary thread storage during data access. - * @throws IOException - * the object cannot be read. - */ - public abstract void materialize(WindowCursor curs) throws IOException; - - public final int getType() { - return objectType; - } - - public final long getSize() { - return objectSize; - } - - @Override - public final byte[] getCachedBytes() { - return cachedBytes; - } - - /** - * @return offset of object header within pack file - */ - public final long getObjectOffset() { - return objectOffset; - } - - /** - * @return id of delta base object for this object representation. null if - * object is not stored as delta. - * @throws IOException - * when delta base cannot read. - */ - public abstract ObjectId getDeltaBase() throws IOException; -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java deleted file mode 100644 index 13e9c22d98..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java +++ /dev/null @@ -1,1006 +0,0 @@ -/* - * Copyright (C) 2007, Dave Watson - * Copyright (C) 2009-2010, Google Inc. - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006, Shawn O. Pearce - * 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.lib; - -import static org.eclipse.jgit.lib.Constants.CHARSET; -import static org.eclipse.jgit.lib.Constants.HEAD; -import static org.eclipse.jgit.lib.Constants.LOGS; -import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH; -import static org.eclipse.jgit.lib.Constants.PACKED_REFS; -import static org.eclipse.jgit.lib.Constants.R_HEADS; -import static org.eclipse.jgit.lib.Constants.R_REFS; -import static org.eclipse.jgit.lib.Constants.R_REMOTES; -import static org.eclipse.jgit.lib.Constants.R_TAGS; -import static org.eclipse.jgit.lib.Constants.encode; -import static org.eclipse.jgit.lib.Ref.Storage.LOOSE; -import static org.eclipse.jgit.lib.Ref.Storage.NEW; -import static org.eclipse.jgit.lib.Ref.Storage.PACKED; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.text.MessageFormat; -import java.util.Arrays; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.ObjectWritingException; -import org.eclipse.jgit.events.RefsChangedEvent; -import org.eclipse.jgit.revwalk.RevObject; -import org.eclipse.jgit.revwalk.RevTag; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.util.FS; -import org.eclipse.jgit.util.IO; -import org.eclipse.jgit.util.RawParseUtils; -import org.eclipse.jgit.util.RefList; -import org.eclipse.jgit.util.RefMap; - -/** - * Traditional file system based {@link RefDatabase}. - *

- * This is the classical reference database representation for a Git repository. - * References are stored in two formats: loose, and packed. - *

- * Loose references are stored as individual files within the {@code refs/} - * directory. The file name matches the reference name and the file contents is - * the current {@link ObjectId} in string form. - *

- * Packed references are stored in a single text file named {@code packed-refs}. - * In the packed format, each reference is stored on its own line. This file - * reduces the number of files needed for large reference spaces, reducing the - * overall size of a Git repository on disk. - */ -public class RefDirectory extends RefDatabase { - /** Magic string denoting the start of a symbolic reference file. */ - public static final String SYMREF = "ref: "; //$NON-NLS-1$ - - /** Magic string denoting the header of a packed-refs file. */ - public static final String PACKED_REFS_HEADER = "# pack-refs with:"; //$NON-NLS-1$ - - /** If in the header, denotes the file has peeled data. */ - public static final String PACKED_REFS_PEELED = " peeled"; //$NON-NLS-1$ - - private final FileRepository parent; - - private final File gitDir; - - private final File refsDir; - - private final File logsDir; - - private final File logsRefsDir; - - private final File packedRefsFile; - - /** - * Immutable sorted list of loose references. - *

- * Symbolic references in this collection are stored unresolved, that is - * their target appears to be a new reference with no ObjectId. These are - * converted into resolved references during a get operation, ensuring the - * live value is always returned. - */ - private final AtomicReference> looseRefs = new AtomicReference>(); - - /** Immutable sorted list of packed references. */ - private final AtomicReference packedRefs = new AtomicReference(); - - /** - * Number of modifications made to this database. - *

- * This counter is incremented when a change is made, or detected from the - * filesystem during a read operation. - */ - private final AtomicInteger modCnt = new AtomicInteger(); - - /** - * Last {@link #modCnt} that we sent to listeners. - *

- * This value is compared to {@link #modCnt}, and a notification is sent to - * the listeners only when it differs. - */ - private final AtomicInteger lastNotifiedModCnt = new AtomicInteger(); - - RefDirectory(final FileRepository db) { - final FS fs = db.getFS(); - parent = db; - gitDir = db.getDirectory(); - refsDir = fs.resolve(gitDir, R_REFS); - logsDir = fs.resolve(gitDir, LOGS); - logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS); - packedRefsFile = fs.resolve(gitDir, PACKED_REFS); - - looseRefs.set(RefList. emptyList()); - packedRefs.set(PackedRefList.NO_PACKED_REFS); - } - - Repository getRepository() { - return parent; - } - - public void create() throws IOException { - refsDir.mkdir(); - logsDir.mkdir(); - logsRefsDir.mkdir(); - - new File(refsDir, R_HEADS.substring(R_REFS.length())).mkdir(); - new File(refsDir, R_TAGS.substring(R_REFS.length())).mkdir(); - new File(logsRefsDir, R_HEADS.substring(R_REFS.length())).mkdir(); - } - - @Override - public void close() { - // We have no resources to close. - } - - void rescan() { - looseRefs.set(RefList. emptyList()); - packedRefs.set(PackedRefList.NO_PACKED_REFS); - } - - @Override - public boolean isNameConflicting(String name) throws IOException { - RefList packed = getPackedRefs(); - RefList loose = getLooseRefs(); - - // Cannot be nested within an existing reference. - int lastSlash = name.lastIndexOf('/'); - while (0 < lastSlash) { - String needle = name.substring(0, lastSlash); - if (loose.contains(needle) || packed.contains(needle)) - return true; - lastSlash = name.lastIndexOf('/', lastSlash - 1); - } - - // Cannot be the container of an existing reference. - String prefix = name + '/'; - int idx; - - idx = -(packed.find(prefix) + 1); - if (idx < packed.size() && packed.get(idx).getName().startsWith(prefix)) - return true; - - idx = -(loose.find(prefix) + 1); - if (idx < loose.size() && loose.get(idx).getName().startsWith(prefix)) - return true; - - return false; - } - - private RefList getLooseRefs() { - final RefList oldLoose = looseRefs.get(); - - LooseScanner scan = new LooseScanner(oldLoose); - scan.scan(ALL); - - RefList loose; - if (scan.newLoose != null) { - loose = scan.newLoose.toRefList(); - if (looseRefs.compareAndSet(oldLoose, loose)) - modCnt.incrementAndGet(); - } else - loose = oldLoose; - return loose; - } - - @Override - public Ref getRef(final String needle) throws IOException { - final RefList packed = getPackedRefs(); - Ref ref = null; - for (String prefix : SEARCH_PATH) { - ref = readRef(prefix + needle, packed); - if (ref != null) { - ref = resolve(ref, 0, null, null, packed); - break; - } - } - fireRefsChanged(); - return ref; - } - - @Override - public Map getRefs(String prefix) throws IOException { - final RefList packed = getPackedRefs(); - final RefList oldLoose = looseRefs.get(); - - LooseScanner scan = new LooseScanner(oldLoose); - scan.scan(prefix); - - RefList loose; - if (scan.newLoose != null) { - loose = scan.newLoose.toRefList(); - if (looseRefs.compareAndSet(oldLoose, loose)) - modCnt.incrementAndGet(); - } else - loose = oldLoose; - fireRefsChanged(); - - RefList.Builder symbolic = scan.symbolic; - for (int idx = 0; idx < symbolic.size();) { - Ref ref = symbolic.get(idx); - ref = resolve(ref, 0, prefix, loose, packed); - if (ref != null && ref.getObjectId() != null) { - symbolic.set(idx, ref); - idx++; - } else { - // A broken symbolic reference, we have to drop it from the - // collections the client is about to receive. Should be a - // rare occurrence so pay a copy penalty. - loose = loose.remove(idx); - symbolic.remove(idx); - } - } - - return new RefMap(prefix, packed, upcast(loose), symbolic.toRefList()); - } - - @SuppressWarnings("unchecked") - private RefList upcast(RefList loose) { - return (RefList) loose; - } - - private class LooseScanner { - private final RefList curLoose; - - private int curIdx; - - final RefList.Builder symbolic = new RefList.Builder(4); - - RefList.Builder newLoose; - - LooseScanner(final RefList curLoose) { - this.curLoose = curLoose; - } - - void scan(String prefix) { - if (ALL.equals(prefix)) { - scanOne(HEAD); - scanTree(R_REFS, refsDir); - - // If any entries remain, they are deleted, drop them. - if (newLoose == null && curIdx < curLoose.size()) - newLoose = curLoose.copy(curIdx); - - } else if (prefix.startsWith(R_REFS) && prefix.endsWith("/")) { - curIdx = -(curLoose.find(prefix) + 1); - File dir = new File(refsDir, prefix.substring(R_REFS.length())); - scanTree(prefix, dir); - - // Skip over entries still within the prefix; these have - // been removed from the directory. - while (curIdx < curLoose.size()) { - if (!curLoose.get(curIdx).getName().startsWith(prefix)) - break; - if (newLoose == null) - newLoose = curLoose.copy(curIdx); - curIdx++; - } - - // Keep any entries outside of the prefix space, we - // do not know anything about their status. - if (newLoose != null) { - while (curIdx < curLoose.size()) - newLoose.add(curLoose.get(curIdx++)); - } - } - } - - private boolean scanTree(String prefix, File dir) { - final String[] entries = dir.list(LockFile.FILTER); - if (entries == null) // not a directory or an I/O error - return false; - if (0 < entries.length) { - Arrays.sort(entries); - for (String name : entries) { - File e = new File(dir, name); - if (!scanTree(prefix + name + '/', e)) - scanOne(prefix + name); - } - } - return true; - } - - private void scanOne(String name) { - LooseRef cur; - - if (curIdx < curLoose.size()) { - do { - cur = curLoose.get(curIdx); - int cmp = RefComparator.compareTo(cur, name); - if (cmp < 0) { - // Reference is not loose anymore, its been deleted. - // Skip the name in the new result list. - if (newLoose == null) - newLoose = curLoose.copy(curIdx); - curIdx++; - cur = null; - continue; - } - - if (cmp > 0) // Newly discovered loose reference. - cur = null; - break; - } while (curIdx < curLoose.size()); - } else - cur = null; // Newly discovered loose reference. - - LooseRef n; - try { - n = scanRef(cur, name); - } catch (IOException notValid) { - n = null; - } - - if (n != null) { - if (cur != n && newLoose == null) - newLoose = curLoose.copy(curIdx); - if (newLoose != null) - newLoose.add(n); - if (n.isSymbolic()) - symbolic.add(n); - } else if (cur != null) { - // Tragically, this file is no longer a loose reference. - // Kill our cached entry of it. - if (newLoose == null) - newLoose = curLoose.copy(curIdx); - } - - if (cur != null) - curIdx++; - } - } - - @Override - public Ref peel(final Ref ref) throws IOException { - final Ref leaf = ref.getLeaf(); - if (leaf.isPeeled() || leaf.getObjectId() == null) - return ref; - - RevWalk rw = new RevWalk(getRepository()); - RevObject obj = rw.parseAny(leaf.getObjectId()); - ObjectIdRef newLeaf; - if (obj instanceof RevTag) { - newLeaf = new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf - .getName(), leaf.getObjectId(), rw.peel(obj).copy()); - } else { - newLeaf = new ObjectIdRef.PeeledNonTag(leaf.getStorage(), leaf - .getName(), leaf.getObjectId()); - } - - // Try to remember this peeling in the cache, so we don't have to do - // it again in the future, but only if the reference is unchanged. - if (leaf.getStorage().isLoose()) { - RefList curList = looseRefs.get(); - int idx = curList.find(leaf.getName()); - if (0 <= idx && curList.get(idx) == leaf) { - LooseRef asPeeled = ((LooseRef) leaf).peel(newLeaf); - RefList newList = curList.set(idx, asPeeled); - looseRefs.compareAndSet(curList, newList); - } - } - - return recreate(ref, newLeaf); - } - - private static Ref recreate(final Ref old, final ObjectIdRef leaf) { - if (old.isSymbolic()) { - Ref dst = recreate(old.getTarget(), leaf); - return new SymbolicRef(old.getName(), dst); - } - return leaf; - } - - void storedSymbolicRef(RefDirectoryUpdate u, long modified, String target) { - putLooseRef(newSymbolicRef(modified, u.getRef().getName(), target)); - fireRefsChanged(); - } - - public RefDirectoryUpdate newUpdate(String name, boolean detach) - throws IOException { - final RefList packed = getPackedRefs(); - Ref ref = readRef(name, packed); - if (ref != null) - ref = resolve(ref, 0, null, null, packed); - if (ref == null) - ref = new ObjectIdRef.Unpeeled(NEW, name, null); - else if (detach && ref.isSymbolic()) - ref = new ObjectIdRef.Unpeeled(LOOSE, name, ref.getObjectId()); - return new RefDirectoryUpdate(this, ref); - } - - @Override - public RefDirectoryRename newRename(String fromName, String toName) - throws IOException { - RefDirectoryUpdate from = newUpdate(fromName, false); - RefDirectoryUpdate to = newUpdate(toName, false); - return new RefDirectoryRename(from, to); - } - - void stored(RefDirectoryUpdate update, long modified) { - final ObjectId target = update.getNewObjectId().copy(); - final Ref leaf = update.getRef().getLeaf(); - putLooseRef(new LooseUnpeeled(modified, leaf.getName(), target)); - } - - private void putLooseRef(LooseRef ref) { - RefList cList, nList; - do { - cList = looseRefs.get(); - nList = cList.put(ref); - } while (!looseRefs.compareAndSet(cList, nList)); - modCnt.incrementAndGet(); - fireRefsChanged(); - } - - void delete(RefDirectoryUpdate update) throws IOException { - Ref dst = update.getRef().getLeaf(); - String name = dst.getName(); - - // Write the packed-refs file using an atomic update. We might - // wind up reading it twice, before and after the lock, to ensure - // we don't miss an edit made externally. - final PackedRefList packed = getPackedRefs(); - if (packed.contains(name)) { - LockFile lck = new LockFile(packedRefsFile); - if (!lck.lock()) - throw new IOException(MessageFormat.format( - JGitText.get().cannotLockFile, packedRefsFile)); - try { - PackedRefList cur = readPackedRefs(0, 0); - int idx = cur.find(name); - if (0 <= idx) - commitPackedRefs(lck, cur.remove(idx), packed); - } finally { - lck.unlock(); - } - } - - RefList curLoose, newLoose; - do { - curLoose = looseRefs.get(); - int idx = curLoose.find(name); - if (idx < 0) - break; - newLoose = curLoose.remove(idx); - } while (!looseRefs.compareAndSet(curLoose, newLoose)); - - int levels = levelsIn(name) - 2; - delete(logFor(name), levels); - if (dst.getStorage().isLoose()) { - update.unlock(); - delete(fileFor(name), levels); - } - - modCnt.incrementAndGet(); - fireRefsChanged(); - } - - void log(final RefUpdate update, final String msg, final boolean deref) - throws IOException { - final ObjectId oldId = update.getOldObjectId(); - final ObjectId newId = update.getNewObjectId(); - final Ref ref = update.getRef(); - - PersonIdent ident = update.getRefLogIdent(); - if (ident == null) - ident = new PersonIdent(parent); - else - ident = new PersonIdent(ident); - - final StringBuilder r = new StringBuilder(); - r.append(ObjectId.toString(oldId)); - r.append(' '); - r.append(ObjectId.toString(newId)); - r.append(' '); - r.append(ident.toExternalString()); - r.append('\t'); - r.append(msg); - r.append('\n'); - final byte[] rec = encode(r.toString()); - - if (deref && ref.isSymbolic()) { - log(ref.getName(), rec); - log(ref.getLeaf().getName(), rec); - } else { - log(ref.getName(), rec); - } - } - - private void log(final String refName, final byte[] rec) throws IOException { - final File log = logFor(refName); - final boolean write; - if (isLogAllRefUpdates() && shouldAutoCreateLog(refName)) - write = true; - else if (log.isFile()) - write = true; - else - write = false; - - if (write) { - FileOutputStream out; - try { - out = new FileOutputStream(log, true); - } catch (FileNotFoundException err) { - final File dir = log.getParentFile(); - if (dir.exists()) - throw err; - if (!dir.mkdirs() && !dir.isDirectory()) - throw new IOException(MessageFormat.format(JGitText.get().cannotCreateDirectory, dir)); - out = new FileOutputStream(log, true); - } - try { - out.write(rec); - } finally { - out.close(); - } - } - } - - private boolean isLogAllRefUpdates() { - return parent.getConfig().get(CoreConfig.KEY).isLogAllRefUpdates(); - } - - private boolean shouldAutoCreateLog(final String refName) { - return refName.equals(HEAD) // - || refName.startsWith(R_HEADS) // - || refName.startsWith(R_REMOTES); - } - - private Ref resolve(final Ref ref, int depth, String prefix, - RefList loose, RefList packed) throws IOException { - if (ref.isSymbolic()) { - Ref dst = ref.getTarget(); - - if (MAX_SYMBOLIC_REF_DEPTH <= depth) - return null; // claim it doesn't exist - - // If the cached value can be assumed to be current due to a - // recent scan of the loose directory, use it. - if (loose != null && dst.getName().startsWith(prefix)) { - int idx; - if (0 <= (idx = loose.find(dst.getName()))) - dst = loose.get(idx); - else if (0 <= (idx = packed.find(dst.getName()))) - dst = packed.get(idx); - else - return ref; - } else { - dst = readRef(dst.getName(), packed); - if (dst == null) - return ref; - } - - dst = resolve(dst, depth + 1, prefix, loose, packed); - if (dst == null) - return null; - return new SymbolicRef(ref.getName(), dst); - } - return ref; - } - - private PackedRefList getPackedRefs() throws IOException { - long size = packedRefsFile.length(); - long mtime = size != 0 ? packedRefsFile.lastModified() : 0; - - final PackedRefList curList = packedRefs.get(); - if (size == curList.lastSize && mtime == curList.lastModified) - return curList; - - final PackedRefList newList = readPackedRefs(size, mtime); - if (packedRefs.compareAndSet(curList, newList)) - modCnt.incrementAndGet(); - return newList; - } - - private PackedRefList readPackedRefs(long size, long mtime) - throws IOException { - final BufferedReader br; - try { - br = new BufferedReader(new InputStreamReader(new FileInputStream( - packedRefsFile), CHARSET)); - } catch (FileNotFoundException noPackedRefs) { - // Ignore it and leave the new list empty. - return PackedRefList.NO_PACKED_REFS; - } - try { - return new PackedRefList(parsePackedRefs(br), size, mtime); - } finally { - br.close(); - } - } - - private RefList parsePackedRefs(final BufferedReader br) - throws IOException { - RefList.Builder all = new RefList.Builder(); - Ref last = null; - boolean peeled = false; - boolean needSort = false; - - String p; - while ((p = br.readLine()) != null) { - if (p.charAt(0) == '#') { - if (p.startsWith(PACKED_REFS_HEADER)) { - p = p.substring(PACKED_REFS_HEADER.length()); - peeled = p.contains(PACKED_REFS_PEELED); - } - continue; - } - - if (p.charAt(0) == '^') { - if (last == null) - throw new IOException(JGitText.get().peeledLineBeforeRef); - - ObjectId id = ObjectId.fromString(p.substring(1)); - last = new ObjectIdRef.PeeledTag(PACKED, last.getName(), last - .getObjectId(), id); - all.set(all.size() - 1, last); - continue; - } - - int sp = p.indexOf(' '); - ObjectId id = ObjectId.fromString(p.substring(0, sp)); - String name = copy(p, sp + 1, p.length()); - ObjectIdRef cur; - if (peeled) - cur = new ObjectIdRef.PeeledNonTag(PACKED, name, id); - else - cur = new ObjectIdRef.Unpeeled(PACKED, name, id); - if (last != null && RefComparator.compareTo(last, cur) > 0) - needSort = true; - all.add(cur); - last = cur; - } - - if (needSort) - all.sort(); - return all.toRefList(); - } - - private static String copy(final String src, final int off, final int end) { - // Don't use substring since it could leave a reference to the much - // larger existing string. Force construction of a full new object. - return new StringBuilder(end - off).append(src, off, end).toString(); - } - - private void commitPackedRefs(final LockFile lck, final RefList refs, - final PackedRefList oldPackedList) throws IOException { - new RefWriter(refs) { - @Override - protected void writeFile(String name, byte[] content) - throws IOException { - lck.setNeedStatInformation(true); - try { - lck.write(content); - } catch (IOException ioe) { - throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name), ioe); - } - try { - lck.waitForStatChange(); - } catch (InterruptedException e) { - lck.unlock(); - throw new ObjectWritingException(MessageFormat.format(JGitText.get().interruptedWriting, name)); - } - if (!lck.commit()) - throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name)); - - packedRefs.compareAndSet(oldPackedList, new PackedRefList(refs, - content.length, lck.getCommitLastModified())); - } - }.writePackedRefs(); - } - - private Ref readRef(String name, RefList packed) throws IOException { - final RefList curList = looseRefs.get(); - final int idx = curList.find(name); - if (0 <= idx) { - final LooseRef o = curList.get(idx); - final LooseRef n = scanRef(o, name); - if (n == null) { - if (looseRefs.compareAndSet(curList, curList.remove(idx))) - modCnt.incrementAndGet(); - return packed.get(name); - } - - if (o == n) - return n; - if (looseRefs.compareAndSet(curList, curList.set(idx, n))) - modCnt.incrementAndGet(); - return n; - } - - final LooseRef n = scanRef(null, name); - if (n == null) - return packed.get(name); - if (looseRefs.compareAndSet(curList, curList.add(idx, n))) - modCnt.incrementAndGet(); - return n; - } - - private LooseRef scanRef(LooseRef ref, String name) throws IOException { - final File path = fileFor(name); - final long modified = path.lastModified(); - - if (ref != null) { - if (ref.getLastModified() == modified) - return ref; - name = ref.getName(); - } else if (modified == 0) - return null; - - final byte[] buf; - try { - buf = IO.readFully(path, 4096); - } catch (FileNotFoundException noFile) { - return null; // doesn't exist; not a reference. - } - - int n = buf.length; - if (n == 0) - return null; // empty file; not a reference. - - if (isSymRef(buf, n)) { - // trim trailing whitespace - while (0 < n && Character.isWhitespace(buf[n - 1])) - n--; - if (n < 6) { - String content = RawParseUtils.decode(buf, 0, n); - throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content)); - } - final String target = RawParseUtils.decode(buf, 5, n); - return newSymbolicRef(modified, name, target); - } - - if (n < OBJECT_ID_STRING_LENGTH) - return null; // impossibly short object identifier; not a reference. - - final ObjectId id; - try { - id = ObjectId.fromString(buf, 0); - } catch (IllegalArgumentException notRef) { - while (0 < n && Character.isWhitespace(buf[n - 1])) - n--; - String content = RawParseUtils.decode(buf, 0, n); - throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content)); - } - return new LooseUnpeeled(modified, name, id); - } - - private static boolean isSymRef(final byte[] buf, int n) { - if (n < 6) - return false; - return /**/buf[0] == 'r' // - && buf[1] == 'e' // - && buf[2] == 'f' // - && buf[3] == ':' // - && buf[4] == ' '; - } - - /** If the parent should fire listeners, fires them. */ - private void fireRefsChanged() { - final int last = lastNotifiedModCnt.get(); - final int curr = modCnt.get(); - if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr)) - parent.fireEvent(new RefsChangedEvent()); - } - - /** - * Create a reference update to write a temporary reference. - * - * @return an update for a new temporary reference. - * @throws IOException - * a temporary name cannot be allocated. - */ - RefDirectoryUpdate newTemporaryUpdate() throws IOException { - File tmp = File.createTempFile("renamed_", "_ref", refsDir); - String name = Constants.R_REFS + tmp.getName(); - Ref ref = new ObjectIdRef.Unpeeled(NEW, name, null); - return new RefDirectoryUpdate(this, ref); - } - - /** - * Locate the file on disk for a single reference name. - * - * @param name - * name of the ref, relative to the Git repository top level - * directory (so typically starts with refs/). - * @return the loose file location. - */ - File fileFor(String name) { - if (name.startsWith(R_REFS)) { - name = name.substring(R_REFS.length()); - return new File(refsDir, name); - } - return new File(gitDir, name); - } - - /** - * Locate the log file on disk for a single reference name. - * - * @param name - * name of the ref, relative to the Git repository top level - * directory (so typically starts with refs/). - * @return the log file location. - */ - File logFor(String name) { - if (name.startsWith(R_REFS)) { - name = name.substring(R_REFS.length()); - return new File(logsRefsDir, name); - } - return new File(logsDir, name); - } - - static int levelsIn(final String name) { - int count = 0; - for (int p = name.indexOf('/'); p >= 0; p = name.indexOf('/', p + 1)) - count++; - return count; - } - - static void delete(final File file, final int depth) throws IOException { - if (!file.delete() && file.isFile()) - throw new IOException(MessageFormat.format(JGitText.get().fileCannotBeDeleted, file)); - - File dir = file.getParentFile(); - for (int i = 0; i < depth; ++i) { - if (!dir.delete()) - break; // ignore problem here - dir = dir.getParentFile(); - } - } - - private static class PackedRefList extends RefList { - static final PackedRefList NO_PACKED_REFS = new PackedRefList(RefList - .emptyList(), 0, 0); - - /** Last length of the packed-refs file when we read it. */ - final long lastSize; - - /** Last modified time of the packed-refs file when we read it. */ - final long lastModified; - - PackedRefList(RefList src, long size, long mtime) { - super(src); - lastSize = size; - lastModified = mtime; - } - } - - private static LooseSymbolicRef newSymbolicRef(long lastModified, - String name, String target) { - Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null); - return new LooseSymbolicRef(lastModified, name, dst); - } - - private static interface LooseRef extends Ref { - long getLastModified(); - - LooseRef peel(ObjectIdRef newLeaf); - } - - private final static class LoosePeeledTag extends ObjectIdRef.PeeledTag - implements LooseRef { - private final long lastModified; - - LoosePeeledTag(long mtime, String refName, ObjectId id, ObjectId p) { - super(LOOSE, refName, id, p); - this.lastModified = mtime; - } - - public long getLastModified() { - return lastModified; - } - - public LooseRef peel(ObjectIdRef newLeaf) { - return this; - } - } - - private final static class LooseNonTag extends ObjectIdRef.PeeledNonTag - implements LooseRef { - private final long lastModified; - - LooseNonTag(long mtime, String refName, ObjectId id) { - super(LOOSE, refName, id); - this.lastModified = mtime; - } - - public long getLastModified() { - return lastModified; - } - - public LooseRef peel(ObjectIdRef newLeaf) { - return this; - } - } - - private final static class LooseUnpeeled extends ObjectIdRef.Unpeeled - implements LooseRef { - private final long lastModified; - - LooseUnpeeled(long mtime, String refName, ObjectId id) { - super(LOOSE, refName, id); - this.lastModified = mtime; - } - - public long getLastModified() { - return lastModified; - } - - public LooseRef peel(ObjectIdRef newLeaf) { - if (newLeaf.getPeeledObjectId() != null) - return new LoosePeeledTag(lastModified, getName(), - getObjectId(), newLeaf.getPeeledObjectId()); - else - return new LooseNonTag(lastModified, getName(), getObjectId()); - } - } - - private final static class LooseSymbolicRef extends SymbolicRef implements - LooseRef { - private final long lastModified; - - LooseSymbolicRef(long mtime, String refName, Ref target) { - super(refName, target); - this.lastModified = mtime; - } - - public long getLastModified() { - return lastModified; - } - - public LooseRef peel(ObjectIdRef newLeaf) { - // We should never try to peel the symbolic references. - throw new UnsupportedOperationException(); - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java deleted file mode 100644 index fec00d9325..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2010, Google Inc. - * Copyright (C) 2009, Robin Rosenberg - * 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.lib; - -import java.io.File; -import java.io.IOException; - -import org.eclipse.jgit.lib.RefUpdate.Result; -import org.eclipse.jgit.revwalk.RevWalk; - -/** - * Rename any reference stored by {@link RefDirectory}. - *

- * This class works by first renaming the source reference to a temporary name, - * then renaming the temporary name to the final destination reference. - *

- * This strategy permits switching a reference like {@code refs/heads/foo}, - * which is a file, to {@code refs/heads/foo/bar}, which is stored inside a - * directory that happens to match the source name. - */ -class RefDirectoryRename extends RefRename { - private final RefDirectory refdb; - - /** - * The value of the source reference at the start of the rename. - *

- * At the end of the rename the destination reference must have this same - * value, otherwise we have a concurrent update and the rename must fail - * without making any changes. - */ - private ObjectId objId; - - /** True if HEAD must be moved to the destination reference. */ - private boolean updateHEAD; - - /** A reference we backup {@link #objId} into during the rename. */ - private RefDirectoryUpdate tmp; - - RefDirectoryRename(RefDirectoryUpdate src, RefDirectoryUpdate dst) { - super(src, dst); - refdb = src.getRefDatabase(); - } - - @Override - protected Result doRename() throws IOException { - if (source.getRef().isSymbolic()) - return Result.IO_FAILURE; // not supported - - final RevWalk rw = new RevWalk(refdb.getRepository()); - objId = source.getOldObjectId(); - updateHEAD = needToUpdateHEAD(); - tmp = refdb.newTemporaryUpdate(); - try { - // First backup the source so its never unreachable. - tmp.setNewObjectId(objId); - tmp.setForceUpdate(true); - tmp.disableRefLog(); - switch (tmp.update(rw)) { - case NEW: - case FORCED: - case NO_CHANGE: - break; - default: - return tmp.getResult(); - } - - // Save the source's log under the temporary name, we must do - // this before we delete the source, otherwise we lose the log. - if (!renameLog(source, tmp)) - return Result.IO_FAILURE; - - // If HEAD has to be updated, link it now to destination. - // We have to link before we delete, otherwise the delete - // fails because its the current branch. - RefUpdate dst = destination; - if (updateHEAD) { - if (!linkHEAD(destination)) { - renameLog(tmp, source); - return Result.LOCK_FAILURE; - } - - // Replace the update operation so HEAD will log the rename. - dst = refdb.newUpdate(Constants.HEAD, false); - dst.setRefLogIdent(destination.getRefLogIdent()); - dst.setRefLogMessage(destination.getRefLogMessage(), false); - } - - // Delete the source name so its path is free for replacement. - source.setExpectedOldObjectId(objId); - source.setForceUpdate(true); - source.disableRefLog(); - if (source.delete(rw) != Result.FORCED) { - renameLog(tmp, source); - if (updateHEAD) - linkHEAD(source); - return source.getResult(); - } - - // Move the log to the destination. - if (!renameLog(tmp, destination)) { - renameLog(tmp, source); - source.setExpectedOldObjectId(ObjectId.zeroId()); - source.setNewObjectId(objId); - source.update(rw); - if (updateHEAD) - linkHEAD(source); - return Result.IO_FAILURE; - } - - // Create the destination, logging the rename during the creation. - dst.setExpectedOldObjectId(ObjectId.zeroId()); - dst.setNewObjectId(objId); - if (dst.update(rw) != Result.NEW) { - // If we didn't create the destination we have to undo - // our work. Put the log back and restore source. - if (renameLog(destination, tmp)) - renameLog(tmp, source); - source.setExpectedOldObjectId(ObjectId.zeroId()); - source.setNewObjectId(objId); - source.update(rw); - if (updateHEAD) - linkHEAD(source); - return dst.getResult(); - } - - return Result.RENAMED; - } finally { - // Always try to free the temporary name. - try { - refdb.delete(tmp); - } catch (IOException err) { - refdb.fileFor(tmp.getName()).delete(); - } - } - } - - private boolean renameLog(RefUpdate src, RefUpdate dst) { - File srcLog = refdb.logFor(src.getName()); - File dstLog = refdb.logFor(dst.getName()); - - if (!srcLog.exists()) - return true; - - if (!rename(srcLog, dstLog)) - return false; - - try { - final int levels = RefDirectory.levelsIn(src.getName()) - 2; - RefDirectory.delete(srcLog, levels); - return true; - } catch (IOException e) { - rename(dstLog, srcLog); - return false; - } - } - - private static boolean rename(File src, File dst) { - if (src.renameTo(dst)) - return true; - - File dir = dst.getParentFile(); - if ((dir.exists() || !dir.mkdirs()) && !dir.isDirectory()) - return false; - return src.renameTo(dst); - } - - private boolean linkHEAD(RefUpdate target) { - try { - RefUpdate u = refdb.newUpdate(Constants.HEAD, false); - u.disableRefLog(); - switch (u.link(target.getName())) { - case NEW: - case FORCED: - case NO_CHANGE: - return true; - default: - return false; - } - } catch (IOException e) { - return false; - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java deleted file mode 100644 index 447be104ab..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2009-2010, Google Inc. - * Copyright (C) 2008, Shawn O. Pearce - * 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.lib; - -import static org.eclipse.jgit.lib.Constants.encode; - -import java.io.IOException; - -/** Updates any reference stored by {@link RefDirectory}. */ -class RefDirectoryUpdate extends RefUpdate { - private final RefDirectory database; - - private LockFile lock; - - RefDirectoryUpdate(final RefDirectory r, final Ref ref) { - super(ref); - database = r; - } - - @Override - protected RefDirectory getRefDatabase() { - return database; - } - - @Override - protected Repository getRepository() { - return database.getRepository(); - } - - @Override - protected boolean tryLock(boolean deref) throws IOException { - Ref dst = getRef(); - if (deref) - dst = dst.getLeaf(); - String name = dst.getName(); - lock = new LockFile(database.fileFor(name)); - if (lock.lock()) { - dst = database.getRef(name); - setOldObjectId(dst != null ? dst.getObjectId() : null); - return true; - } else { - return false; - } - } - - @Override - protected void unlock() { - if (lock != null) { - lock.unlock(); - lock = null; - } - } - - @Override - protected Result doUpdate(final Result status) throws IOException { - lock.setNeedStatInformation(true); - lock.write(getNewObjectId()); - - String msg = getRefLogMessage(); - if (msg != null) { - if (isRefLogIncludingResult()) { - String strResult = toResultString(status); - if (strResult != null) { - if (msg.length() > 0) - msg = msg + ": " + strResult; - else - msg = strResult; - } - } - database.log(this, msg, true); - } - if (!lock.commit()) - return Result.LOCK_FAILURE; - database.stored(this, lock.getCommitLastModified()); - return status; - } - - private String toResultString(final Result status) { - switch (status) { - case FORCED: - return "forced-update"; - case FAST_FORWARD: - return "fast forward"; - case NEW: - return "created"; - default: - return null; - } - } - - @Override - protected Result doDelete(final Result status) throws IOException { - if (getRef().getLeaf().getStorage() != Ref.Storage.NEW) - database.delete(this); - return status; - } - - @Override - protected Result doLink(final String target) throws IOException { - lock.setNeedStatInformation(true); - lock.write(encode(RefDirectory.SYMREF + target + '\n')); - - String msg = getRefLogMessage(); - if (msg != null) - database.log(this, msg, false); - if (!lock.commit()) - return Result.LOCK_FAILURE; - database.storedSymbolicRef(this, lock.getCommitLastModified(), target); - - if (getRef().getStorage() == Ref.Storage.NEW) - return Result.NEW; - return Result.FORCED; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java index f2738245d4..4acb3ecd89 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java @@ -51,6 +51,7 @@ import java.io.StringWriter; import java.util.Collection; import java.util.Map; +import org.eclipse.jgit.storage.file.RefDirectory; import org.eclipse.jgit.util.RefList; import org.eclipse.jgit.util.RefMap; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java deleted file mode 100644 index 4c5503f321..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2009, Robin Rosenberg - * Copyright (C) 2009, Robin Rosenberg - * 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.lib; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.util.IO; -import org.eclipse.jgit.util.RawParseUtils; - -/** - * Utility for reading reflog entries - */ -public class ReflogReader { - /** - * Parsed reflog entry - */ - static public class Entry { - private ObjectId oldId; - - private ObjectId newId; - - private PersonIdent who; - - private String comment; - - Entry(byte[] raw, int pos) { - oldId = ObjectId.fromString(raw, pos); - pos += Constants.OBJECT_ID_STRING_LENGTH; - if (raw[pos++] != ' ') - throw new IllegalArgumentException( - JGitText.get().rawLogMessageDoesNotParseAsLogEntry); - newId = ObjectId.fromString(raw, pos); - pos += Constants.OBJECT_ID_STRING_LENGTH; - if (raw[pos++] != ' ') { - throw new IllegalArgumentException( - JGitText.get().rawLogMessageDoesNotParseAsLogEntry); - } - who = RawParseUtils.parsePersonIdentOnly(raw, pos); - int p0 = RawParseUtils.next(raw, pos, '\t'); // personident has no - // \t - if (p0 == -1) { - throw new IllegalArgumentException( - JGitText.get().rawLogMessageDoesNotParseAsLogEntry); - } - int p1 = RawParseUtils.nextLF(raw, p0); - if (p1 == -1) { - throw new IllegalArgumentException( - JGitText.get().rawLogMessageDoesNotParseAsLogEntry); - } - comment = RawParseUtils.decode(raw, p0, p1 - 1); - } - - /** - * @return the commit id before the change - */ - public ObjectId getOldId() { - return oldId; - } - - /** - * @return the commit id after the change - */ - public ObjectId getNewId() { - return newId; - } - - /** - * @return user performin the change - */ - public PersonIdent getWho() { - return who; - } - - /** - * @return textual description of the change - */ - public String getComment() { - return comment; - } - - @Override - public String toString() { - return "Entry[" + oldId.name() + ", " + newId.name() + ", " + getWho() + ", " - + getComment() + "]"; - } - } - - private File logName; - - ReflogReader(Repository db, String refname) { - logName = new File(db.getDirectory(), "logs/" + refname); - } - - /** - * Get the last entry in the reflog - * - * @return the latest reflog entry, or null if no log - * @throws IOException - */ - public Entry getLastEntry() throws IOException { - List entries = getReverseEntries(1); - return entries.size() > 0 ? entries.get(0) : null; - } - - /** - * @return all reflog entries in reverse order - * @throws IOException - */ - public List getReverseEntries() throws IOException { - return getReverseEntries(Integer.MAX_VALUE); - } - - /** - * @param max - * max numer of entries to read - * @return all reflog entries in reverse order - * @throws IOException - */ - public List getReverseEntries(int max) throws IOException { - final byte[] log; - try { - log = IO.readFully(logName); - } catch (FileNotFoundException e) { - return Collections.emptyList(); - } - - int rs = RawParseUtils.prevLF(log, log.length); - List ret = new ArrayList(); - while (rs >= 0 && max-- > 0) { - rs = RawParseUtils.prevLF(log, rs); - Entry entry = new Entry(log, rs < 0 ? 0 : rs + 2); - ret.add(entry); - } - return ret; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index 6b91481d3e..65f75094ea 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -69,6 +69,7 @@ import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.ReflogReader; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java index 4b0e1f6901..f9185e8f26 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java @@ -45,6 +45,8 @@ package org.eclipse.jgit.lib; import java.io.File; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; + /** * Base class to support constructing a {@link Repository}. *

diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java index 39d734a32b..dc5eae5173 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java @@ -52,6 +52,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java deleted file mode 100644 index 3cef48242d..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2008, Shawn O. Pearce - * 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.lib; - -import java.lang.ref.SoftReference; - -class UnpackedObjectCache { - private static final int CACHE_SZ = 1024; - - private static final SoftReference DEAD; - - private static int hash(final long position) { - return (((int) position) << 22) >>> 22; - } - - private static int maxByteCount; - - private static final Slot[] cache; - - private static Slot lruHead; - - private static Slot lruTail; - - private static int openByteCount; - - static { - DEAD = new SoftReference(null); - maxByteCount = new WindowCacheConfig().getDeltaBaseCacheLimit(); - - cache = new Slot[CACHE_SZ]; - for (int i = 0; i < CACHE_SZ; i++) - cache[i] = new Slot(); - } - - static synchronized void reconfigure(final WindowCacheConfig cfg) { - final int dbLimit = cfg.getDeltaBaseCacheLimit(); - if (maxByteCount != dbLimit) { - maxByteCount = dbLimit; - releaseMemory(); - } - } - - static synchronized Entry get(final PackFile pack, final long position) { - final Slot e = cache[hash(position)]; - if (e.provider == pack && e.position == position) { - final Entry buf = e.data.get(); - if (buf != null) { - moveToHead(e); - return buf; - } - } - return null; - } - - static synchronized void store(final PackFile pack, final long position, - final byte[] data, final int objectType) { - if (data.length > maxByteCount) - return; // Too large to cache. - - final Slot e = cache[hash(position)]; - clearEntry(e); - - openByteCount += data.length; - releaseMemory(); - - e.provider = pack; - e.position = position; - e.sz = data.length; - e.data = new SoftReference(new Entry(data, objectType)); - moveToHead(e); - } - - private static void releaseMemory() { - while (openByteCount > maxByteCount && lruTail != null) { - final Slot currOldest = lruTail; - final Slot nextOldest = currOldest.lruPrev; - - clearEntry(currOldest); - currOldest.lruPrev = null; - currOldest.lruNext = null; - - if (nextOldest == null) - lruHead = null; - else - nextOldest.lruNext = null; - lruTail = nextOldest; - } - } - - static synchronized void purge(final PackFile file) { - for (final Slot e : cache) { - if (e.provider == file) { - clearEntry(e); - unlink(e); - } - } - } - - private static void moveToHead(final Slot e) { - unlink(e); - e.lruPrev = null; - e.lruNext = lruHead; - if (lruHead != null) - lruHead.lruPrev = e; - else - lruTail = e; - lruHead = e; - } - - private static void unlink(final Slot e) { - final Slot prev = e.lruPrev; - final Slot next = e.lruNext; - if (prev != null) - prev.lruNext = next; - if (next != null) - next.lruPrev = prev; - } - - private static void clearEntry(final Slot e) { - openByteCount -= e.sz; - e.provider = null; - e.data = DEAD; - e.sz = 0; - } - - private UnpackedObjectCache() { - throw new UnsupportedOperationException(); - } - - static class Entry { - final byte[] data; - - final int type; - - Entry(final byte[] aData, final int aType) { - data = aData; - type = aType; - } - } - - private static class Slot { - Slot lruPrev; - - Slot lruNext; - - PackFile provider; - - long position; - - int sz; - - SoftReference data = DEAD; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java deleted file mode 100644 index cd2eb38ef1..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.zip.DataFormatException; -import java.util.zip.Inflater; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.util.IO; -import org.eclipse.jgit.util.MutableInteger; -import org.eclipse.jgit.util.RawParseUtils; - -/** - * Loose object loader. This class loads an object not stored in a pack. - */ -public class UnpackedObjectLoader extends ObjectLoader { - private final int objectType; - - private final int objectSize; - - private final byte[] bytes; - - /** - * Construct an ObjectLoader to read from the file. - * - * @param path - * location of the loose object to read. - * @param id - * expected identity of the object being loaded, if known. - * @throws FileNotFoundException - * the loose object file does not exist. - * @throws IOException - * the loose object file exists, but is corrupt. - */ - public UnpackedObjectLoader(final File path, final AnyObjectId id) - throws IOException { - this(IO.readFully(path), id); - } - - /** - * Construct an ObjectLoader from a loose object's compressed form. - * - * @param compressed - * entire content of the loose object file. - * @throws CorruptObjectException - * The compressed data supplied does not match the format for a - * valid loose object. - */ - public UnpackedObjectLoader(final byte[] compressed) - throws CorruptObjectException { - this(compressed, null); - } - - private UnpackedObjectLoader(final byte[] compressed, final AnyObjectId id) - throws CorruptObjectException { - // Try to determine if this is a legacy format loose object or - // a new style loose object. The legacy format was completely - // compressed with zlib so the first byte must be 0x78 (15-bit - // window size, deflated) and the first 16 bit word must be - // evenly divisible by 31. Otherwise its a new style loose - // object. - // - final Inflater inflater = InflaterCache.get(); - try { - final int fb = compressed[0] & 0xff; - if (fb == 0x78 && (((fb << 8) | compressed[1] & 0xff) % 31) == 0) { - inflater.setInput(compressed); - final byte[] hdr = new byte[64]; - int avail = 0; - while (!inflater.finished() && avail < hdr.length) - try { - int uncompressed = inflater.inflate(hdr, avail, - hdr.length - avail); - if (uncompressed == 0) { - throw new CorruptObjectException(id, - JGitText.get().corruptObjectBadStreamCorruptHeader); - } - avail += uncompressed; - } catch (DataFormatException dfe) { - final CorruptObjectException coe; - coe = new CorruptObjectException(id, JGitText.get().corruptObjectBadStream); - coe.initCause(dfe); - throw coe; - } - if (avail < 5) - throw new CorruptObjectException(id, JGitText.get().corruptObjectNoHeader); - - final MutableInteger p = new MutableInteger(); - objectType = Constants.decodeTypeString(id, hdr, (byte) ' ', p); - objectSize = RawParseUtils.parseBase10(hdr, p.value, p); - if (objectSize < 0) - throw new CorruptObjectException(id, JGitText.get().corruptObjectNegativeSize); - if (hdr[p.value++] != 0) - throw new CorruptObjectException(id, JGitText.get().corruptObjectGarbageAfterSize); - bytes = new byte[objectSize]; - if (p.value < avail) - System.arraycopy(hdr, p.value, bytes, 0, avail - p.value); - decompress(id, inflater, avail - p.value); - } else { - int p = 0; - int c = compressed[p++] & 0xff; - final int typeCode = (c >> 4) & 7; - int size = c & 15; - int shift = 4; - while ((c & 0x80) != 0) { - c = compressed[p++] & 0xff; - size += (c & 0x7f) << shift; - shift += 7; - } - - switch (typeCode) { - case Constants.OBJ_COMMIT: - case Constants.OBJ_TREE: - case Constants.OBJ_BLOB: - case Constants.OBJ_TAG: - objectType = typeCode; - break; - default: - throw new CorruptObjectException(id, JGitText.get().corruptObjectInvalidType); - } - - objectSize = size; - bytes = new byte[objectSize]; - inflater.setInput(compressed, p, compressed.length - p); - decompress(id, inflater, 0); - } - } finally { - InflaterCache.release(inflater); - } - } - - private void decompress(final AnyObjectId id, final Inflater inf, int p) - throws CorruptObjectException { - try { - while (!inf.finished()) { - int uncompressed = inf.inflate(bytes, p, objectSize - p); - p += uncompressed; - if (uncompressed == 0 && !inf.finished()) { - throw new CorruptObjectException(id, - JGitText.get().corruptObjectBadStreamCorruptHeader); - } - } - } catch (DataFormatException dfe) { - final CorruptObjectException coe; - coe = new CorruptObjectException(id, JGitText.get().corruptObjectBadStream); - coe.initCause(dfe); - throw coe; - } - if (p != objectSize) - throw new CorruptObjectException(id, JGitText.get().corruptObjectIncorrectLength); - } - - @Override - public int getType() { - return objectType; - } - - @Override - public long getSize() { - return objectSize; - } - - @Override - public byte[] getCachedBytes() { - return bytes; - } - - @Override - public int getRawType() { - return objectType; - } - - @Override - public long getRawSize() { - return objectSize; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java deleted file mode 100644 index fcfa57339e..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.text.MessageFormat; -import java.util.zip.DataFormatException; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.CorruptObjectException; - -/** Reader for a non-delta (just deflated) object in a pack file. */ -class WholePackedObjectLoader extends PackedObjectLoader { - private static final int OBJ_COMMIT = Constants.OBJ_COMMIT; - - WholePackedObjectLoader(final PackFile pr, final long objectOffset, - final int headerSize, final int type, final int size) { - super(pr, objectOffset, headerSize); - objectType = type; - objectSize = size; - } - - @Override - public void materialize(final WindowCursor curs) throws IOException { - if (cachedBytes != null) { - return; - } - - if (objectType != OBJ_COMMIT) { - UnpackedObjectCache.Entry cache = pack.readCache(objectOffset); - if (cache != null) { - curs.release(); - cachedBytes = cache.data; - return; - } - } - - try { - cachedBytes = pack.decompress(objectOffset + headerSize, - objectSize, curs); - curs.release(); - if (objectType != OBJ_COMMIT) - pack.saveCache(objectOffset, cachedBytes, objectType); - } catch (DataFormatException dfe) { - final CorruptObjectException coe; - coe = new CorruptObjectException(MessageFormat.format(JGitText.get().objectAtHasBadZlibStream, - objectOffset, pack.getPackFile())); - coe.initCause(dfe); - throw coe; - } - } - - @Override - public int getRawType() { - return objectType; - } - - @Override - public long getRawSize() { - return objectSize; - } - - @Override - public ObjectId getDeltaBase() { - return null; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java deleted file mode 100644 index a44a30ee2d..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java +++ /dev/null @@ -1,624 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.lang.ref.ReferenceQueue; -import java.lang.ref.SoftReference; -import java.util.Random; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReferenceArray; -import java.util.concurrent.locks.ReentrantLock; - -import org.eclipse.jgit.JGitText; - -/** - * Caches slices of a {@link PackFile} in memory for faster read access. - *

- * The WindowCache serves as a Java based "buffer cache", loading segments of a - * PackFile into the JVM heap prior to use. As JGit often wants to do reads of - * only tiny slices of a file, the WindowCache tries to smooth out these tiny - * reads into larger block-sized IO operations. - *

- * Whenever a cache miss occurs, {@link #load(PackFile, long)} is invoked by - * exactly one thread for the given (PackFile,position) key tuple. - * This is ensured by an array of locks, with the tuple hashed to a lock - * instance. - *

- * During a miss, older entries are evicted from the cache so long as - * {@link #isFull()} returns true. - *

- * Its too expensive during object access to be 100% accurate with a least - * recently used (LRU) algorithm. Strictly ordering every read is a lot of - * overhead that typically doesn't yield a corresponding benefit to the - * application. - *

- * This cache implements a loose LRU policy by randomly picking a window - * comprised of roughly 10% of the cache, and evicting the oldest accessed entry - * within that window. - *

- * Entities created by the cache are held under SoftReferences, permitting the - * Java runtime's garbage collector to evict entries when heap memory gets low. - * Most JREs implement a loose least recently used algorithm for this eviction. - *

- * The internal hash table does not expand at runtime, instead it is fixed in - * size at cache creation time. The internal lock table used to gate load - * invocations is also fixed in size. - *

- * The key tuple is passed through to methods as a pair of parameters rather - * than as a single Object, thus reducing the transient memory allocations of - * callers. It is more efficient to avoid the allocation, as we can't be 100% - * sure that a JIT would be able to stack-allocate a key tuple. - *

- * This cache has an implementation rule such that: - *

    - *
  • {@link #load(PackFile, long)} is invoked by at most one thread at a time - * for a given (PackFile,position) tuple.
  • - *
  • For every load() invocation there is exactly one - * {@link #createRef(PackFile, long, ByteWindow)} invocation to wrap a - * SoftReference around the cached entity.
  • - *
  • For every Reference created by createRef() there will be - * exactly one call to {@link #clear(Ref)} to cleanup any resources associated - * with the (now expired) cached entity.
  • - *
- *

- * Therefore, it is safe to perform resource accounting increments during the - * {@link #load(PackFile, long)} or - * {@link #createRef(PackFile, long, ByteWindow)} methods, and matching - * decrements during {@link #clear(Ref)}. Implementors may need to override - * {@link #createRef(PackFile, long, ByteWindow)} in order to embed additional - * accounting information into an implementation specific {@link Ref} subclass, - * as the cached entity may have already been evicted by the JRE's garbage - * collector. - *

- * To maintain higher concurrency workloads, during eviction only one thread - * performs the eviction work, while other threads can continue to insert new - * objects in parallel. This means that the cache can be temporarily over limit, - * especially if the nominated eviction thread is being starved relative to the - * other threads. - */ -public class WindowCache { - private static final int bits(int newSize) { - if (newSize < 4096) - throw new IllegalArgumentException(JGitText.get().invalidWindowSize); - if (Integer.bitCount(newSize) != 1) - throw new IllegalArgumentException(JGitText.get().windowSizeMustBePowerOf2); - return Integer.numberOfTrailingZeros(newSize); - } - - private static final Random rng = new Random(); - - private static volatile WindowCache cache; - - static { - reconfigure(new WindowCacheConfig()); - } - - /** - * Modify the configuration of the window cache. - *

- * The new configuration is applied immediately. If the new limits are - * smaller than what what is currently cached, older entries will be purged - * as soon as possible to allow the cache to meet the new limit. - * - * @param packedGitLimit - * maximum number of bytes to hold within this instance. - * @param packedGitWindowSize - * number of bytes per window within the cache. - * @param packedGitMMAP - * true to enable use of mmap when creating windows. - * @param deltaBaseCacheLimit - * number of bytes to hold in the delta base cache. - * @deprecated Use {@link WindowCacheConfig} instead. - */ - public static void reconfigure(final int packedGitLimit, - final int packedGitWindowSize, final boolean packedGitMMAP, - final int deltaBaseCacheLimit) { - final WindowCacheConfig c = new WindowCacheConfig(); - c.setPackedGitLimit(packedGitLimit); - c.setPackedGitWindowSize(packedGitWindowSize); - c.setPackedGitMMAP(packedGitMMAP); - c.setDeltaBaseCacheLimit(deltaBaseCacheLimit); - reconfigure(c); - } - - /** - * Modify the configuration of the window cache. - *

- * The new configuration is applied immediately. If the new limits are - * smaller than what what is currently cached, older entries will be purged - * as soon as possible to allow the cache to meet the new limit. - * - * @param cfg - * the new window cache configuration. - * @throws IllegalArgumentException - * the cache configuration contains one or more invalid - * settings, usually too low of a limit. - */ - public static void reconfigure(final WindowCacheConfig cfg) { - final WindowCache nc = new WindowCache(cfg); - final WindowCache oc = cache; - if (oc != null) - oc.removeAll(); - cache = nc; - UnpackedObjectCache.reconfigure(cfg); - } - - static WindowCache getInstance() { - return cache; - } - - static final ByteWindow get(final PackFile pack, final long offset) - throws IOException { - final WindowCache c = cache; - final ByteWindow r = c.getOrLoad(pack, c.toStart(offset)); - if (c != cache) { - // The cache was reconfigured while we were using the old one - // to load this window. The window is still valid, but our - // cache may think its still live. Ensure the window is removed - // from the old cache so resources can be released. - // - c.removeAll(); - } - return r; - } - - static final void purge(final PackFile pack) { - cache.removeAll(pack); - } - - /** ReferenceQueue to cleanup released and garbage collected windows. */ - private final ReferenceQueue queue; - - /** Number of entries in {@link #table}. */ - private final int tableSize; - - /** Access clock for loose LRU. */ - private final AtomicLong clock; - - /** Hash bucket directory; entries are chained below. */ - private final AtomicReferenceArray table; - - /** Locks to prevent concurrent loads for same (PackFile,position). */ - private final Lock[] locks; - - /** Lock to elect the eviction thread after a load occurs. */ - private final ReentrantLock evictLock; - - /** Number of {@link #table} buckets to scan for an eviction window. */ - private final int evictBatch; - - private final int maxFiles; - - private final long maxBytes; - - private final boolean mmap; - - private final int windowSizeShift; - - private final int windowSize; - - private final AtomicInteger openFiles; - - private final AtomicLong openBytes; - - private WindowCache(final WindowCacheConfig cfg) { - tableSize = tableSize(cfg); - final int lockCount = lockCount(cfg); - if (tableSize < 1) - throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1); - if (lockCount < 1) - throw new IllegalArgumentException(JGitText.get().lockCountMustBeGreaterOrEqual1); - - queue = new ReferenceQueue(); - clock = new AtomicLong(1); - table = new AtomicReferenceArray(tableSize); - locks = new Lock[lockCount]; - for (int i = 0; i < locks.length; i++) - locks[i] = new Lock(); - evictLock = new ReentrantLock(); - - int eb = (int) (tableSize * .1); - if (64 < eb) - eb = 64; - else if (eb < 4) - eb = 4; - if (tableSize < eb) - eb = tableSize; - evictBatch = eb; - - maxFiles = cfg.getPackedGitOpenFiles(); - maxBytes = cfg.getPackedGitLimit(); - mmap = cfg.isPackedGitMMAP(); - windowSizeShift = bits(cfg.getPackedGitWindowSize()); - windowSize = 1 << windowSizeShift; - - openFiles = new AtomicInteger(); - openBytes = new AtomicLong(); - - if (maxFiles < 1) - throw new IllegalArgumentException(JGitText.get().openFilesMustBeAtLeast1); - if (maxBytes < windowSize) - throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit); - } - - int getOpenFiles() { - return openFiles.get(); - } - - long getOpenBytes() { - return openBytes.get(); - } - - private int hash(final int packHash, final long off) { - return packHash + (int) (off >>> windowSizeShift); - } - - private ByteWindow load(final PackFile pack, final long offset) - throws IOException { - if (pack.beginWindowCache()) - openFiles.incrementAndGet(); - try { - if (mmap) - return pack.mmap(offset, windowSize); - return pack.read(offset, windowSize); - } catch (IOException e) { - close(pack); - throw e; - } catch (RuntimeException e) { - close(pack); - throw e; - } catch (Error e) { - close(pack); - throw e; - } - } - - private Ref createRef(final PackFile p, final long o, final ByteWindow v) { - final Ref ref = new Ref(p, o, v, queue); - openBytes.addAndGet(ref.size); - return ref; - } - - private void clear(final Ref ref) { - openBytes.addAndGet(-ref.size); - close(ref.pack); - } - - private void close(final PackFile pack) { - if (pack.endWindowCache()) - openFiles.decrementAndGet(); - } - - private boolean isFull() { - return maxFiles < openFiles.get() || maxBytes < openBytes.get(); - } - - private long toStart(final long offset) { - return (offset >>> windowSizeShift) << windowSizeShift; - } - - private static int tableSize(final WindowCacheConfig cfg) { - final int wsz = cfg.getPackedGitWindowSize(); - final long limit = cfg.getPackedGitLimit(); - if (wsz <= 0) - throw new IllegalArgumentException(JGitText.get().invalidWindowSize); - if (limit < wsz) - throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit); - return (int) Math.min(5 * (limit / wsz) / 2, 2000000000); - } - - private static int lockCount(final WindowCacheConfig cfg) { - return Math.max(cfg.getPackedGitOpenFiles(), 32); - } - - /** - * Lookup a cached object, creating and loading it if it doesn't exist. - * - * @param pack - * the pack that "contains" the cached object. - * @param position - * offset within pack of the object. - * @return the object reference. - * @throws IOException - * the object reference was not in the cache and could not be - * obtained by {@link #load(PackFile, long)}. - */ - private ByteWindow getOrLoad(final PackFile pack, final long position) - throws IOException { - final int slot = slot(pack, position); - final Entry e1 = table.get(slot); - ByteWindow v = scan(e1, pack, position); - if (v != null) - return v; - - synchronized (lock(pack, position)) { - Entry e2 = table.get(slot); - if (e2 != e1) { - v = scan(e2, pack, position); - if (v != null) - return v; - } - - v = load(pack, position); - final Ref ref = createRef(pack, position, v); - hit(ref); - for (;;) { - final Entry n = new Entry(clean(e2), ref); - if (table.compareAndSet(slot, e2, n)) - break; - e2 = table.get(slot); - } - } - - if (evictLock.tryLock()) { - try { - gc(); - evict(); - } finally { - evictLock.unlock(); - } - } - - return v; - } - - private ByteWindow scan(Entry n, final PackFile pack, final long position) { - for (; n != null; n = n.next) { - final Ref r = n.ref; - if (r.pack == pack && r.position == position) { - final ByteWindow v = r.get(); - if (v != null) { - hit(r); - return v; - } - n.kill(); - break; - } - } - return null; - } - - private void hit(final Ref r) { - // We don't need to be 100% accurate here. Its sufficient that at least - // one thread performs the increment. Any other concurrent access at - // exactly the same time can simply use the same clock value. - // - // Consequently we attempt the set, but we don't try to recover should - // it fail. This is why we don't use getAndIncrement() here. - // - final long c = clock.get(); - clock.compareAndSet(c, c + 1); - r.lastAccess = c; - } - - private void evict() { - while (isFull()) { - int ptr = rng.nextInt(tableSize); - Entry old = null; - int slot = 0; - for (int b = evictBatch - 1; b >= 0; b--, ptr++) { - if (tableSize <= ptr) - ptr = 0; - for (Entry e = table.get(ptr); e != null; e = e.next) { - if (e.dead) - continue; - if (old == null || e.ref.lastAccess < old.ref.lastAccess) { - old = e; - slot = ptr; - } - } - } - if (old != null) { - old.kill(); - gc(); - final Entry e1 = table.get(slot); - table.compareAndSet(slot, e1, clean(e1)); - } - } - } - - /** - * Clear every entry from the cache. - *

- * This is a last-ditch effort to clear out the cache, such as before it - * gets replaced by another cache that is configured differently. This - * method tries to force every cached entry through {@link #clear(Ref)} to - * ensure that resources are correctly accounted for and cleaned up by the - * subclass. A concurrent reader loading entries while this method is - * running may cause resource accounting failures. - */ - private void removeAll() { - for (int s = 0; s < tableSize; s++) { - Entry e1; - do { - e1 = table.get(s); - for (Entry e = e1; e != null; e = e.next) - e.kill(); - } while (!table.compareAndSet(s, e1, null)); - } - gc(); - } - - /** - * Clear all entries related to a single file. - *

- * Typically this method is invoked during {@link PackFile#close()}, when we - * know the pack is never going to be useful to us again (for example, it no - * longer exists on disk). A concurrent reader loading an entry from this - * same pack may cause the pack to become stuck in the cache anyway. - * - * @param pack - * the file to purge all entries of. - */ - private void removeAll(final PackFile pack) { - for (int s = 0; s < tableSize; s++) { - final Entry e1 = table.get(s); - boolean hasDead = false; - for (Entry e = e1; e != null; e = e.next) { - if (e.ref.pack == pack) { - e.kill(); - hasDead = true; - } else if (e.dead) - hasDead = true; - } - if (hasDead) - table.compareAndSet(s, e1, clean(e1)); - } - gc(); - } - - @SuppressWarnings("unchecked") - private void gc() { - Ref r; - while ((r = (Ref) queue.poll()) != null) { - // Sun's Java 5 and 6 implementation have a bug where a Reference - // can be enqueued and dequeued twice on the same reference queue - // due to a race condition within ReferenceQueue.enqueue(Reference). - // - // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6837858 - // - // We CANNOT permit a Reference to come through us twice, as it will - // skew the resource counters we maintain. Our canClear() check here - // provides a way to skip the redundant dequeues, if any. - // - if (r.canClear()) { - clear(r); - - boolean found = false; - final int s = slot(r.pack, r.position); - final Entry e1 = table.get(s); - for (Entry n = e1; n != null; n = n.next) { - if (n.ref == r) { - n.dead = true; - found = true; - break; - } - } - if (found) - table.compareAndSet(s, e1, clean(e1)); - } - } - } - - private int slot(final PackFile pack, final long position) { - return (hash(pack.hash, position) >>> 1) % tableSize; - } - - private Lock lock(final PackFile pack, final long position) { - return locks[(hash(pack.hash, position) >>> 1) % locks.length]; - } - - private static Entry clean(Entry top) { - while (top != null && top.dead) { - top.ref.enqueue(); - top = top.next; - } - if (top == null) - return null; - final Entry n = clean(top.next); - return n == top.next ? top : new Entry(n, top.ref); - } - - private static class Entry { - /** Next entry in the hash table's chain list. */ - final Entry next; - - /** The referenced object. */ - final Ref ref; - - /** - * Marked true when ref.get() returns null and the ref is dead. - *

- * A true here indicates that the ref is no longer accessible, and that - * we therefore need to eventually purge this Entry object out of the - * bucket's chain. - */ - volatile boolean dead; - - Entry(final Entry n, final Ref r) { - next = n; - ref = r; - } - - final void kill() { - dead = true; - ref.enqueue(); - } - } - - /** A soft reference wrapped around a cached object. */ - private static class Ref extends SoftReference { - final PackFile pack; - - final long position; - - final int size; - - long lastAccess; - - private boolean cleared; - - protected Ref(final PackFile pack, final long position, - final ByteWindow v, final ReferenceQueue queue) { - super(v, queue); - this.pack = pack; - this.position = position; - this.size = v.size(); - } - - final synchronized boolean canClear() { - if (cleared) - return false; - cleared = true; - return true; - } - } - - private static final class Lock { - // Used only for its implicit monitor. - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java deleted file mode 100644 index 2d8aef34ca..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2009, Google Inc. - * 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.lib; - -/** Configuration parameters for {@link WindowCache}. */ -public class WindowCacheConfig { - /** 1024 (number of bytes in one kibibyte/kilobyte) */ - public static final int KB = 1024; - - /** 1024 {@link #KB} (number of bytes in one mebibyte/megabyte) */ - public static final int MB = 1024 * KB; - - private int packedGitOpenFiles; - - private long packedGitLimit; - - private int packedGitWindowSize; - - private boolean packedGitMMAP; - - private int deltaBaseCacheLimit; - - /** Create a default configuration. */ - public WindowCacheConfig() { - packedGitOpenFiles = 128; - packedGitLimit = 10 * MB; - packedGitWindowSize = 8 * KB; - packedGitMMAP = false; - deltaBaseCacheLimit = 10 * MB; - } - - /** - * @return maximum number of streams to open at a time. Open packs count - * against the process limits. Default is 128. - */ - public int getPackedGitOpenFiles() { - return packedGitOpenFiles; - } - - /** - * @param fdLimit - * maximum number of streams to open at a time. Open packs count - * against the process limits - */ - public void setPackedGitOpenFiles(final int fdLimit) { - packedGitOpenFiles = fdLimit; - } - - /** - * @return maximum number bytes of heap memory to dedicate to caching pack - * file data. Default is 10 MB. - */ - public long getPackedGitLimit() { - return packedGitLimit; - } - - /** - * @param newLimit - * maximum number bytes of heap memory to dedicate to caching - * pack file data. - */ - public void setPackedGitLimit(final long newLimit) { - packedGitLimit = newLimit; - } - - /** - * @return size in bytes of a single window mapped or read in from the pack - * file. Default is 8 KB. - */ - public int getPackedGitWindowSize() { - return packedGitWindowSize; - } - - /** - * @param newSize - * size in bytes of a single window read in from the pack file. - */ - public void setPackedGitWindowSize(final int newSize) { - packedGitWindowSize = newSize; - } - - /** - * @return true enables use of Java NIO virtual memory mapping for windows; - * false reads entire window into a byte[] with standard read calls. - * Default false. - */ - public boolean isPackedGitMMAP() { - return packedGitMMAP; - } - - /** - * @param usemmap - * true enables use of Java NIO virtual memory mapping for - * windows; false reads entire window into a byte[] with standard - * read calls. - */ - public void setPackedGitMMAP(final boolean usemmap) { - packedGitMMAP = usemmap; - } - - /** - * @return maximum number of bytes to cache in {@link UnpackedObjectCache} - * for inflated, recently accessed objects, without delta chains. - * Default 10 MB. - */ - public int getDeltaBaseCacheLimit() { - return deltaBaseCacheLimit; - } - - /** - * @param newLimit - * maximum number of bytes to cache in - * {@link UnpackedObjectCache} for inflated, recently accessed - * objects, without delta chains. - */ - public void setDeltaBaseCacheLimit(final int newLimit) { - deltaBaseCacheLimit = newLimit; - } - - /** - * Update properties by setting fields from the configuration. - *

- * If a property is not defined in the configuration, then it is left - * unmodified. - * - * @param rc configuration to read properties from. - */ - public void fromConfig(final Config rc) { - setPackedGitOpenFiles(rc.getInt("core", null, "packedgitopenfiles", getPackedGitOpenFiles())); - setPackedGitLimit(rc.getLong("core", null, "packedgitlimit", getPackedGitLimit())); - setPackedGitWindowSize(rc.getInt("core", null, "packedgitwindowsize", getPackedGitWindowSize())); - setPackedGitMMAP(rc.getBoolean("core", null, "packedgitmmap", isPackedGitMMAP())); - setDeltaBaseCacheLimit(rc.getInt("core", null, "deltabasecachelimit", getDeltaBaseCacheLimit())); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java deleted file mode 100644 index 98916efcbd..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2006-2008, Shawn O. Pearce - * 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.lib; - -import java.io.IOException; -import java.util.zip.DataFormatException; -import java.util.zip.Inflater; - -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; -import org.eclipse.jgit.revwalk.RevObject; - -/** Active handle to a ByteWindow. */ -final class WindowCursor extends ObjectReader implements ObjectReuseAsIs { - /** Temporary buffer large enough for at least one raw object id. */ - final byte[] tempId = new byte[Constants.OBJECT_ID_LENGTH]; - - private Inflater inf; - - private ByteWindow window; - - private final FileObjectDatabase db; - - WindowCursor(FileObjectDatabase db) { - this.db = db; - } - - public boolean hasObject(AnyObjectId objectId) throws IOException { - return db.hasObject(objectId); - } - - public ObjectLoader openObject(AnyObjectId objectId, int typeHint) - throws MissingObjectException, IOException { - final ObjectLoader ldr = db.openObject(this, objectId); - if (ldr == null) { - if (typeHint == OBJ_ANY) - throw new MissingObjectException(objectId.copy(), "unknown"); - throw new MissingObjectException(objectId.copy(), typeHint); - } - return ldr; - } - - public LocalObjectToPack newObjectToPack(RevObject obj) { - return new LocalObjectToPack(obj); - } - - public void selectObjectRepresentation(PackWriter packer, ObjectToPack otp) - throws IOException, MissingObjectException { - db.selectObjectRepresentation(packer, otp, this); - } - - public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp) - throws IOException, StoredObjectRepresentationNotAvailableException { - LocalObjectToPack src = (LocalObjectToPack) otp; - src.copyFromPack.copyAsIs(out, src, this); - } - - /** - * Copy bytes from the window to a caller supplied buffer. - * - * @param pack - * the file the desired window is stored within. - * @param position - * position within the file to read from. - * @param dstbuf - * destination buffer to copy into. - * @param dstoff - * offset within dstbuf to start copying into. - * @param cnt - * number of bytes to copy. This value may exceed the number of - * bytes remaining in the window starting at offset - * pos. - * @return number of bytes actually copied; this may be less than - * cnt if cnt exceeded the number of bytes - * available. - * @throws IOException - * this cursor does not match the provider or id and the proper - * window could not be acquired through the provider's cache. - */ - int copy(final PackFile pack, long position, final byte[] dstbuf, - int dstoff, final int cnt) throws IOException { - final long length = pack.length; - int need = cnt; - while (need > 0 && position < length) { - pin(pack, position); - final int r = window.copy(position, dstbuf, dstoff, need); - position += r; - dstoff += r; - need -= r; - } - return cnt - need; - } - - /** - * Pump bytes into the supplied inflater as input. - * - * @param pack - * the file the desired window is stored within. - * @param position - * position within the file to read from. - * @param dstbuf - * destination buffer the inflater should output decompressed - * data to. - * @param dstoff - * current offset within dstbuf to inflate into. - * @return updated dstoff based on the number of bytes - * successfully inflated into dstbuf. - * @throws IOException - * this cursor does not match the provider or id and the proper - * window could not be acquired through the provider's cache. - * @throws DataFormatException - * the inflater encountered an invalid chunk of data. Data - * stream corruption is likely. - */ - int inflate(final PackFile pack, long position, final byte[] dstbuf, - int dstoff) throws IOException, DataFormatException { - prepareInflater(); - for (;;) { - pin(pack, position); - dstoff = window.inflate(position, dstbuf, dstoff, inf); - if (inf.finished()) - return dstoff; - position = window.end; - } - } - - ByteArrayWindow quickCopy(PackFile p, long pos, long cnt) - throws IOException { - pin(p, pos); - if (window instanceof ByteArrayWindow - && window.contains(p, pos + (cnt - 1))) - return (ByteArrayWindow) window; - return null; - } - - Inflater inflater() { - prepareInflater(); - return inf; - } - - private void prepareInflater() { - if (inf == null) - inf = InflaterCache.get(); - else - inf.reset(); - } - - private void pin(final PackFile pack, final long position) - throws IOException { - final ByteWindow w = window; - if (w == null || !w.contains(pack, position)) { - // If memory is low, we may need what is in our window field to - // be cleaned up by the GC during the get for the next window. - // So we always clear it, even though we are just going to set - // it again. - // - window = null; - window = WindowCache.get(pack, position); - } - } - - /** Release the current window cursor. */ - public void release() { - window = null; - try { - InflaterCache.release(inf); - } finally { - inf = null; - } - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java new file mode 100644 index 0000000000..840244b77f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.IOException; +import java.io.OutputStream; +import java.util.zip.CRC32; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +/** + * A {@link ByteWindow} with an underlying byte array for storage. + */ +final class ByteArrayWindow extends ByteWindow { + private final byte[] array; + + ByteArrayWindow(final PackFile pack, final long o, final byte[] b) { + super(pack, o, b.length); + array = b; + } + + @Override + protected int copy(final int p, final byte[] b, final int o, int n) { + n = Math.min(array.length - p, n); + System.arraycopy(array, p, b, o, n); + return n; + } + + @Override + protected int inflate(final int pos, final byte[] b, int o, + final Inflater inf) throws DataFormatException { + while (!inf.finished()) { + if (inf.needsInput()) { + inf.setInput(array, pos, array.length - pos); + break; + } + o += inf.inflate(b, o, b.length - o); + } + while (!inf.finished() && !inf.needsInput()) + o += inf.inflate(b, o, b.length - o); + return o; + } + + void crc32(CRC32 out, long pos, int cnt) { + out.update(array, (int) (pos - start), cnt); + } + + void write(OutputStream out, long pos, int cnt) throws IOException { + out.write(array, (int) (pos - start), cnt); + } + + void check(Inflater inf, byte[] tmp, long pos, int cnt) + throws DataFormatException { + inf.setInput(array, (int) (pos - start), cnt); + while (inf.inflate(tmp, 0, tmp.length) > 0) + continue; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java new file mode 100644 index 0000000000..52bc00f355 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.nio.ByteBuffer; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +/** + * A window for accessing git packs using a {@link ByteBuffer} for storage. + * + * @see ByteWindow + */ +final class ByteBufferWindow extends ByteWindow { + private final ByteBuffer buffer; + + ByteBufferWindow(final PackFile pack, final long o, final ByteBuffer b) { + super(pack, o, b.capacity()); + buffer = b; + } + + @Override + protected int copy(final int p, final byte[] b, final int o, int n) { + final ByteBuffer s = buffer.slice(); + s.position(p); + n = Math.min(s.remaining(), n); + s.get(b, o, n); + return n; + } + + @Override + protected int inflate(final int pos, final byte[] b, int o, + final Inflater inf) throws DataFormatException { + final byte[] tmp = new byte[512]; + final ByteBuffer s = buffer.slice(); + s.position(pos); + while (s.remaining() > 0 && !inf.finished()) { + if (inf.needsInput()) { + final int n = Math.min(s.remaining(), tmp.length); + s.get(tmp, 0, n); + inf.setInput(tmp, 0, n); + } + o += inf.inflate(b, o, b.length - o); + } + while (!inf.finished() && !inf.needsInput()) + o += inf.inflate(b, o, b.length - o); + return o; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java new file mode 100644 index 0000000000..5c77cff012 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.util.zip.DataFormatException; +import java.util.zip.Inflater; + +/** + * A window of data currently stored within a cache. + *

+ * All bytes in the window can be assumed to be "immediately available", that is + * they are very likely already in memory, unless the operating system's memory + * is very low and has paged part of this process out to disk. Therefore copying + * bytes from a window is very inexpensive. + *

+ */ +abstract class ByteWindow { + protected final PackFile pack; + + protected final long start; + + protected final long end; + + protected ByteWindow(final PackFile p, final long s, final int n) { + pack = p; + start = s; + end = start + n; + } + + final int size() { + return (int) (end - start); + } + + final boolean contains(final PackFile neededFile, final long neededPos) { + return pack == neededFile && start <= neededPos && neededPos < end; + } + + /** + * Copy bytes from the window to a caller supplied buffer. + * + * @param pos + * offset within the file to start copying from. + * @param dstbuf + * destination buffer to copy into. + * @param dstoff + * offset within dstbuf to start copying into. + * @param cnt + * number of bytes to copy. This value may exceed the number of + * bytes remaining in the window starting at offset + * pos. + * @return number of bytes actually copied; this may be less than + * cnt if cnt exceeded the number of + * bytes available. + */ + final int copy(long pos, byte[] dstbuf, int dstoff, int cnt) { + return copy((int) (pos - start), dstbuf, dstoff, cnt); + } + + /** + * Copy bytes from the window to a caller supplied buffer. + * + * @param pos + * offset within the window to start copying from. + * @param dstbuf + * destination buffer to copy into. + * @param dstoff + * offset within dstbuf to start copying into. + * @param cnt + * number of bytes to copy. This value may exceed the number of + * bytes remaining in the window starting at offset + * pos. + * @return number of bytes actually copied; this may be less than + * cnt if cnt exceeded the number of + * bytes available. + */ + protected abstract int copy(int pos, byte[] dstbuf, int dstoff, int cnt); + + /** + * Pump bytes into the supplied inflater as input. + * + * @param pos + * offset within the file to start supplying input from. + * @param dstbuf + * destination buffer the inflater should output decompressed + * data to. + * @param dstoff + * current offset within dstbuf to inflate into. + * @param inf + * the inflater to feed input to. The caller is responsible for + * initializing the inflater as multiple windows may need to + * supply data to the same inflater to completely decompress + * something. + * @return updated dstoff based on the number of bytes + * successfully copied into dstbuf by + * inf. If the inflater is not yet finished then + * another window's data must still be supplied as input to finish + * decompression. + * @throws DataFormatException + * the inflater encountered an invalid chunk of data. Data + * stream corruption is likely. + */ + final int inflate(long pos, byte[] dstbuf, int dstoff, Inflater inf) + throws DataFormatException { + return inflate((int) (pos - start), dstbuf, dstoff, inf); + } + + /** + * Pump bytes into the supplied inflater as input. + * + * @param pos + * offset within the window to start supplying input from. + * @param dstbuf + * destination buffer the inflater should output decompressed + * data to. + * @param dstoff + * current offset within dstbuf to inflate into. + * @param inf + * the inflater to feed input to. The caller is responsible for + * initializing the inflater as multiple windows may need to + * supply data to the same inflater to completely decompress + * something. + * @return updated dstoff based on the number of bytes + * successfully copied into dstbuf by + * inf. If the inflater is not yet finished then + * another window's data must still be supplied as input to finish + * decompression. + * @throws DataFormatException + * the inflater encountered an invalid chunk of data. Data + * stream corruption is likely. + */ + protected abstract int inflate(int pos, byte[] dstbuf, int dstoff, + Inflater inf) throws DataFormatException; +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java new file mode 100644 index 0000000000..79730d0551 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2010, Constantine Plotnikov + * Copyright (C) 2010, JetBrains s.r.o. + * 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.IOException; + +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectDatabase; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdSubclassMap; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.lib.PackWriter; + +/** + * The cached instance of an {@link ObjectDirectory}. + *

+ * This class caches the list of loose objects in memory, so the file system is + * not queried with stat calls. + */ +class CachedObjectDirectory extends FileObjectDatabase { + /** + * The set that contains unpacked objects identifiers, it is created when + * the cached instance is created. + */ + private final ObjectIdSubclassMap unpackedObjects = new ObjectIdSubclassMap(); + + private final ObjectDirectory wrapped; + + private AlternateHandle[] alts; + + /** + * The constructor + * + * @param wrapped + * the wrapped database + */ + CachedObjectDirectory(ObjectDirectory wrapped) { + this.wrapped = wrapped; + + File objects = wrapped.getDirectory(); + String[] fanout = objects.list(); + if (fanout == null) + fanout = new String[0]; + for (String d : fanout) { + if (d.length() != 2) + continue; + String[] entries = new File(objects, d).list(); + if (entries == null) + continue; + for (String e : entries) { + if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2) + continue; + try { + unpackedObjects.add(ObjectId.fromString(d + e)); + } catch (IllegalArgumentException notAnObject) { + // ignoring the file that does not represent loose object + } + } + } + } + + @Override + public void close() { + // Don't close anything. + } + + @Override + public ObjectInserter newInserter() { + return wrapped.newInserter(); + } + + @Override + public ObjectDatabase newCachedDatabase() { + return this; + } + + @Override + FileObjectDatabase newCachedFileObjectDatabase() { + return this; + } + + @Override + File getDirectory() { + return wrapped.getDirectory(); + } + + @Override + AlternateHandle[] myAlternates() { + if (alts == null) { + AlternateHandle[] src = wrapped.myAlternates(); + alts = new AlternateHandle[src.length]; + for (int i = 0; i < alts.length; i++) { + FileObjectDatabase s = src[i].db; + alts[i] = new AlternateHandle(s.newCachedFileObjectDatabase()); + } + } + return alts; + } + + @Override + boolean tryAgain1() { + return wrapped.tryAgain1(); + } + + @Override + public boolean hasObject(final AnyObjectId objectId) { + return hasObjectImpl1(objectId); + } + + @Override + boolean hasObject1(AnyObjectId objectId) { + return unpackedObjects.contains(objectId) + || wrapped.hasObject1(objectId); + } + + @Override + ObjectLoader openObject(final WindowCursor curs, + final AnyObjectId objectId) throws IOException { + return openObjectImpl1(curs, objectId); + } + + @Override + ObjectLoader openObject1(WindowCursor curs, AnyObjectId objectId) + throws IOException { + if (unpackedObjects.contains(objectId)) + return wrapped.openObject2(curs, objectId.name(), objectId); + return wrapped.openObject1(curs, objectId); + } + + @Override + boolean hasObject2(String objectId) { + // This method should never be invoked. + throw new UnsupportedOperationException(); + } + + @Override + ObjectLoader openObject2(WindowCursor curs, String objectName, + AnyObjectId objectId) throws IOException { + // This method should never be invoked. + throw new UnsupportedOperationException(); + } + + @Override + void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, + WindowCursor curs) throws IOException { + wrapped.selectObjectRepresentation(packer, otp, curs); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java new file mode 100644 index 0000000000..1a5fe8ea90 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.IOException; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; + +/** Reads a deltified object which uses an offset to find its base. */ +class DeltaOfsPackedObjectLoader extends DeltaPackedObjectLoader { + private final long deltaBase; + + DeltaOfsPackedObjectLoader(final PackFile pr, final long objectOffset, + final int headerSz, final int deltaSz, final long base) { + super(pr, objectOffset, headerSz, deltaSz); + deltaBase = base; + } + + protected PackedObjectLoader getBaseLoader(final WindowCursor curs) + throws IOException { + return pack.resolveBase(curs, deltaBase); + } + + @Override + public int getRawType() { + return Constants.OBJ_OFS_DELTA; + } + + @Override + ObjectId getDeltaBase() throws IOException { + final ObjectId id = pack.findObjectForOffset(deltaBase); + if (id == null) + throw new CorruptObjectException( + JGitText.get().offsetWrittenDeltaBaseForObjectNotFoundInAPack); + return id; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java new file mode 100644 index 0000000000..4a9323cccf --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.IOException; +import java.text.MessageFormat; +import java.util.zip.DataFormatException; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.BinaryDelta; +import org.eclipse.jgit.lib.Constants; + +/** Reader for a deltified object stored in a pack file. */ +abstract class DeltaPackedObjectLoader extends PackedObjectLoader { + private static final int OBJ_COMMIT = Constants.OBJ_COMMIT; + + private final int deltaSize; + + DeltaPackedObjectLoader(final PackFile pr, final long objectOffset, + final int headerSize, final int deltaSz) { + super(pr, objectOffset, headerSize); + objectType = -1; + deltaSize = deltaSz; + } + + @Override + void materialize(final WindowCursor curs) throws IOException { + if (cachedBytes != null) { + return; + } + + if (objectType != OBJ_COMMIT) { + UnpackedObjectCache.Entry cache = pack.readCache(objectOffset); + if (cache != null) { + curs.release(); + objectType = cache.type; + objectSize = cache.data.length; + cachedBytes = cache.data; + return; + } + } + + try { + final PackedObjectLoader baseLoader = getBaseLoader(curs); + baseLoader.materialize(curs); + cachedBytes = BinaryDelta.apply(baseLoader.getCachedBytes(), pack + .decompress(objectOffset + headerSize, deltaSize, curs)); + curs.release(); + objectType = baseLoader.getType(); + objectSize = cachedBytes.length; + if (objectType != OBJ_COMMIT) + pack.saveCache(objectOffset, cachedBytes, objectType); + } catch (DataFormatException dfe) { + final CorruptObjectException coe; + coe = new CorruptObjectException(MessageFormat.format(JGitText.get().objectAtHasBadZlibStream, + objectOffset, pack.getPackFile())); + coe.initCause(dfe); + throw coe; + } + } + + @Override + public long getRawSize() { + return deltaSize; + } + + /** + * @param curs + * temporary thread storage during data access. + * @return the object loader for the base object + * @throws IOException + */ + protected abstract PackedObjectLoader getBaseLoader(WindowCursor curs) + throws IOException; +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java new file mode 100644 index 0000000000..40e1975e94 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.IOException; + +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; + +/** Reads a deltified object which uses an {@link ObjectId} to find its base. */ +class DeltaRefPackedObjectLoader extends DeltaPackedObjectLoader { + private final ObjectId deltaBase; + + DeltaRefPackedObjectLoader(final PackFile pr, final long objectOffset, + final int headerSz, final int deltaSz, final ObjectId base) { + super(pr, objectOffset, headerSz, deltaSz); + deltaBase = base; + } + + protected PackedObjectLoader getBaseLoader(final WindowCursor curs) + throws IOException { + final PackedObjectLoader or = pack.get(curs, deltaBase); + if (or == null) + throw new MissingObjectException(deltaBase, "delta base"); + return or; + } + + @Override + public int getRawType() { + return Constants.OBJ_REF_DELTA; + } + + @Override + ObjectId getDeltaBase() throws IOException { + return deltaBase; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java new file mode 100644 index 0000000000..b39efb0e8b --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2009, JetBrains s.r.o. + * Copyright (C) 2008-2009, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * Copyright (C) 2008, Thad Hughes + * 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.FileNotFoundException; +import java.io.IOException; +import java.text.MessageFormat; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.RawParseUtils; + +/** + * The configuration file that is stored in the file of the file system. + */ +public class FileBasedConfig extends Config { + private final File configFile; + private volatile long lastModified; + + /** + * Create a configuration with no default fallback. + * + * @param cfgLocation + * the location of the configuration file on the file system + */ + public FileBasedConfig(File cfgLocation) { + this(null, cfgLocation); + } + + /** + * The constructor + * + * @param base + * the base configuration file + * @param cfgLocation + * the location of the configuration file on the file system + */ + public FileBasedConfig(Config base, File cfgLocation) { + super(base); + configFile = cfgLocation; + } + + /** @return location of the configuration file on disk */ + public final File getFile() { + return configFile; + } + + /** + * Load the configuration as a Git text style configuration file. + *

+ * If the file does not exist, this configuration is cleared, and thus + * behaves the same as though the file exists, but is empty. + * + * @throws IOException + * the file could not be read (but does exist). + * @throws ConfigInvalidException + * the file is not a properly formatted configuration file. + */ + public void load() throws IOException, ConfigInvalidException { + lastModified = getFile().lastModified(); + try { + fromText(RawParseUtils.decode(IO.readFully(getFile()))); + } catch (FileNotFoundException noFile) { + clear(); + } catch (IOException e) { + final IOException e2 = new IOException(MessageFormat.format(JGitText.get().cannotReadFile, getFile())); + e2.initCause(e); + throw e2; + } catch (ConfigInvalidException e) { + throw new ConfigInvalidException(MessageFormat.format(JGitText.get().cannotReadFile, getFile()), e); + } + } + + /** + * Save the configuration as a Git text style configuration file. + *

+ * Warning: Although this method uses the traditional Git file + * locking approach to protect against concurrent writes of the + * configuration file, it does not ensure that the file has not been + * modified since the last read, which means updates performed by other + * objects accessing the same backing file may be lost. + * + * @throws IOException + * the file could not be written. + */ + public void save() throws IOException { + final byte[] out = Constants.encode(toText()); + final LockFile lf = new LockFile(getFile()); + if (!lf.lock()) + throw new IOException(MessageFormat.format(JGitText.get().cannotLockFile, getFile())); + try { + lf.setNeedStatInformation(true); + lf.write(out); + if (!lf.commit()) + throw new IOException(MessageFormat.format(JGitText.get().cannotCommitWriteTo, getFile())); + } finally { + lf.unlock(); + } + lastModified = lf.getCommitLastModified(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + getFile().getPath() + "]"; + } + + /** + * @return returns true if the currently loaded configuration file is older + * than the file on disk + */ + public boolean isOutdated() { + return getFile().lastModified() != lastModified; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java new file mode 100644 index 0000000000..6266a7c084 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2010, Google Inc. + * 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.IOException; + +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectDatabase; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.lib.PackWriter; + +abstract class FileObjectDatabase extends ObjectDatabase { + @Override + public ObjectReader newReader() { + return new WindowCursor(this); + } + + /** + * Does the requested object exist in this database? + *

+ * Alternates (if present) are searched automatically. + * + * @param objectId + * identity of the object to test for existence of. + * @return true if the specified object is stored in this database, or any + * of the alternate databases. + */ + public boolean hasObject(final AnyObjectId objectId) { + return hasObjectImpl1(objectId) || hasObjectImpl2(objectId.name()); + } + + final boolean hasObjectImpl1(final AnyObjectId objectId) { + if (hasObject1(objectId)) + return true; + + for (final AlternateHandle alt : myAlternates()) { + if (alt.db.hasObjectImpl1(objectId)) + return true; + } + + return tryAgain1() && hasObject1(objectId); + } + + final boolean hasObjectImpl2(final String objectId) { + if (hasObject2(objectId)) + return true; + + for (final AlternateHandle alt : myAlternates()) { + if (alt.db.hasObjectImpl2(objectId)) + return true; + } + + return false; + } + + /** + * Open an object from this database. + *

+ * Alternates (if present) are searched automatically. + * + * @param curs + * temporary working space associated with the calling thread. + * @param objectId + * identity of the object to open. + * @return a {@link ObjectLoader} for accessing the data of the named + * object, or null if the object does not exist. + * @throws IOException + */ + ObjectLoader openObject(final WindowCursor curs, final AnyObjectId objectId) + throws IOException { + ObjectLoader ldr; + + ldr = openObjectImpl1(curs, objectId); + if (ldr != null) + return ldr; + + ldr = openObjectImpl2(curs, objectId.name(), objectId); + if (ldr != null) + return ldr; + + return null; + } + + final ObjectLoader openObjectImpl1(final WindowCursor curs, + final AnyObjectId objectId) throws IOException { + ObjectLoader ldr; + + ldr = openObject1(curs, objectId); + if (ldr != null) + return ldr; + + for (final AlternateHandle alt : myAlternates()) { + ldr = alt.db.openObjectImpl1(curs, objectId); + if (ldr != null) + return ldr; + } + + if (tryAgain1()) { + ldr = openObject1(curs, objectId); + if (ldr != null) + return ldr; + } + + return null; + } + + final ObjectLoader openObjectImpl2(final WindowCursor curs, + final String objectName, final AnyObjectId objectId) + throws IOException { + ObjectLoader ldr; + + ldr = openObject2(curs, objectName, objectId); + if (ldr != null) + return ldr; + + for (final AlternateHandle alt : myAlternates()) { + ldr = alt.db.openObjectImpl2(curs, objectName, objectId); + if (ldr != null) + return ldr; + } + + return null; + } + + abstract void selectObjectRepresentation(PackWriter packer, + ObjectToPack otp, WindowCursor curs) throws IOException; + + abstract File getDirectory(); + + abstract AlternateHandle[] myAlternates(); + + abstract boolean tryAgain1(); + + abstract boolean hasObject1(AnyObjectId objectId); + + abstract boolean hasObject2(String objectId); + + abstract ObjectLoader openObject1(WindowCursor curs, AnyObjectId objectId) + throws IOException; + + abstract ObjectLoader openObject2(WindowCursor curs, String objectName, + AnyObjectId objectId) throws IOException; + + abstract FileObjectDatabase newCachedFileObjectDatabase(); + + static class AlternateHandle { + final FileObjectDatabase db; + + AlternateHandle(FileObjectDatabase db) { + this.db = db; + } + + void close() { + db.close(); + } + } + + static class AlternateRepository extends AlternateHandle { + final FileRepository repository; + + AlternateRepository(FileRepository r) { + super(r.getObjectDatabase()); + repository = r; + } + + void close() { + repository.close(); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepository.java new file mode 100644 index 0000000000..c86549b2e2 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepository.java @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2008-2010, Google Inc. + * Copyright (C) 2006-2010, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.IOException; +import java.text.MessageFormat; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.BaseRepositoryBuilder; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.FileObjectDatabase.AlternateHandle; +import org.eclipse.jgit.storage.file.FileObjectDatabase.AlternateRepository; +import org.eclipse.jgit.util.SystemReader; + +/** + * Represents a Git repository. A repository holds all objects and refs used for + * managing source code (could by any type of file, but source code is what + * SCM's are typically used for). + * + * In Git terms all data is stored in GIT_DIR, typically a directory called + * .git. A work tree is maintained unless the repository is a bare repository. + * Typically the .git directory is located at the root of the work dir. + * + *

    + *
  • GIT_DIR + *
      + *
    • objects/ - objects
    • + *
    • refs/ - tags and heads
    • + *
    • config - configuration
    • + *
    • info/ - more configurations
    • + *
    + *
  • + *
+ *

+ * This class is thread-safe. + *

+ * This implementation only handles a subtly undocumented subset of git features. + * + */ +public class FileRepository extends Repository { + private final FileBasedConfig userConfig; + + private final FileBasedConfig repoConfig; + + private final RefDatabase refs; + + private final ObjectDirectory objectDatabase; + + /** + * Construct a representation of a Git repository. + *

+ * The work tree, object directory, alternate object directories and index + * file locations are deduced from the given git directory and the default + * rules by running {@link FileRepositoryBuilder}. This constructor is the + * same as saying: + * + *

+	 * new FileRepositoryBuilder().setGitDir(gitDir).build()
+	 * 
+ * + * @param gitDir + * GIT_DIR (the location of the repository metadata). + * @throws IOException + * the repository appears to already exist but cannot be + * accessed. + * @see FileRepositoryBuilder + */ + public FileRepository(final File gitDir) throws IOException { + this(new FileRepositoryBuilder().setGitDir(gitDir).setup()); + } + + /** + * Create a repository using the local file system. + * + * @param options + * description of the repository's important paths. + * @throws IOException + * the user configuration file or repository configuration file + * cannot be accessed. + */ + public FileRepository(final BaseRepositoryBuilder options) throws IOException { + super(options); + + userConfig = SystemReader.getInstance().openUserConfig(getFS()); + repoConfig = new FileBasedConfig(userConfig, getFS().resolve(getDirectory(), "config")); + + loadUserConfig(); + loadRepoConfig(); + + refs = new RefDirectory(this); + objectDatabase = new ObjectDirectory(repoConfig, // + options.getObjectDirectory(), // + options.getAlternateObjectDirectories(), // + getFS()); + + if (objectDatabase.exists()) { + final String repositoryFormatVersion = getConfig().getString( + ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION); + if (!"0".equals(repositoryFormatVersion)) { + throw new IOException(MessageFormat.format( + JGitText.get().unknownRepositoryFormat2, + repositoryFormatVersion)); + } + } + } + + private void loadUserConfig() throws IOException { + try { + userConfig.load(); + } catch (ConfigInvalidException e1) { + IOException e2 = new IOException(MessageFormat.format(JGitText + .get().userConfigFileInvalid, userConfig.getFile() + .getAbsolutePath(), e1)); + e2.initCause(e1); + throw e2; + } + } + + private void loadRepoConfig() throws IOException { + try { + repoConfig.load(); + } catch (ConfigInvalidException e1) { + IOException e2 = new IOException(JGitText.get().unknownRepositoryFormat); + e2.initCause(e1); + throw e2; + } + } + + /** + * Create a new Git repository initializing the necessary files and + * directories. + * + * @param bare + * if true, a bare repository is created. + * + * @throws IOException + * in case of IO problem + */ + public void create(boolean bare) throws IOException { + final FileBasedConfig cfg = getConfig(); + if (cfg.getFile().exists()) { + throw new IllegalStateException(MessageFormat.format( + JGitText.get().repositoryAlreadyExists, getDirectory())); + } + getDirectory().mkdirs(); + refs.create(); + objectDatabase.create(); + + new File(getDirectory(), "branches").mkdir(); + + RefUpdate head = updateRef(Constants.HEAD); + head.disableRefLog(); + head.link(Constants.R_HEADS + Constants.MASTER); + + cfg.setInt(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0); + cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_FILEMODE, true); + if (bare) + cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_BARE, true); + cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, !bare); + cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF, false); + cfg.save(); + } + + /** + * @return the directory containing the objects owned by this repository. + */ + public File getObjectsDirectory() { + return objectDatabase.getDirectory(); + } + + /** + * @return the object database which stores this repository's data. + */ + public ObjectDirectory getObjectDatabase() { + return objectDatabase; + } + + /** @return the reference database which stores the reference namespace. */ + public RefDatabase getRefDatabase() { + return refs; + } + + /** + * @return the configuration of this repository + */ + public FileBasedConfig getConfig() { + if (userConfig.isOutdated()) { + try { + loadUserConfig(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + if (repoConfig.isOutdated()) { + try { + loadRepoConfig(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return repoConfig; + } + + /** + * Objects known to exist but not expressed by {@link #getAllRefs()}. + *

+ * When a repository borrows objects from another repository, it can + * advertise that it safely has that other repository's references, without + * exposing any other details about the other repository. This may help + * a client trying to push changes avoid pushing more than it needs to. + * + * @return unmodifiable collection of other known objects. + */ + public Set getAdditionalHaves() { + HashSet r = new HashSet(); + for (AlternateHandle d : objectDatabase. myAlternates()) { + if (d instanceof AlternateRepository) { + Repository repo; + + repo = ((AlternateRepository) d).repository; + for (Ref ref : repo.getAllRefs().values()) + r.add(ref.getObjectId()); + r.addAll(repo.getAdditionalHaves()); + } + } + return r; + } + + /** + * Add a single existing pack to the list of available pack files. + * + * @param pack + * path of the pack file to open. + * @param idx + * path of the corresponding index file. + * @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 { + objectDatabase.openPack(pack, idx); + } + + /** + * Force a scan for changed refs. + * + * @throws IOException + */ + public void scanForRepoChanges() throws IOException { + getAllRefs(); // This will look for changes to refs + if (!isBare()) + getIndex(); // This will detect changes in the index + } + + /** + * @param refName + * @return a {@link ReflogReader} for the supplied refname, or null if the + * named ref does not exist. + * @throws IOException the ref could not be accessed. + */ + public ReflogReader getReflogReader(String refName) throws IOException { + Ref ref = getRef(refName); + if (ref != null) + return new ReflogReader(this, ref.getName()); + return null; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java new file mode 100644 index 0000000000..31d3e99ad6 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2010, Google Inc. + * 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.IOException; + +import org.eclipse.jgit.lib.BaseRepositoryBuilder; + +/** + * Constructs a {@link FileRepository}. + *

+ * Applications must set one of {@link #setGitDir(File)} or + * {@link #setWorkTree(File)}, or use {@link #readEnvironment()} or + * {@link #findGitDir()} in order to configure the minimum property set + * necessary to open a repository. + *

+ * Single repository applications trying to be compatible with other Git + * implementations are encouraged to use a model such as: + * + *

+ * new FileRepositoryBuilder() //
+ * 		.setGitDir(gitDirArgument) // --git-dir if supplied, no-op if null
+ * 		.readEnviroment() // scan environment GIT_* variables
+ * 		.findGitDir() // scan up the file system tree
+ * 		.build()
+ * 
+ */ +public class FileRepositoryBuilder extends + BaseRepositoryBuilder { + /** + * Create a repository matching the configuration in this builder. + *

+ * If an option was not set, the build method will try to default the option + * based on other options. If insufficient information is available, an + * exception is thrown to the caller. + * + * @return a repository matching this configuration. + * @throws IllegalArgumentException + * insufficient parameters were set. + * @throws IOException + * the repository could not be accessed to configure the rest of + * the builder's parameters. + */ + @Override + public FileRepository build() throws IOException { + return new FileRepository(setup()); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java new file mode 100644 index 0000000000..4949b507eb --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2010, Google Inc. + * 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.IOException; + +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.StoredObjectRepresentation; + +class LocalObjectRepresentation extends StoredObjectRepresentation { + final PackedObjectLoader ldr; + + LocalObjectRepresentation(PackedObjectLoader ldr) { + this.ldr = ldr; + } + + @Override + public int getFormat() { + if (ldr instanceof DeltaPackedObjectLoader) + return PACK_DELTA; + if (ldr instanceof WholePackedObjectLoader) + return PACK_WHOLE; + return FORMAT_OTHER; + } + + @Override + public int getWeight() { + long sz = ldr.getRawSize(); + if (Integer.MAX_VALUE < sz) + return WEIGHT_UNKNOWN; + return (int) sz; + } + + @Override + public ObjectId getDeltaBase() { + try { + return ldr.getDeltaBase(); + } catch (IOException e) { + return null; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java new file mode 100644 index 0000000000..9ee43f398d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2010, Google Inc. + * 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 org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.lib.StoredObjectRepresentation; +import org.eclipse.jgit.revwalk.RevObject; + +/** {@link ObjectToPack} for {@link ObjectDirectory}. */ +class LocalObjectToPack extends ObjectToPack { + /** Pack to reuse compressed data from, otherwise null. */ + PackFile copyFromPack; + + /** Offset of the object's header in {@link #copyFromPack}. */ + long copyOffset; + + LocalObjectToPack(RevObject obj) { + super(obj); + } + + @Override + public void select(StoredObjectRepresentation ref) { + LocalObjectRepresentation ptr = (LocalObjectRepresentation)ref; + this.copyFromPack = ptr.ldr.pack; + this.copyOffset = ptr.ldr.objectOffset; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java new file mode 100644 index 0000000000..ad89a24847 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; +import java.text.MessageFormat; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; + +/** + * Git style file locking and replacement. + *

+ * To modify a ref file Git tries to use an atomic update approach: we write the + * new data into a brand new file, then rename it in place over the old name. + * This way we can just delete the temporary file if anything goes wrong, and + * nothing has been damaged. To coordinate access from multiple processes at + * once Git tries to atomically create the new temporary file under a well-known + * name. + */ +public class LockFile { + static final String SUFFIX = ".lock"; //$NON-NLS-1$ + + /** Filter to skip over active lock files when listing a directory. */ + static final FilenameFilter FILTER = new FilenameFilter() { + public boolean accept(File dir, String name) { + return !name.endsWith(SUFFIX); + } + }; + + private final File ref; + + private final File lck; + + private FileLock fLck; + + private boolean haveLck; + + private FileOutputStream os; + + private boolean needStatInformation; + + private long commitLastModified; + + /** + * Create a new lock for any file. + * + * @param f + * the file that will be locked. + */ + public LockFile(final File f) { + ref = f; + lck = new File(ref.getParentFile(), ref.getName() + SUFFIX); + } + + /** + * Try to establish the lock. + * + * @return true if the lock is now held by the caller; false if it is held + * by someone else. + * @throws IOException + * the temporary output file could not be created. The caller + * does not hold the lock. + */ + public boolean lock() throws IOException { + lck.getParentFile().mkdirs(); + if (lck.createNewFile()) { + haveLck = true; + try { + os = new FileOutputStream(lck); + try { + fLck = os.getChannel().tryLock(); + if (fLck == null) + throw new OverlappingFileLockException(); + } catch (OverlappingFileLockException ofle) { + // We cannot use unlock() here as this file is not + // held by us, but we thought we created it. We must + // not delete it, as it belongs to some other process. + // + haveLck = false; + try { + os.close(); + } catch (IOException ioe) { + // Fail by returning haveLck = false. + } + os = null; + } + } catch (IOException ioe) { + unlock(); + throw ioe; + } + } + return haveLck; + } + + /** + * Try to establish the lock for appending. + * + * @return true if the lock is now held by the caller; false if it is held + * by someone else. + * @throws IOException + * the temporary output file could not be created. The caller + * does not hold the lock. + */ + public boolean lockForAppend() throws IOException { + if (!lock()) + return false; + copyCurrentContent(); + return true; + } + + /** + * Copy the current file content into the temporary file. + *

+ * This method saves the current file content by inserting it into the + * temporary file, so that the caller can safely append rather than replace + * the primary file. + *

+ * This method does nothing if the current file does not exist, or exists + * but is empty. + * + * @throws IOException + * the temporary file could not be written, or a read error + * occurred while reading from the current file. The lock is + * released before throwing the underlying IO exception to the + * caller. + * @throws RuntimeException + * the temporary file could not be written. The lock is released + * before throwing the underlying exception to the caller. + */ + public void copyCurrentContent() throws IOException { + requireLock(); + try { + final FileInputStream fis = new FileInputStream(ref); + try { + final byte[] buf = new byte[2048]; + int r; + while ((r = fis.read(buf)) >= 0) + os.write(buf, 0, r); + } finally { + fis.close(); + } + } catch (FileNotFoundException fnfe) { + // Don't worry about a file that doesn't exist yet, it + // conceptually has no current content to copy. + // + } catch (IOException ioe) { + unlock(); + throw ioe; + } catch (RuntimeException ioe) { + unlock(); + throw ioe; + } catch (Error ioe) { + unlock(); + throw ioe; + } + } + + /** + * Write an ObjectId and LF to the temporary file. + * + * @param id + * the id to store in the file. The id will be written in hex, + * followed by a sole LF. + * @throws IOException + * the temporary file could not be written. The lock is released + * before throwing the underlying IO exception to the caller. + * @throws RuntimeException + * the temporary file could not be written. The lock is released + * before throwing the underlying exception to the caller. + */ + public void write(final ObjectId id) throws IOException { + requireLock(); + try { + final BufferedOutputStream b; + b = new BufferedOutputStream(os, Constants.OBJECT_ID_STRING_LENGTH + 1); + id.copyTo(b); + b.write('\n'); + b.flush(); + fLck.release(); + b.close(); + os = null; + } catch (IOException ioe) { + unlock(); + throw ioe; + } catch (RuntimeException ioe) { + unlock(); + throw ioe; + } catch (Error ioe) { + unlock(); + throw ioe; + } + } + + /** + * Write arbitrary data to the temporary file. + * + * @param content + * the bytes to store in the temporary file. No additional bytes + * are added, so if the file must end with an LF it must appear + * at the end of the byte array. + * @throws IOException + * the temporary file could not be written. The lock is released + * before throwing the underlying IO exception to the caller. + * @throws RuntimeException + * the temporary file could not be written. The lock is released + * before throwing the underlying exception to the caller. + */ + public void write(final byte[] content) throws IOException { + requireLock(); + try { + os.write(content); + os.flush(); + fLck.release(); + os.close(); + os = null; + } catch (IOException ioe) { + unlock(); + throw ioe; + } catch (RuntimeException ioe) { + unlock(); + throw ioe; + } catch (Error ioe) { + unlock(); + throw ioe; + } + } + + /** + * Obtain the direct output stream for this lock. + *

+ * The stream may only be accessed once, and only after {@link #lock()} has + * been successfully invoked and returned true. Callers must close the + * stream prior to calling {@link #commit()} to commit the change. + * + * @return a stream to write to the new file. The stream is unbuffered. + */ + public OutputStream getOutputStream() { + requireLock(); + return new OutputStream() { + @Override + public void write(final byte[] b, final int o, final int n) + throws IOException { + os.write(b, o, n); + } + + @Override + public void write(final byte[] b) throws IOException { + os.write(b); + } + + @Override + public void write(final int b) throws IOException { + os.write(b); + } + + @Override + public void flush() throws IOException { + os.flush(); + } + + @Override + public void close() throws IOException { + try { + os.flush(); + fLck.release(); + os.close(); + os = null; + } catch (IOException ioe) { + unlock(); + throw ioe; + } catch (RuntimeException ioe) { + unlock(); + throw ioe; + } catch (Error ioe) { + unlock(); + throw ioe; + } + } + }; + } + + private void requireLock() { + if (os == null) { + unlock(); + throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotHeld, ref)); + } + } + + /** + * Request that {@link #commit()} remember modification time. + * + * @param on + * true if the commit method must remember the modification time. + */ + public void setNeedStatInformation(final boolean on) { + needStatInformation = on; + } + + /** + * Wait until the lock file information differs from the old file. + *

+ * This method tests both the length and the last modification date. If both + * are the same, this method sleeps until it can force the new lock file's + * modification date to be later than the target file. + * + * @throws InterruptedException + * the thread was interrupted before the last modified date of + * the lock file was different from the last modified date of + * the target file. + */ + public void waitForStatChange() throws InterruptedException { + if (ref.length() == lck.length()) { + long otime = ref.lastModified(); + long ntime = lck.lastModified(); + while (otime == ntime) { + Thread.sleep(25 /* milliseconds */); + lck.setLastModified(System.currentTimeMillis()); + ntime = lck.lastModified(); + } + } + } + + /** + * Commit this change and release the lock. + *

+ * If this method fails (returns false) the lock is still released. + * + * @return true if the commit was successful and the file contains the new + * data; false if the commit failed and the file remains with the + * old data. + * @throws IllegalStateException + * the lock is not held. + */ + public boolean commit() { + if (os != null) { + unlock(); + throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotClosed, ref)); + } + + saveStatInformation(); + if (lck.renameTo(ref)) + return true; + if (!ref.exists() || ref.delete()) + if (lck.renameTo(ref)) + return true; + unlock(); + return false; + } + + private void saveStatInformation() { + if (needStatInformation) + commitLastModified = lck.lastModified(); + } + + /** + * Get the modification time of the output file when it was committed. + * + * @return modification time of the lock file right before we committed it. + */ + public long getCommitLastModified() { + return commitLastModified; + } + + /** + * Unlock this file and abort this change. + *

+ * The temporary file (if created) is deleted before returning. + */ + public void unlock() { + if (os != null) { + if (fLck != null) { + try { + fLck.release(); + } catch (IOException ioe) { + // Huh? + } + fLck = null; + } + try { + os.close(); + } catch (IOException ioe) { + // Ignore this + } + os = null; + } + + if (haveLck) { + haveLck = false; + lck.delete(); + } + } + + @Override + public String toString() { + return "LockFile[" + lck + ", haveLck=" + haveLck + "]"; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java new file mode 100644 index 0000000000..8a8055605a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java @@ -0,0 +1,630 @@ +/* + * Copyright (C) 2009, Google Inc. + * 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.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.PackMismatchException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ObjectDatabase; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.lib.PackWriter; +import org.eclipse.jgit.lib.RepositoryCache; +import org.eclipse.jgit.lib.RepositoryCache.FileKey; +import org.eclipse.jgit.util.FS; + +/** + * Traditional file system based {@link ObjectDatabase}. + *

+ * This is the classical object database representation for a Git repository, + * where objects are stored loose by hashing them into directories by their + * {@link ObjectId}, or are stored in compressed containers known as + * {@link PackFile}s. + *

+ * Optionally an object database can reference one or more alternates; other + * ObjectDatabase instances that are searched in addition to the current + * database. + *

+ * Databases are divided into two halves: a half that is considered to be fast + * to search (the {@code PackFile}s), and a half that is considered to be slow + * to search (loose objects). When alternates are present the fast half is fully + * searched (recursively through all alternates) before the slow half is + * considered. + */ +public class ObjectDirectory extends FileObjectDatabase { + private static final PackList NO_PACKS = new PackList(-1, -1, new PackFile[0]); + + private final Config config; + + private final File objects; + + private final File infoDirectory; + + private final File packDirectory; + + private final File alternatesFile; + + private final AtomicReference packList; + + private final FS fs; + + private final AtomicReference alternates; + + /** + * Initialize a reference to an on-disk object directory. + * + * @param cfg + * configuration this directory consults for write settings. + * @param dir + * the location of the objects directory. + * @param alternatePaths + * a list of alternate object directories + * @param fs + * the file system abstraction which will be necessary to perform + * certain file system operations. + * @throws IOException + * an alternate object cannot be opened. + */ + public ObjectDirectory(final Config cfg, final File dir, + File[] alternatePaths, FS fs) throws IOException { + config = cfg; + objects = dir; + infoDirectory = new File(objects, "info"); + packDirectory = new File(objects, "pack"); + alternatesFile = new File(infoDirectory, "alternates"); + packList = new AtomicReference(NO_PACKS); + this.fs = fs; + + alternates = new AtomicReference(); + if (alternatePaths != null) { + AlternateHandle[] alt; + + alt = new AlternateHandle[alternatePaths.length]; + for (int i = 0; i < alternatePaths.length; i++) + alt[i] = openAlternate(alternatePaths[i]); + alternates.set(alt); + } + } + + /** + * @return the location of the objects directory. + */ + public final File getDirectory() { + return objects; + } + + @Override + public boolean exists() { + return objects.exists(); + } + + @Override + public void create() throws IOException { + objects.mkdirs(); + infoDirectory.mkdir(); + packDirectory.mkdir(); + } + + @Override + public ObjectInserter newInserter() { + return new ObjectDirectoryInserter(this, config); + } + + @Override + public void close() { + final PackList packs = packList.get(); + packList.set(NO_PACKS); + for (final PackFile p : packs.packs) + p.close(); + + // Fully close all loaded alternates and clear the alternate list. + AlternateHandle[] alt = alternates.get(); + if (alt != null) { + alternates.set(null); + for(final AlternateHandle od : alt) + od.close(); + } + } + + /** + * Compute the location of a loose object file. + * + * @param objectId + * identity of the loose object to map to the directory. + * @return location of the object, if it were to exist as a loose object. + */ + public File fileFor(final AnyObjectId objectId) { + return fileFor(objectId.name()); + } + + private File fileFor(final String objectName) { + final String d = objectName.substring(0, 2); + final String f = objectName.substring(2); + return new File(new File(objects, d), f); + } + + /** + * @return unmodifiable collection of all known pack files local to this + * directory. Most recent packs are presented first. Packs most + * likely to contain more recent objects appear before packs + * containing objects referenced by commits further back in the + * history of the repository. + */ + public Collection getPacks() { + final PackFile[] packs = packList.get().packs; + return Collections.unmodifiableCollection(Arrays.asList(packs)); + } + + /** + * Add a single existing pack to the list of available pack files. + * + * @param pack + * path of the pack file to open. + * @param idx + * path of the corresponding index file. + * @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 { + final String p = pack.getName(); + final String i = idx.getName(); + + if (p.length() != 50 || !p.startsWith("pack-") || !p.endsWith(".pack")) + throw new IOException(MessageFormat.format(JGitText.get().notAValidPack, pack)); + + if (i.length() != 49 || !i.startsWith("pack-") || !i.endsWith(".idx")) + throw new IOException(MessageFormat.format(JGitText.get().notAValidPack, idx)); + + if (!p.substring(0, 45).equals(i.substring(0, 45))) + throw new IOException(MessageFormat.format(JGitText.get().packDoesNotMatchIndex, pack)); + + insertPack(new PackFile(idx, pack)); + } + + @Override + public String toString() { + return "ObjectDirectory[" + getDirectory() + "]"; + } + + boolean hasObject1(final AnyObjectId objectId) { + for (final PackFile p : packList.get().packs) { + try { + if (p.hasObject(objectId)) { + return true; + } + } catch (IOException e) { + // The hasObject call should have only touched the index, + // so any failure here indicates the index is unreadable + // by this process, and the pack is likewise not readable. + // + removePack(p); + continue; + } + } + return false; + } + + ObjectLoader openObject1(final WindowCursor curs, + final AnyObjectId objectId) throws IOException { + PackList pList = packList.get(); + SEARCH: for (;;) { + for (final PackFile p : pList.packs) { + try { + final PackedObjectLoader ldr = p.get(curs, objectId); + if (ldr != null) { + ldr.materialize(curs); + return ldr; + } + } catch (PackMismatchException e) { + // Pack was modified; refresh the entire pack list. + // + pList = scanPacks(pList); + continue SEARCH; + } catch (IOException e) { + // Assume the pack is corrupted. + // + removePack(p); + } + } + return null; + } + } + + @Override + void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, + WindowCursor curs) throws IOException { + PackList pList = packList.get(); + SEARCH: for (;;) { + for (final PackFile p : pList.packs) { + try { + PackedObjectLoader ldr = p.get(curs, otp); + if (ldr != null) + packer.select(otp, new LocalObjectRepresentation(ldr)); + } catch (PackMismatchException e) { + // Pack was modified; refresh the entire pack list. + // + pList = scanPacks(pList); + continue SEARCH; + } catch (IOException e) { + // Assume the pack is corrupted. + // + removePack(p); + } + } + break SEARCH; + } + + for (AlternateHandle h : myAlternates()) + h.db.selectObjectRepresentation(packer, otp, curs); + } + + boolean hasObject2(final String objectName) { + return fileFor(objectName).exists(); + } + + ObjectLoader openObject2(final WindowCursor curs, + final String objectName, final AnyObjectId objectId) + throws IOException { + try { + return new UnpackedObjectLoader(fileFor(objectName), objectId); + } catch (FileNotFoundException noFile) { + return null; + } + } + + boolean tryAgain1() { + final PackList old = packList.get(); + if (old.tryAgain(packDirectory.lastModified())) + return old != scanPacks(old); + return false; + } + + private void insertPack(final PackFile pf) { + PackList o, n; + do { + o = packList.get(); + + // If the pack in question is already present in the list + // (picked up by a concurrent thread that did a scan?) we + // do not want to insert it a second time. + // + final PackFile[] oldList = o.packs; + final String name = pf.getPackFile().getName(); + for (PackFile p : oldList) { + if (PackFile.SORT.compare(pf, p) < 0) + break; + if (name.equals(p.getPackFile().getName())) + return; + } + + final PackFile[] newList = new PackFile[1 + oldList.length]; + newList[0] = pf; + System.arraycopy(oldList, 0, newList, 1, oldList.length); + n = new PackList(o.lastRead, o.lastModified, newList); + } while (!packList.compareAndSet(o, n)); + } + + private void removePack(final PackFile deadPack) { + PackList o, n; + do { + o = packList.get(); + + final PackFile[] oldList = o.packs; + final int j = indexOf(oldList, deadPack); + if (j < 0) + break; + + final PackFile[] newList = new PackFile[oldList.length - 1]; + System.arraycopy(oldList, 0, newList, 0, j); + System.arraycopy(oldList, j + 1, newList, j, newList.length - j); + n = new PackList(o.lastRead, o.lastModified, newList); + } while (!packList.compareAndSet(o, n)); + deadPack.close(); + } + + private static int indexOf(final PackFile[] list, final PackFile pack) { + for (int i = 0; i < list.length; i++) { + if (list[i] == pack) + return i; + } + return -1; + } + + private PackList scanPacks(final PackList original) { + synchronized (packList) { + PackList o, n; + do { + o = packList.get(); + if (o != original) { + // Another thread did the scan for us, while we + // were blocked on the monitor above. + // + return o; + } + n = scanPacksImpl(o); + if (n == o) + return n; + } while (!packList.compareAndSet(o, n)); + return n; + } + } + + private PackList scanPacksImpl(final PackList old) { + final Map forReuse = reuseMap(old); + final long lastRead = System.currentTimeMillis(); + final long lastModified = packDirectory.lastModified(); + final Set names = listPackDirectory(); + final List list = new ArrayList(names.size() >> 2); + boolean foundNew = false; + for (final String indexName : names) { + // Must match "pack-[0-9a-f]{40}.idx" to be an index. + // + if (indexName.length() != 49 || !indexName.endsWith(".idx")) + continue; + + final String base = indexName.substring(0, indexName.length() - 4); + final String packName = base + ".pack"; + if (!names.contains(packName)) { + // Sometimes C Git's HTTP fetch transport leaves a + // .idx file behind and does not download the .pack. + // We have to skip over such useless indexes. + // + continue; + } + + final PackFile oldPack = forReuse.remove(packName); + if (oldPack != null) { + list.add(oldPack); + continue; + } + + final File packFile = new File(packDirectory, packName); + final File idxFile = new File(packDirectory, indexName); + list.add(new PackFile(idxFile, packFile)); + foundNew = true; + } + + // If we did not discover any new files, the modification time was not + // changed, and we did not remove any files, then the set of files is + // the same as the set we were given. Instead of building a new object + // return the same collection. + // + if (!foundNew && lastModified == old.lastModified && forReuse.isEmpty()) + return old.updateLastRead(lastRead); + + for (final PackFile p : forReuse.values()) { + p.close(); + } + + if (list.isEmpty()) + return new PackList(lastRead, lastModified, NO_PACKS.packs); + + final PackFile[] r = list.toArray(new PackFile[list.size()]); + Arrays.sort(r, PackFile.SORT); + return new PackList(lastRead, lastModified, r); + } + + private static Map reuseMap(final PackList old) { + final Map forReuse = new HashMap(); + for (final PackFile p : old.packs) { + if (p.invalid()) { + // The pack instance is corrupted, and cannot be safely used + // again. Do not include it in our reuse map. + // + p.close(); + continue; + } + + final PackFile prior = forReuse.put(p.getPackFile().getName(), p); + if (prior != null) { + // This should never occur. It should be impossible for us + // to have two pack files with the same name, as all of them + // came out of the same directory. If it does, we promised to + // close any PackFiles we did not reuse, so close the second, + // readers are likely to be actively using the first. + // + forReuse.put(prior.getPackFile().getName(), prior); + p.close(); + } + } + return forReuse; + } + + private Set listPackDirectory() { + final String[] nameList = packDirectory.list(); + if (nameList == null) + return Collections.emptySet(); + final Set nameSet = new HashSet(nameList.length << 1); + for (final String name : nameList) { + if (name.startsWith("pack-")) + nameSet.add(name); + } + return nameSet; + } + + AlternateHandle[] myAlternates() { + AlternateHandle[] alt = alternates.get(); + if (alt == null) { + synchronized (alternates) { + alt = alternates.get(); + if (alt == null) { + try { + alt = loadAlternates(); + } catch (IOException e) { + alt = new AlternateHandle[0]; + } + alternates.set(alt); + } + } + } + return alt; + } + + private AlternateHandle[] loadAlternates() throws IOException { + final List l = new ArrayList(4); + final BufferedReader br = open(alternatesFile); + try { + String line; + while ((line = br.readLine()) != null) { + l.add(openAlternate(line)); + } + } finally { + br.close(); + } + return l.toArray(new AlternateHandle[l.size()]); + } + + private static BufferedReader open(final File f) + throws FileNotFoundException { + return new BufferedReader(new FileReader(f)); + } + + private AlternateHandle openAlternate(final String location) + throws IOException { + final File objdir = fs.resolve(objects, location); + return openAlternate(objdir); + } + + private AlternateHandle openAlternate(File objdir) throws IOException { + final File parent = objdir.getParentFile(); + if (FileKey.isGitRepository(parent, fs)) { + FileKey key = FileKey.exact(parent, fs); + FileRepository db = (FileRepository) RepositoryCache.open(key); + return new AlternateRepository(db); + } + + ObjectDirectory db = new ObjectDirectory(config, objdir, null, fs); + return new AlternateHandle(db); + } + + private static final class PackList { + /** Last wall-clock time the directory was read. */ + volatile long lastRead; + + /** Last modification time of {@link ObjectDirectory#packDirectory}. */ + final long lastModified; + + /** All known packs, sorted by {@link PackFile#SORT}. */ + final PackFile[] packs; + + private boolean cannotBeRacilyClean; + + PackList(final long lastRead, final long lastModified, + final PackFile[] packs) { + this.lastRead = lastRead; + this.lastModified = lastModified; + this.packs = packs; + this.cannotBeRacilyClean = notRacyClean(lastRead); + } + + private boolean notRacyClean(final long read) { + return read - lastModified > 2 * 60 * 1000L; + } + + PackList updateLastRead(final long now) { + if (notRacyClean(now)) + cannotBeRacilyClean = true; + lastRead = now; + return this; + } + + boolean tryAgain(final long currLastModified) { + // Any difference indicates the directory was modified. + // + if (lastModified != currLastModified) + return true; + + // We have already determined the last read was far enough + // after the last modification that any new modifications + // are certain to change the last modified time. + // + if (cannotBeRacilyClean) + return false; + + if (notRacyClean(lastRead)) { + // Our last read should have marked cannotBeRacilyClean, + // but this thread may not have seen the change. The read + // of the volatile field lastRead should have fixed that. + // + return false; + } + + // We last read this directory too close to its last observed + // modification time. We may have missed a modification. Scan + // the directory again, to ensure we still see the same state. + // + return true; + } + } + + @Override + public ObjectDatabase newCachedDatabase() { + return newCachedFileObjectDatabase(); + } + + FileObjectDatabase newCachedFileObjectDatabase() { + return new CachedObjectDirectory(this); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java new file mode 100644 index 0000000000..e6ed54022f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * Copyright (C) 2009, Google Inc. + * 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.EOFException; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.DigestOutputStream; +import java.security.MessageDigest; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; + +import org.eclipse.jgit.errors.ObjectWritingException; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.CoreConfig; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; + +/** Creates loose objects in a {@link ObjectDirectory}. */ +class ObjectDirectoryInserter extends ObjectInserter { + private final ObjectDirectory db; + + private final Config config; + + private Deflater deflate; + + ObjectDirectoryInserter(final ObjectDirectory dest, final Config cfg) { + db = dest; + config = cfg; + } + + @Override + public ObjectId insert(final int type, long len, final InputStream is) + throws IOException { + final MessageDigest md = digest(); + final File tmp = toTemp(md, type, len, is); + final ObjectId id = ObjectId.fromRaw(md.digest()); + if (db.hasObject(id)) { + // Object is already in the repository, remove temporary file. + // + tmp.delete(); + return id; + } + + final File dst = db.fileFor(id); + if (tmp.renameTo(dst)) + return id; + + // 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. + // + dst.getParentFile().mkdir(); + if (tmp.renameTo(dst)) + return id; + + if (db.hasObject(id)) { + tmp.delete(); + return id; + } + + // The object failed to be renamed into its proper + // location and it doesn't exist in the repository + // either. We really don't know what went wrong, so + // fail. + // + tmp.delete(); + throw new ObjectWritingException("Unable to create new object: " + dst); + } + + @Override + public void flush() throws IOException { + // Do nothing. Objects are immediately visible. + } + + @Override + public void release() { + if (deflate != null) { + try { + deflate.end(); + } finally { + deflate = null; + } + } + } + + private File toTemp(final MessageDigest md, final int type, long len, + final InputStream is) throws IOException, FileNotFoundException, + Error { + boolean delete = true; + File tmp = File.createTempFile("noz", null, db.getDirectory()); + try { + DigestOutputStream dOut = new DigestOutputStream( + compress(new FileOutputStream(tmp)), md); + try { + dOut.write(Constants.encodedTypeString(type)); + dOut.write((byte) ' '); + dOut.write(Constants.encodeASCII(len)); + dOut.write((byte) 0); + + final byte[] buf = buffer(); + while (len > 0) { + int n = is.read(buf, 0, (int) Math.min(len, buf.length)); + if (n <= 0) + throw shortInput(len); + dOut.write(buf, 0, n); + len -= n; + } + } finally { + dOut.close(); + } + + tmp.setReadOnly(); + delete = false; + return tmp; + } finally { + if (delete) + tmp.delete(); + } + } + + private DeflaterOutputStream compress(final OutputStream out) { + if (deflate == null) + deflate = new Deflater(config.get(CoreConfig.KEY).getCompression()); + else + deflate.reset(); + return new DeflaterOutputStream(out, deflate); + } + + private static EOFException shortInput(long missing) { + return new EOFException("Input did not match supplied length. " + + missing + " bytes are missing."); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java new file mode 100644 index 0000000000..e5f6f03f40 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java @@ -0,0 +1,700 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.EOFException; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel.MapMode; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.zip.CRC32; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.PackInvalidException; +import org.eclipse.jgit.errors.PackMismatchException; +import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.lib.PackOutputStream; +import org.eclipse.jgit.util.LongList; +import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.RawParseUtils; + +/** + * A Git version 2 pack file representation. A pack file contains Git objects in + * delta packed format yielding high compression of lots of object where some + * objects are similar. + */ +public class PackFile implements Iterable { + /** Sorts PackFiles to be most recently created to least recently created. */ + public static Comparator SORT = new Comparator() { + public int compare(final PackFile a, final PackFile b) { + return b.packLastModified - a.packLastModified; + } + }; + + private final File idxFile; + + private final File packFile; + + final int hash; + + private RandomAccessFile fd; + + /** Serializes reads performed against {@link #fd}. */ + private final Object readLock = new Object(); + + long length; + + private int activeWindows; + + private int activeCopyRawData; + + private int packLastModified; + + private volatile boolean invalid; + + private byte[] packChecksum; + + private PackIndex loadedIdx; + + private PackReverseIndex reverseIdx; + + /** + * Objects we have tried to read, and discovered to be corrupt. + *

+ * The list is allocated after the first corruption is found, and filled in + * as more entries are discovered. Typically this list is never used, as + * pack files do not usually contain corrupt objects. + */ + private volatile LongList corruptObjects; + + /** + * Construct a reader for an existing, pre-indexed packfile. + * + * @param idxFile + * path of the .idx file listing the contents. + * @param packFile + * path of the .pack file holding the data. + */ + public PackFile(final File idxFile, final File packFile) { + this.idxFile = idxFile; + this.packFile = packFile; + this.packLastModified = (int) (packFile.lastModified() >> 10); + + // Multiply by 31 here so we can more directly combine with another + // value in WindowCache.hash(), without doing the multiply there. + // + hash = System.identityHashCode(this) * 31; + length = Long.MAX_VALUE; + } + + private synchronized PackIndex idx() throws IOException { + if (loadedIdx == null) { + if (invalid) + throw new PackInvalidException(packFile); + + try { + final PackIndex idx = PackIndex.open(idxFile); + + if (packChecksum == null) + packChecksum = idx.packChecksum; + else if (!Arrays.equals(packChecksum, idx.packChecksum)) + throw new PackMismatchException(JGitText.get().packChecksumMismatch); + + loadedIdx = idx; + } catch (IOException e) { + invalid = true; + throw e; + } + } + return loadedIdx; + } + + final PackedObjectLoader resolveBase(final WindowCursor curs, final long ofs) + throws IOException { + if (isCorrupt(ofs)) { + throw new CorruptObjectException(MessageFormat.format(JGitText + .get().objectAtHasBadZlibStream, ofs, getPackFile())); + } + return reader(curs, ofs); + } + + /** @return the File object which locates this pack on disk. */ + public File getPackFile() { + return packFile; + } + + /** + * Determine if an object is contained within the pack file. + *

+ * For performance reasons only the index file is searched; the main pack + * content is ignored entirely. + *

+ * + * @param id + * the object to look for. Must not be null. + * @return true if the object is in this pack; false otherwise. + * @throws IOException + * the index file cannot be loaded into memory. + */ + public boolean hasObject(final AnyObjectId id) throws IOException { + final long offset = idx().findOffset(id); + return 0 < offset && !isCorrupt(offset); + } + + /** + * Get an object from this pack. + * + * @param curs + * temporary working space associated with the calling thread. + * @param id + * the object to obtain from the pack. Must not be null. + * @return the object loader for the requested object if it is contained in + * this pack; null if the object was not found. + * @throws IOException + * the pack file or the index could not be read. + */ + public PackedObjectLoader get(final WindowCursor curs, final AnyObjectId id) + throws IOException { + final long offset = idx().findOffset(id); + return 0 < offset && !isCorrupt(offset) ? reader(curs, offset) : null; + } + + /** + * Close the resources utilized by this repository + */ + public void close() { + UnpackedObjectCache.purge(this); + WindowCache.purge(this); + synchronized (this) { + loadedIdx = null; + reverseIdx = null; + } + } + + /** + * Provide iterator over entries in associated pack index, that should also + * exist in this pack file. Objects returned by such iterator are mutable + * during iteration. + *

+ * Iterator returns objects in SHA-1 lexicographical order. + *

+ * + * @return iterator over entries of associated pack index + * + * @see PackIndex#iterator() + */ + public Iterator iterator() { + try { + return idx().iterator(); + } catch (IOException e) { + return Collections. emptyList().iterator(); + } + } + + /** + * Obtain the total number of objects available in this pack. This method + * relies on pack index, giving number of effectively available objects. + * + * @return number of objects in index of this pack, likewise in this pack + * @throws IOException + * the index file cannot be loaded into memory. + */ + long getObjectCount() throws IOException { + return idx().getObjectCount(); + } + + /** + * Search for object id with the specified start offset in associated pack + * (reverse) index. + * + * @param offset + * start offset of object to find + * @return object id for this offset, or null if no object was found + * @throws IOException + * the index file cannot be loaded into memory. + */ + ObjectId findObjectForOffset(final long offset) throws IOException { + return getReverseIdx().findObject(offset); + } + + final UnpackedObjectCache.Entry readCache(final long position) { + return UnpackedObjectCache.get(this, position); + } + + final void saveCache(final long position, final byte[] data, final int type) { + UnpackedObjectCache.store(this, position, data, type); + } + + final byte[] decompress(final long position, final int totalSize, + final WindowCursor curs) throws DataFormatException, IOException { + final byte[] dstbuf = new byte[totalSize]; + if (curs.inflate(this, position, dstbuf, 0) != totalSize) + throw new EOFException(MessageFormat.format(JGitText.get().shortCompressedStreamAt, position)); + return dstbuf; + } + + final void copyAsIs(PackOutputStream out, LocalObjectToPack src, + WindowCursor curs) throws IOException, + StoredObjectRepresentationNotAvailableException { + beginCopyAsIs(src); + try { + copyAsIs2(out, src, curs); + } finally { + endCopyAsIs(); + } + } + + private void copyAsIs2(PackOutputStream out, LocalObjectToPack src, + WindowCursor curs) throws IOException, + StoredObjectRepresentationNotAvailableException { + final CRC32 crc1 = new CRC32(); + final CRC32 crc2 = new CRC32(); + final byte[] buf = out.getCopyBuffer(); + + // Rip apart the header so we can discover the size. + // + readFully(src.copyOffset, buf, 0, 20, curs); + int c = buf[0] & 0xff; + final int typeCode = (c >> 4) & 7; + long inflatedLength = c & 15; + int shift = 4; + int headerCnt = 1; + while ((c & 0x80) != 0) { + c = buf[headerCnt++] & 0xff; + inflatedLength += (c & 0x7f) << shift; + shift += 7; + } + + if (typeCode == Constants.OBJ_OFS_DELTA) { + do { + c = buf[headerCnt++] & 0xff; + } while ((c & 128) != 0); + crc1.update(buf, 0, headerCnt); + crc2.update(buf, 0, headerCnt); + } else if (typeCode == Constants.OBJ_REF_DELTA) { + crc1.update(buf, 0, headerCnt); + crc2.update(buf, 0, headerCnt); + + readFully(src.copyOffset + headerCnt, buf, 0, 20, curs); + crc1.update(buf, 0, 20); + crc2.update(buf, 0, headerCnt); + headerCnt += 20; + } else { + crc1.update(buf, 0, headerCnt); + crc2.update(buf, 0, headerCnt); + } + + final long dataOffset = src.copyOffset + headerCnt; + final long dataLength; + final long expectedCRC; + final ByteArrayWindow quickCopy; + + // Verify the object isn't corrupt before sending. If it is, + // we report it missing instead. + // + try { + dataLength = findEndOffset(src.copyOffset) - dataOffset; + quickCopy = curs.quickCopy(this, dataOffset, dataLength); + + if (idx().hasCRC32Support()) { + // Index has the CRC32 code cached, validate the object. + // + expectedCRC = idx().findCRC32(src); + if (quickCopy != null) { + quickCopy.crc32(crc1, dataOffset, (int) dataLength); + } else { + long pos = dataOffset; + long cnt = dataLength; + while (cnt > 0) { + final int n = (int) Math.min(cnt, buf.length); + readFully(pos, buf, 0, n, curs); + crc1.update(buf, 0, n); + pos += n; + cnt -= n; + } + } + if (crc1.getValue() != expectedCRC) { + setCorrupt(src.copyOffset); + throw new CorruptObjectException(MessageFormat.format( + JGitText.get().objectAtHasBadZlibStream, + src.copyOffset, getPackFile())); + } + } else { + // We don't have a CRC32 code in the index, so compute it + // now while inflating the raw data to get zlib to tell us + // whether or not the data is safe. + // + Inflater inf = curs.inflater(); + byte[] tmp = new byte[1024]; + if (quickCopy != null) { + quickCopy.check(inf, tmp, dataOffset, (int) dataLength); + } else { + long pos = dataOffset; + long cnt = dataLength; + while (cnt > 0) { + final int n = (int) Math.min(cnt, buf.length); + readFully(pos, buf, 0, n, curs); + crc1.update(buf, 0, n); + inf.setInput(buf, 0, n); + while (inf.inflate(tmp, 0, tmp.length) > 0) + continue; + pos += n; + cnt -= n; + } + } + if (!inf.finished() || inf.getBytesRead() != dataLength) { + setCorrupt(src.copyOffset); + throw new EOFException(MessageFormat.format( + JGitText.get().shortCompressedStreamAt, + src.copyOffset)); + } + expectedCRC = crc1.getValue(); + } + } catch (DataFormatException dataFormat) { + setCorrupt(src.copyOffset); + + CorruptObjectException corruptObject = new CorruptObjectException( + MessageFormat.format( + JGitText.get().objectAtHasBadZlibStream, + src.copyOffset, getPackFile())); + corruptObject.initCause(dataFormat); + + StoredObjectRepresentationNotAvailableException gone; + gone = new StoredObjectRepresentationNotAvailableException(src); + gone.initCause(corruptObject); + throw gone; + + } catch (IOException ioError) { + StoredObjectRepresentationNotAvailableException gone; + gone = new StoredObjectRepresentationNotAvailableException(src); + gone.initCause(ioError); + throw gone; + } + + if (quickCopy != null) { + // The entire object fits into a single byte array window slice, + // and we have it pinned. Write this out without copying. + // + out.writeHeader(src, inflatedLength); + quickCopy.write(out, dataOffset, (int) dataLength); + + } else if (dataLength <= buf.length) { + // Tiny optimization: Lots of objects are very small deltas or + // deflated commits that are likely to fit in the copy buffer. + // + out.writeHeader(src, inflatedLength); + out.write(buf, 0, (int) dataLength); + } else { + // Now we are committed to sending the object. As we spool it out, + // check its CRC32 code to make sure there wasn't corruption between + // the verification we did above, and us actually outputting it. + // + out.writeHeader(src, inflatedLength); + long pos = dataOffset; + long cnt = dataLength; + while (cnt > 0) { + final int n = (int) Math.min(cnt, buf.length); + readFully(pos, buf, 0, n, curs); + crc2.update(buf, 0, n); + out.write(buf, 0, n); + pos += n; + cnt -= n; + } + if (crc2.getValue() != expectedCRC) { + throw new CorruptObjectException(MessageFormat.format(JGitText + .get().objectAtHasBadZlibStream, src.copyOffset, + getPackFile())); + } + } + } + + boolean invalid() { + return invalid; + } + + private void readFully(final long position, final byte[] dstbuf, + int dstoff, final int cnt, final WindowCursor curs) + throws IOException { + if (curs.copy(this, position, dstbuf, dstoff, cnt) != cnt) + throw new EOFException(); + } + + private synchronized void beginCopyAsIs(ObjectToPack otp) + throws StoredObjectRepresentationNotAvailableException { + if (++activeCopyRawData == 1 && activeWindows == 0) { + try { + doOpen(); + } catch (IOException thisPackNotValid) { + StoredObjectRepresentationNotAvailableException gone; + + gone = new StoredObjectRepresentationNotAvailableException(otp); + gone.initCause(thisPackNotValid); + throw gone; + } + } + } + + private synchronized void endCopyAsIs() { + if (--activeCopyRawData == 0 && activeWindows == 0) + doClose(); + } + + synchronized boolean beginWindowCache() throws IOException { + if (++activeWindows == 1) { + if (activeCopyRawData == 0) + doOpen(); + return true; + } + return false; + } + + synchronized boolean endWindowCache() { + final boolean r = --activeWindows == 0; + if (r && activeCopyRawData == 0) + doClose(); + return r; + } + + private void doOpen() throws IOException { + try { + if (invalid) + throw new PackInvalidException(packFile); + synchronized (readLock) { + fd = new RandomAccessFile(packFile, "r"); + length = fd.length(); + onOpenPack(); + } + } catch (IOException ioe) { + openFail(); + throw ioe; + } catch (RuntimeException re) { + openFail(); + throw re; + } catch (Error re) { + openFail(); + throw re; + } + } + + private void openFail() { + activeWindows = 0; + activeCopyRawData = 0; + invalid = true; + doClose(); + } + + private void doClose() { + synchronized (readLock) { + if (fd != null) { + try { + fd.close(); + } catch (IOException err) { + // Ignore a close event. We had it open only for reading. + // There should not be errors related to network buffers + // not flushed, etc. + } + fd = null; + } + } + } + + ByteArrayWindow read(final long pos, int size) throws IOException { + synchronized (readLock) { + if (length < pos + size) + size = (int) (length - pos); + final byte[] buf = new byte[size]; + fd.seek(pos); + fd.readFully(buf, 0, size); + return new ByteArrayWindow(this, pos, buf); + } + } + + ByteWindow mmap(final long pos, int size) throws IOException { + synchronized (readLock) { + if (length < pos + size) + size = (int) (length - pos); + + MappedByteBuffer map; + try { + map = fd.getChannel().map(MapMode.READ_ONLY, pos, size); + } catch (IOException ioe1) { + // The most likely reason this failed is the JVM has run out + // of virtual memory. We need to discard quickly, and try to + // force the GC to finalize and release any existing mappings. + // + System.gc(); + System.runFinalization(); + map = fd.getChannel().map(MapMode.READ_ONLY, pos, size); + } + + if (map.hasArray()) + return new ByteArrayWindow(this, pos, map.array()); + return new ByteBufferWindow(this, pos, map); + } + } + + private void onOpenPack() throws IOException { + final PackIndex idx = idx(); + final byte[] buf = new byte[20]; + + fd.seek(0); + fd.readFully(buf, 0, 12); + if (RawParseUtils.match(buf, 0, Constants.PACK_SIGNATURE) != 4) + throw new IOException(JGitText.get().notAPACKFile); + final long vers = NB.decodeUInt32(buf, 4); + final long packCnt = NB.decodeUInt32(buf, 8); + if (vers != 2 && vers != 3) + throw new IOException(MessageFormat.format(JGitText.get().unsupportedPackVersion, vers)); + + if (packCnt != idx.getObjectCount()) + throw new PackMismatchException(MessageFormat.format( + JGitText.get().packObjectCountMismatch, packCnt, idx.getObjectCount(), getPackFile())); + + fd.seek(length - 20); + fd.read(buf, 0, 20); + if (!Arrays.equals(buf, packChecksum)) + throw new PackMismatchException(MessageFormat.format( + JGitText.get().packObjectCountMismatch + , ObjectId.fromRaw(buf).name() + , ObjectId.fromRaw(idx.packChecksum).name() + , getPackFile())); + } + + private PackedObjectLoader reader(final WindowCursor curs, + final long objOffset) throws IOException { + int p = 0; + final byte[] ib = curs.tempId; + readFully(objOffset, ib, 0, 20, curs); + int c = ib[p++] & 0xff; + final int typeCode = (c >> 4) & 7; + long dataSize = c & 15; + int shift = 4; + while ((c & 0x80) != 0) { + c = ib[p++] & 0xff; + dataSize += (c & 0x7f) << shift; + shift += 7; + } + + switch (typeCode) { + case Constants.OBJ_COMMIT: + case Constants.OBJ_TREE: + case Constants.OBJ_BLOB: + case Constants.OBJ_TAG: + return new WholePackedObjectLoader(this, objOffset, p, typeCode, + (int) dataSize); + + case Constants.OBJ_OFS_DELTA: { + c = ib[p++] & 0xff; + long ofs = c & 127; + while ((c & 128) != 0) { + ofs += 1; + c = ib[p++] & 0xff; + ofs <<= 7; + ofs += (c & 127); + } + return new DeltaOfsPackedObjectLoader(this, objOffset, p, + (int) dataSize, objOffset - ofs); + } + case Constants.OBJ_REF_DELTA: { + readFully(objOffset + p, ib, 0, 20, curs); + return new DeltaRefPackedObjectLoader(this, objOffset, p + 20, + (int) dataSize, ObjectId.fromRaw(ib)); + } + default: + throw new IOException(MessageFormat.format(JGitText.get().unknownObjectType, typeCode)); + } + } + + private long findEndOffset(final long startOffset) + throws IOException, CorruptObjectException { + final long maxOffset = length - 20; + return getReverseIdx().findNextOffset(startOffset, maxOffset); + } + + private synchronized PackReverseIndex getReverseIdx() throws IOException { + if (reverseIdx == null) + reverseIdx = new PackReverseIndex(idx()); + return reverseIdx; + } + + private boolean isCorrupt(long offset) { + LongList list = corruptObjects; + if (list == null) + return false; + synchronized (list) { + return list.contains(offset); + } + } + + private void setCorrupt(long offset) { + LongList list = corruptObjects; + if (list == null) { + synchronized (readLock) { + list = corruptObjects; + if (list == null) { + list = new LongList(); + corruptObjects = list; + } + } + } + synchronized (list) { + list.add(offset); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java new file mode 100644 index 0000000000..62d1c9d8f8 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008, Shawn O. Pearce + * 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.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Iterator; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.MutableObjectId; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.NB; + +/** + * Access path to locate objects by {@link ObjectId} in a {@link PackFile}. + *

+ * Indexes are strictly redundant information in that we can rebuild all of the + * data held in the index file from the on disk representation of the pack file + * itself, but it is faster to access for random requests because data is stored + * by ObjectId. + *

+ */ +public abstract class PackIndex implements Iterable { + /** + * Open an existing pack .idx file for reading. + *

+ * The format of the file will be automatically detected and a proper access + * implementation for that format will be constructed and returned to the + * caller. The file may or may not be held open by the returned instance. + *

+ * + * @param idxFile + * existing pack .idx to read. + * @return access implementation for the requested file. + * @throws FileNotFoundException + * the file does not exist. + * @throws IOException + * the file exists but could not be read due to security errors, + * unrecognized data version, or unexpected data corruption. + */ + public static PackIndex open(final File idxFile) throws IOException { + final FileInputStream fd = new FileInputStream(idxFile); + try { + final byte[] hdr = new byte[8]; + IO.readFully(fd, hdr, 0, hdr.length); + if (isTOC(hdr)) { + final int v = NB.decodeInt32(hdr, 4); + switch (v) { + case 2: + return new PackIndexV2(fd); + default: + throw new IOException(MessageFormat.format(JGitText.get().unsupportedPackIndexVersion, v)); + } + } + return new PackIndexV1(fd, hdr); + } catch (IOException ioe) { + final String path = idxFile.getAbsolutePath(); + final IOException err; + err = new IOException(MessageFormat.format(JGitText.get().unreadablePackIndex, path)); + err.initCause(ioe); + throw err; + } finally { + try { + fd.close(); + } catch (IOException err2) { + // ignore + } + } + } + + private static boolean isTOC(final byte[] h) { + final byte[] toc = PackIndexWriter.TOC; + for (int i = 0; i < toc.length; i++) + if (h[i] != toc[i]) + return false; + return true; + } + + /** Footer checksum applied on the bottom of the pack file. */ + protected byte[] packChecksum; + + /** + * Determine if an object is contained within the pack file. + * + * @param id + * the object to look for. Must not be null. + * @return true if the object is listed in this index; false otherwise. + */ + public boolean hasObject(final AnyObjectId id) { + return findOffset(id) != -1; + } + + /** + * Provide iterator that gives access to index entries. Note, that iterator + * returns reference to mutable object, the same reference in each call - + * for performance reason. If client needs immutable objects, it must copy + * returned object on its own. + *

+ * Iterator returns objects in SHA-1 lexicographical order. + *

+ * + * @return iterator over pack index entries + */ + public abstract Iterator iterator(); + + /** + * Obtain the total number of objects described by this index. + * + * @return number of objects in this index, and likewise in the associated + * pack that this index was generated from. + */ + abstract long getObjectCount(); + + /** + * Obtain the total number of objects needing 64 bit offsets. + * + * @return number of objects in this index using a 64 bit offset; that is an + * object positioned after the 2 GB position within the file. + */ + abstract long getOffset64Count(); + + /** + * Get ObjectId for the n-th object entry returned by {@link #iterator()}. + *

+ * This method is a constant-time replacement for the following loop: + * + *

+	 * Iterator<MutableEntry> eItr = index.iterator();
+	 * int curPosition = 0;
+	 * while (eItr.hasNext() && curPosition++ < nthPosition)
+	 * 	eItr.next();
+	 * ObjectId result = eItr.next().toObjectId();
+	 * 
+ * + * @param nthPosition + * position within the traversal of {@link #iterator()} that the + * caller needs the object for. The first returned + * {@link MutableEntry} is 0, the second is 1, etc. + * @return the ObjectId for the corresponding entry. + */ + abstract ObjectId getObjectId(long nthPosition); + + /** + * Get ObjectId for the n-th object entry returned by {@link #iterator()}. + *

+ * This method is a constant-time replacement for the following loop: + * + *

+	 * Iterator<MutableEntry> eItr = index.iterator();
+	 * int curPosition = 0;
+	 * while (eItr.hasNext() && curPosition++ < nthPosition)
+	 * 	eItr.next();
+	 * ObjectId result = eItr.next().toObjectId();
+	 * 
+ * + * @param nthPosition + * unsigned 32 bit position within the traversal of + * {@link #iterator()} that the caller needs the object for. The + * first returned {@link MutableEntry} is 0, the second is 1, + * etc. Positions past 2**31-1 are negative, but still valid. + * @return the ObjectId for the corresponding entry. + */ + final ObjectId getObjectId(final int nthPosition) { + if (nthPosition >= 0) + return getObjectId((long) nthPosition); + final int u31 = nthPosition >>> 1; + final int one = nthPosition & 1; + return getObjectId(((long) u31) << 1 | one); + } + + /** + * Locate the file offset position for the requested object. + * + * @param objId + * name of the object to locate within the pack. + * @return offset of the object's header and compressed content; -1 if the + * object does not exist in this index and is thus not stored in the + * associated pack. + */ + abstract long findOffset(AnyObjectId objId); + + /** + * Retrieve stored CRC32 checksum of the requested object raw-data + * (including header). + * + * @param objId + * id of object to look for + * @return CRC32 checksum of specified object (at 32 less significant bits) + * @throws MissingObjectException + * when requested ObjectId was not found in this index + * @throws UnsupportedOperationException + * when this index doesn't support CRC32 checksum + */ + abstract long findCRC32(AnyObjectId objId) throws MissingObjectException, + UnsupportedOperationException; + + /** + * Check whether this index supports (has) CRC32 checksums for objects. + * + * @return true if CRC32 is stored, false otherwise + */ + abstract boolean hasCRC32Support(); + + /** + * Represent mutable entry of pack index consisting of object id and offset + * in pack (both mutable). + * + */ + public static class MutableEntry { + final MutableObjectId idBuffer = new MutableObjectId(); + + long offset; + + /** + * Returns offset for this index object entry + * + * @return offset of this object in a pack file + */ + public long getOffset() { + return offset; + } + + /** @return hex string describing the object id of this entry. */ + public String name() { + ensureId(); + return idBuffer.name(); + } + + /** @return a copy of the object id. */ + public ObjectId toObjectId() { + ensureId(); + return idBuffer.toObjectId(); + } + + /** @return a complete copy of this entry, that won't modify */ + public MutableEntry cloneEntry() { + final MutableEntry r = new MutableEntry(); + ensureId(); + r.idBuffer.fromObjectId(idBuffer); + r.offset = offset; + return r; + } + + void ensureId() { + // Override in implementations. + } + } + + abstract class EntriesIterator implements Iterator { + protected final MutableEntry entry = initEntry(); + + protected long returnedNumber = 0; + + protected abstract MutableEntry initEntry(); + + public boolean hasNext() { + return returnedNumber < getObjectCount(); + } + + /** + * Implementation must update {@link #returnedNumber} before returning + * element. + */ + public abstract MutableEntry next(); + + public void remove() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java new file mode 100644 index 0000000000..3b68edc191 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2007-2009, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.NB; + +class PackIndexV1 extends PackIndex { + private static final int IDX_HDR_LEN = 256 * 4; + + private final long[] idxHeader; + + private byte[][] idxdata; + + private long objectCnt; + + PackIndexV1(final InputStream fd, final byte[] hdr) + throws CorruptObjectException, IOException { + final byte[] fanoutTable = new byte[IDX_HDR_LEN]; + System.arraycopy(hdr, 0, fanoutTable, 0, hdr.length); + IO.readFully(fd, fanoutTable, hdr.length, IDX_HDR_LEN - hdr.length); + + idxHeader = new long[256]; // really unsigned 32-bit... + for (int k = 0; k < idxHeader.length; k++) + idxHeader[k] = NB.decodeUInt32(fanoutTable, k * 4); + idxdata = new byte[idxHeader.length][]; + for (int k = 0; k < idxHeader.length; k++) { + int n; + if (k == 0) { + n = (int) (idxHeader[k]); + } else { + n = (int) (idxHeader[k] - idxHeader[k - 1]); + } + if (n > 0) { + idxdata[k] = new byte[n * (Constants.OBJECT_ID_LENGTH + 4)]; + IO.readFully(fd, idxdata[k], 0, idxdata[k].length); + } + } + objectCnt = idxHeader[255]; + + packChecksum = new byte[20]; + IO.readFully(fd, packChecksum, 0, packChecksum.length); + } + + long getObjectCount() { + return objectCnt; + } + + @Override + long getOffset64Count() { + long n64 = 0; + for (final MutableEntry e : this) { + if (e.getOffset() >= Integer.MAX_VALUE) + n64++; + } + return n64; + } + + @Override + ObjectId getObjectId(final long nthPosition) { + int levelOne = Arrays.binarySearch(idxHeader, nthPosition + 1); + long base; + if (levelOne >= 0) { + // If we hit the bucket exactly the item is in the bucket, or + // any bucket before it which has the same object count. + // + base = idxHeader[levelOne]; + while (levelOne > 0 && base == idxHeader[levelOne - 1]) + levelOne--; + } else { + // The item is in the bucket we would insert it into. + // + levelOne = -(levelOne + 1); + } + + base = levelOne > 0 ? idxHeader[levelOne - 1] : 0; + final int p = (int) (nthPosition - base); + final int dataIdx = ((4 + Constants.OBJECT_ID_LENGTH) * p) + 4; + return ObjectId.fromRaw(idxdata[levelOne], dataIdx); + } + + long findOffset(final AnyObjectId objId) { + final int levelOne = objId.getFirstByte(); + byte[] data = idxdata[levelOne]; + if (data == null) + return -1; + int high = data.length / (4 + Constants.OBJECT_ID_LENGTH); + int low = 0; + do { + final int mid = (low + high) >>> 1; + final int pos = ((4 + Constants.OBJECT_ID_LENGTH) * mid) + 4; + final int cmp = objId.compareTo(data, pos); + if (cmp < 0) + high = mid; + else if (cmp == 0) { + int b0 = data[pos - 4] & 0xff; + int b1 = data[pos - 3] & 0xff; + int b2 = data[pos - 2] & 0xff; + int b3 = data[pos - 1] & 0xff; + return (((long) b0) << 24) | (b1 << 16) | (b2 << 8) | (b3); + } else + low = mid + 1; + } while (low < high); + return -1; + } + + @Override + long findCRC32(AnyObjectId objId) { + throw new UnsupportedOperationException(); + } + + @Override + boolean hasCRC32Support() { + return false; + } + + public Iterator iterator() { + return new IndexV1Iterator(); + } + + private class IndexV1Iterator extends EntriesIterator { + private int levelOne; + + private int levelTwo; + + @Override + protected MutableEntry initEntry() { + return new MutableEntry() { + protected void ensureId() { + idBuffer.fromRaw(idxdata[levelOne], levelTwo + - Constants.OBJECT_ID_LENGTH); + } + }; + } + + public MutableEntry next() { + for (; levelOne < idxdata.length; levelOne++) { + if (idxdata[levelOne] == null) + continue; + if (levelTwo < idxdata[levelOne].length) { + entry.offset = NB.decodeUInt32(idxdata[levelOne], levelTwo); + levelTwo += Constants.OBJECT_ID_LENGTH + 4; + returnedNumber++; + return entry; + } + levelTwo = 0; + } + throw new NoSuchElementException(); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java new file mode 100644 index 0000000000..cef7cc429e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * 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.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.NB; + +/** Support for the pack index v2 format. */ +class PackIndexV2 extends PackIndex { + private static final long IS_O64 = 1L << 31; + + private static final int FANOUT = 256; + + private static final int[] NO_INTS = {}; + + private static final byte[] NO_BYTES = {}; + + private long objectCnt; + + private final long[] fanoutTable; + + /** 256 arrays of contiguous object names. */ + private int[][] names; + + /** 256 arrays of the 32 bit offset data, matching {@link #names}. */ + private byte[][] offset32; + + /** 256 arrays of the CRC-32 of objects, matching {@link #names}. */ + private byte[][] crc32; + + /** 64 bit offset table. */ + private byte[] offset64; + + PackIndexV2(final InputStream fd) throws IOException { + final byte[] fanoutRaw = new byte[4 * FANOUT]; + IO.readFully(fd, fanoutRaw, 0, fanoutRaw.length); + fanoutTable = new long[FANOUT]; + for (int k = 0; k < FANOUT; k++) + fanoutTable[k] = NB.decodeUInt32(fanoutRaw, k * 4); + objectCnt = fanoutTable[FANOUT - 1]; + + names = new int[FANOUT][]; + offset32 = new byte[FANOUT][]; + crc32 = new byte[FANOUT][]; + + // Object name table. The size we can permit per fan-out bucket + // is limited to Java's 2 GB per byte array limitation. That is + // no more than 107,374,182 objects per fan-out. + // + for (int k = 0; k < FANOUT; k++) { + final long bucketCnt; + if (k == 0) + bucketCnt = fanoutTable[k]; + else + bucketCnt = fanoutTable[k] - fanoutTable[k - 1]; + + if (bucketCnt == 0) { + names[k] = NO_INTS; + offset32[k] = NO_BYTES; + crc32[k] = NO_BYTES; + continue; + } + + final long nameLen = bucketCnt * Constants.OBJECT_ID_LENGTH; + if (nameLen > Integer.MAX_VALUE) + throw new IOException(JGitText.get().indexFileIsTooLargeForJgit); + + final int intNameLen = (int) nameLen; + final byte[] raw = new byte[intNameLen]; + final int[] bin = new int[intNameLen >>> 2]; + IO.readFully(fd, raw, 0, raw.length); + for (int i = 0; i < bin.length; i++) + bin[i] = NB.decodeInt32(raw, i << 2); + + names[k] = bin; + offset32[k] = new byte[(int) (bucketCnt * 4)]; + crc32[k] = new byte[(int) (bucketCnt * 4)]; + } + + // CRC32 table. + for (int k = 0; k < FANOUT; k++) + IO.readFully(fd, crc32[k], 0, crc32[k].length); + + // 32 bit offset table. Any entries with the most significant bit + // set require a 64 bit offset entry in another table. + // + int o64cnt = 0; + for (int k = 0; k < FANOUT; k++) { + final byte[] ofs = offset32[k]; + IO.readFully(fd, ofs, 0, ofs.length); + for (int p = 0; p < ofs.length; p += 4) + if (ofs[p] < 0) + o64cnt++; + } + + // 64 bit offset table. Most objects should not require an entry. + // + if (o64cnt > 0) { + offset64 = new byte[o64cnt * 8]; + IO.readFully(fd, offset64, 0, offset64.length); + } else { + offset64 = NO_BYTES; + } + + packChecksum = new byte[20]; + IO.readFully(fd, packChecksum, 0, packChecksum.length); + } + + @Override + long getObjectCount() { + return objectCnt; + } + + @Override + long getOffset64Count() { + return offset64.length / 8; + } + + @Override + ObjectId getObjectId(final long nthPosition) { + int levelOne = Arrays.binarySearch(fanoutTable, nthPosition + 1); + long base; + if (levelOne >= 0) { + // If we hit the bucket exactly the item is in the bucket, or + // any bucket before it which has the same object count. + // + base = fanoutTable[levelOne]; + while (levelOne > 0 && base == fanoutTable[levelOne - 1]) + levelOne--; + } else { + // The item is in the bucket we would insert it into. + // + levelOne = -(levelOne + 1); + } + + base = levelOne > 0 ? fanoutTable[levelOne - 1] : 0; + final int p = (int) (nthPosition - base); + final int p4 = p << 2; + return ObjectId.fromRaw(names[levelOne], p4 + p); // p * 5 + } + + @Override + long findOffset(final AnyObjectId objId) { + final int levelOne = objId.getFirstByte(); + final int levelTwo = binarySearchLevelTwo(objId, levelOne); + if (levelTwo == -1) + return -1; + final long p = NB.decodeUInt32(offset32[levelOne], levelTwo << 2); + if ((p & IS_O64) != 0) + return NB.decodeUInt64(offset64, (8 * (int) (p & ~IS_O64))); + return p; + } + + @Override + long findCRC32(AnyObjectId objId) throws MissingObjectException { + final int levelOne = objId.getFirstByte(); + final int levelTwo = binarySearchLevelTwo(objId, levelOne); + if (levelTwo == -1) + throw new MissingObjectException(objId.copy(), "unknown"); + return NB.decodeUInt32(crc32[levelOne], levelTwo << 2); + } + + @Override + boolean hasCRC32Support() { + return true; + } + + public Iterator iterator() { + return new EntriesIteratorV2(); + } + + private int binarySearchLevelTwo(final AnyObjectId objId, final int levelOne) { + final int[] data = names[levelOne]; + int high = offset32[levelOne].length >>> 2; + if (high == 0) + return -1; + int low = 0; + do { + final int mid = (low + high) >>> 1; + final int mid4 = mid << 2; + final int cmp; + + cmp = objId.compareTo(data, mid4 + mid); // mid * 5 + if (cmp < 0) + high = mid; + else if (cmp == 0) { + return mid; + } else + low = mid + 1; + } while (low < high); + return -1; + } + + private class EntriesIteratorV2 extends EntriesIterator { + private int levelOne; + + private int levelTwo; + + @Override + protected MutableEntry initEntry() { + return new MutableEntry() { + protected void ensureId() { + idBuffer.fromRaw(names[levelOne], levelTwo + - Constants.OBJECT_ID_LENGTH / 4); + } + }; + } + + public MutableEntry next() { + for (; levelOne < names.length; levelOne++) { + if (levelTwo < names[levelOne].length) { + int idx = levelTwo / (Constants.OBJECT_ID_LENGTH / 4) * 4; + long offset = NB.decodeUInt32(offset32[levelOne], idx); + if ((offset & IS_O64) != 0) { + idx = (8 * (int) (offset & ~IS_O64)); + offset = NB.decodeUInt64(offset64, idx); + } + entry.offset = offset; + + levelTwo += Constants.OBJECT_ID_LENGTH / 4; + returnedNumber++; + return entry; + } + levelTwo = 0; + } + throw new NoSuchElementException(); + } + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriter.java new file mode 100644 index 0000000000..6bd73adcb1 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriter.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * 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.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.security.DigestOutputStream; +import java.text.MessageFormat; +import java.util.List; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.transport.PackedObjectInfo; +import org.eclipse.jgit.util.NB; + +/** + * Creates a table of contents to support random access by {@link PackFile}. + *

+ * Pack index files (the .idx suffix in a pack file pair) + * provides random access to any object in the pack by associating an ObjectId + * to the byte offset within the pack where the object's data can be read. + */ +public abstract class PackIndexWriter { + /** Magic constant indicating post-version 1 format. */ + protected static final byte[] TOC = { -1, 't', 'O', 'c' }; + + /** + * Create a new writer for the oldest (most widely understood) format. + *

+ * This method selects an index format that can accurate describe the + * supplied objects and that will be the most compatible format with older + * Git implementations. + *

+ * Index version 1 is widely recognized by all Git implementations, but + * index version 2 (and later) is not as well recognized as it was + * introduced more than a year later. Index version 1 can only be used if + * the resulting pack file is under 4 gigabytes in size; packs larger than + * that limit must use index version 2. + * + * @param dst + * the stream the index data will be written to. If not already + * buffered it will be automatically wrapped in a buffered + * stream. Callers are always responsible for closing the stream. + * @param objs + * the objects the caller needs to store in the index. Entries + * will be examined until a format can be conclusively selected. + * @return a new writer to output an index file of the requested format to + * the supplied stream. + * @throws IllegalArgumentException + * no recognized pack index version can support the supplied + * objects. This is likely a bug in the implementation. + */ + @SuppressWarnings("fallthrough") + public static PackIndexWriter createOldestPossible(final OutputStream dst, + final List objs) { + int version = 1; + LOOP: for (final PackedObjectInfo oe : objs) { + switch (version) { + case 1: + if (PackIndexWriterV1.canStore(oe)) + continue; + version = 2; + case 2: + break LOOP; + } + } + return createVersion(dst, version); + } + + /** + * Create a new writer instance for a specific index format version. + * + * @param dst + * the stream the index data will be written to. If not already + * buffered it will be automatically wrapped in a buffered + * stream. Callers are always responsible for closing the stream. + * @param version + * index format version number required by the caller. Exactly + * this formatted version will be written. + * @return a new writer to output an index file of the requested format to + * the supplied stream. + * @throws IllegalArgumentException + * the version requested is not supported by this + * implementation. + */ + public static PackIndexWriter createVersion(final OutputStream dst, + final int version) { + switch (version) { + case 1: + return new PackIndexWriterV1(dst); + case 2: + return new PackIndexWriterV2(dst); + default: + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().unsupportedPackIndexVersion, version)); + } + } + + /** The index data stream we are responsible for creating. */ + protected final DigestOutputStream out; + + /** A temporary buffer for use during IO to {link #out}. */ + protected final byte[] tmp; + + /** The entries this writer must pack. */ + protected List entries; + + /** SHA-1 checksum for the entire pack data. */ + protected byte[] packChecksum; + + /** + * Create a new writer instance. + * + * @param dst + * the stream this instance outputs to. If not already buffered + * it will be automatically wrapped in a buffered stream. + */ + protected PackIndexWriter(final OutputStream dst) { + out = new DigestOutputStream(dst instanceof BufferedOutputStream ? dst + : new BufferedOutputStream(dst), Constants.newMessageDigest()); + tmp = new byte[4 + Constants.OBJECT_ID_LENGTH]; + } + + /** + * Write all object entries to the index stream. + *

+ * After writing the stream passed to the factory is flushed but remains + * open. Callers are always responsible for closing the output stream. + * + * @param toStore + * sorted list of objects to store in the index. The caller must + * have previously sorted the list using {@link PackedObjectInfo}'s + * native {@link Comparable} implementation. + * @param packDataChecksum + * checksum signature of the entire pack data content. This is + * traditionally the last 20 bytes of the pack file's own stream. + * @throws IOException + * an error occurred while writing to the output stream, or this + * index format cannot store the object data supplied. + */ + public void write(final List toStore, + final byte[] packDataChecksum) throws IOException { + entries = toStore; + packChecksum = packDataChecksum; + writeImpl(); + out.flush(); + } + + /** + * Writes the index file to {@link #out}. + *

+ * Implementations should go something like: + * + *

+	 * writeFanOutTable();
+	 * for (final PackedObjectInfo po : entries)
+	 * 	writeOneEntry(po);
+	 * writeChecksumFooter();
+	 * 
+ * + *

+ * Where the logic for writeOneEntry is specific to the index + * format in use. Additional headers/footers may be used if necessary and + * the {@link #entries} collection may be iterated over more than once if + * necessary. Implementors therefore have complete control over the data. + * + * @throws IOException + * an error occurred while writing to the output stream, or this + * index format cannot store the object data supplied. + */ + protected abstract void writeImpl() throws IOException; + + /** + * Output the version 2 (and later) TOC header, with version number. + *

+ * Post version 1 all index files start with a TOC header that makes the + * file an invalid version 1 file, and then includes the version number. + * This header is necessary to recognize a version 1 from a version 2 + * formatted index. + * + * @param version + * version number of this index format being written. + * @throws IOException + * an error occurred while writing to the output stream. + */ + protected void writeTOC(final int version) throws IOException { + out.write(TOC); + NB.encodeInt32(tmp, 0, version); + out.write(tmp, 0, 4); + } + + /** + * Output the standard 256 entry first-level fan-out table. + *

+ * The fan-out table is 4 KB in size, holding 256 32-bit unsigned integer + * counts. Each count represents the number of objects within this index + * whose {@link ObjectId#getFirstByte()} matches the count's position in the + * fan-out table. + * + * @throws IOException + * an error occurred while writing to the output stream. + */ + protected void writeFanOutTable() throws IOException { + final int[] fanout = new int[256]; + for (final PackedObjectInfo po : entries) + fanout[po.getFirstByte() & 0xff]++; + for (int i = 1; i < 256; i++) + fanout[i] += fanout[i - 1]; + for (final int n : fanout) { + NB.encodeInt32(tmp, 0, n); + out.write(tmp, 0, 4); + } + } + + /** + * Output the standard two-checksum index footer. + *

+ * The standard footer contains two checksums (20 byte SHA-1 values): + *

    + *
  1. Pack data checksum - taken from the last 20 bytes of the pack file.
  2. + *
  3. Index data checksum - checksum of all index bytes written, including + * the pack data checksum above.
  4. + *
+ * + * @throws IOException + * an error occurred while writing to the output stream. + */ + protected void writeChecksumFooter() throws IOException { + out.write(packChecksum); + out.on(false); + out.write(out.getMessageDigest().digest()); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV1.java new file mode 100644 index 0000000000..722ab0e06b --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV1.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * 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.IOException; +import java.io.OutputStream; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.transport.PackedObjectInfo; +import org.eclipse.jgit.util.NB; + +/** + * Creates the version 1 (old style) pack table of contents files. + * + * @see PackIndexWriter + * @see PackIndexV1 + */ +class PackIndexWriterV1 extends PackIndexWriter { + static boolean canStore(final PackedObjectInfo oe) { + // We are limited to 4 GB per pack as offset is 32 bit unsigned int. + // + return oe.getOffset() >>> 1 < Integer.MAX_VALUE; + } + + PackIndexWriterV1(final OutputStream dst) { + super(dst); + } + + @Override + protected void writeImpl() throws IOException { + writeFanOutTable(); + + for (final PackedObjectInfo oe : entries) { + if (!canStore(oe)) + throw new IOException(JGitText.get().packTooLargeForIndexVersion1); + NB.encodeInt32(tmp, 0, (int) oe.getOffset()); + oe.copyRawTo(tmp, 4); + out.write(tmp); + } + + writeChecksumFooter(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java new file mode 100644 index 0000000000..21ebd1ca9c --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * 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.IOException; +import java.io.OutputStream; + +import org.eclipse.jgit.transport.PackedObjectInfo; +import org.eclipse.jgit.util.NB; + +/** + * Creates the version 2 pack table of contents files. + * + * @see PackIndexWriter + * @see PackIndexV2 + */ +class PackIndexWriterV2 extends PackIndexWriter { + PackIndexWriterV2(final OutputStream dst) { + super(dst); + } + + @Override + protected void writeImpl() throws IOException { + writeTOC(2); + writeFanOutTable(); + writeObjectNames(); + writeCRCs(); + writeOffset32(); + writeOffset64(); + writeChecksumFooter(); + } + + private void writeObjectNames() throws IOException { + for (final PackedObjectInfo oe : entries) + oe.copyRawTo(out); + } + + private void writeCRCs() throws IOException { + for (final PackedObjectInfo oe : entries) { + NB.encodeInt32(tmp, 0, oe.getCRC()); + out.write(tmp, 0, 4); + } + } + + private void writeOffset32() throws IOException { + int o64 = 0; + for (final PackedObjectInfo oe : entries) { + final long o = oe.getOffset(); + if (o < Integer.MAX_VALUE) + NB.encodeInt32(tmp, 0, (int) o); + else + NB.encodeInt32(tmp, 0, (1 << 31) | o64++); + out.write(tmp, 0, 4); + } + } + + private void writeOffset64() throws IOException { + for (final PackedObjectInfo oe : entries) { + final long o = oe.getOffset(); + if (o > Integer.MAX_VALUE) { + NB.encodeInt64(tmp, 0, o); + out.write(tmp, 0, 8); + } + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackLock.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackLock.java new file mode 100644 index 0000000000..be250114c2 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackLock.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2009, Google Inc. + * 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.IOException; + +import org.eclipse.jgit.lib.Constants; + +/** Keeps track of a {@link PackFile}'s associated .keep file. */ +public class PackLock { + private final File keepFile; + + /** + * Create a new lock for a pack file. + * + * @param packFile + * location of the pack-*.pack file. + */ + public PackLock(final File packFile) { + final File p = packFile.getParentFile(); + final String n = packFile.getName(); + keepFile = new File(p, n.substring(0, n.length() - 5) + ".keep"); + } + + /** + * Create the pack-*.keep file, with the given message. + * + * @param msg + * message to store in the file. + * @return true if the keep file was successfully written; false otherwise. + * @throws IOException + * the keep file could not be written. + */ + public boolean lock(String msg) throws IOException { + if (msg == null) + return false; + if (!msg.endsWith("\n")) + msg += "\n"; + final LockFile lf = new LockFile(keepFile); + if (!lf.lock()) + return false; + lf.write(Constants.encode(msg)); + return lf.commit(); + } + + /** Remove the .keep file that holds this pack in place. */ + public void unlock() { + keepFile.delete(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java new file mode 100644 index 0000000000..96abaeefd3 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * 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.text.MessageFormat; +import java.util.Arrays; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.storage.file.PackIndex.MutableEntry; + +/** + *

+ * Reverse index for forward pack index. Provides operations based on offset + * instead of object id. Such offset-based reverse lookups are performed in + * O(log n) time. + *

+ * + * @see PackIndex + * @see PackFile + */ +class PackReverseIndex { + /** Index we were created from, and that has our ObjectId data. */ + private final PackIndex index; + + /** + * (offset31, truly) Offsets accommodating in 31 bits. + */ + private final int offsets32[]; + + /** + * Offsets not accommodating in 31 bits. + */ + private final long offsets64[]; + + /** Position of the corresponding {@link #offsets32} in {@link #index}. */ + private final int nth32[]; + + /** Position of the corresponding {@link #offsets64} in {@link #index}. */ + private final int nth64[]; + + /** + * Create reverse index from straight/forward pack index, by indexing all + * its entries. + * + * @param packIndex + * forward index - entries to (reverse) index. + */ + PackReverseIndex(final PackIndex packIndex) { + index = packIndex; + + final long cnt = index.getObjectCount(); + final long n64 = index.getOffset64Count(); + final long n32 = cnt - n64; + if (n32 > Integer.MAX_VALUE || n64 > Integer.MAX_VALUE + || cnt > 0xffffffffL) + throw new IllegalArgumentException( + JGitText.get().hugeIndexesAreNotSupportedByJgitYet); + + offsets32 = new int[(int) n32]; + offsets64 = new long[(int) n64]; + nth32 = new int[offsets32.length]; + nth64 = new int[offsets64.length]; + + int i32 = 0; + int i64 = 0; + for (final MutableEntry me : index) { + final long o = me.getOffset(); + if (o < Integer.MAX_VALUE) + offsets32[i32++] = (int) o; + else + offsets64[i64++] = o; + } + + Arrays.sort(offsets32); + Arrays.sort(offsets64); + + int nth = 0; + for (final MutableEntry me : index) { + final long o = me.getOffset(); + if (o < Integer.MAX_VALUE) + nth32[Arrays.binarySearch(offsets32, (int) o)] = nth++; + else + nth64[Arrays.binarySearch(offsets64, o)] = nth++; + } + } + + /** + * Search for object id with the specified start offset in this pack + * (reverse) index. + * + * @param offset + * start offset of object to find. + * @return object id for this offset, or null if no object was found. + */ + ObjectId findObject(final long offset) { + if (offset <= Integer.MAX_VALUE) { + final int i32 = Arrays.binarySearch(offsets32, (int) offset); + if (i32 < 0) + return null; + return index.getObjectId(nth32[i32]); + } else { + final int i64 = Arrays.binarySearch(offsets64, offset); + if (i64 < 0) + return null; + return index.getObjectId(nth64[i64]); + } + } + + /** + * Search for the next offset to the specified offset in this pack (reverse) + * index. + * + * @param offset + * start offset of previous object (must be valid-existing + * offset). + * @param maxOffset + * maximum offset in a pack (returned when there is no next + * offset). + * @return offset of the next object in a pack or maxOffset if provided + * offset was the last one. + * @throws CorruptObjectException + * when there is no object with the provided offset. + */ + long findNextOffset(final long offset, final long maxOffset) + throws CorruptObjectException { + if (offset <= Integer.MAX_VALUE) { + final int i32 = Arrays.binarySearch(offsets32, (int) offset); + if (i32 < 0) + throw new CorruptObjectException(MessageFormat.format( + JGitText.get().cantFindObjectInReversePackIndexForTheSpecifiedOffset + , offset)); + + if (i32 + 1 == offsets32.length) { + if (offsets64.length > 0) + return offsets64[0]; + return maxOffset; + } + return offsets32[i32 + 1]; + } else { + final int i64 = Arrays.binarySearch(offsets64, offset); + if (i64 < 0) + throw new CorruptObjectException(MessageFormat.format( + JGitText.get().cantFindObjectInReversePackIndexForTheSpecifiedOffset + , offset)); + + if (i64 + 1 == offsets64.length) + return maxOffset; + return offsets64[i64 + 1]; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java new file mode 100644 index 0000000000..e784f3d181 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.IOException; + +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; + +/** + * Base class for a set of object loader classes for packed objects. + */ +abstract class PackedObjectLoader extends ObjectLoader { + protected final PackFile pack; + + /** Position of the first byte of the object's header. */ + protected final long objectOffset; + + /** Bytes used to express the object header, including delta reference. */ + protected final int headerSize; + + protected int objectType; + + protected int objectSize; + + protected byte[] cachedBytes; + + PackedObjectLoader(final PackFile pr, final long objectOffset, + final int headerSize) { + pack = pr; + this.objectOffset = objectOffset; + this.headerSize = headerSize; + } + + /** + * Force this object to be loaded into memory and pinned in this loader. + *

+ * Once materialized, subsequent get operations for the following methods + * will always succeed without raising an exception, as all information is + * pinned in memory by this loader instance. + *

    + *
  • {@link #getType()}
  • + *
  • {@link #getSize()}
  • + *
  • {@link #getBytes()}, {@link #getCachedBytes}
  • + *
  • {@link #getRawSize()}
  • + *
  • {@link #getRawType()}
  • + *
+ * + * @param curs + * temporary thread storage during data access. + * @throws IOException + * the object cannot be read. + */ + abstract void materialize(WindowCursor curs) throws IOException; + + public final int getType() { + return objectType; + } + + public final long getSize() { + return objectSize; + } + + @Override + public final byte[] getCachedBytes() { + return cachedBytes; + } + + /** + * @return offset of object header within pack file + */ + final long getObjectOffset() { + return objectOffset; + } + + /** + * @return id of delta base object for this object representation. null if + * object is not stored as delta. + * @throws IOException + * when delta base cannot read. + */ + abstract ObjectId getDeltaBase() throws IOException; +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java new file mode 100644 index 0000000000..f7ffa3e39a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java @@ -0,0 +1,1018 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2009-2010, Google Inc. + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006, Shawn O. Pearce + * 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 static org.eclipse.jgit.lib.Constants.CHARSET; +import static org.eclipse.jgit.lib.Constants.HEAD; +import static org.eclipse.jgit.lib.Constants.LOGS; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH; +import static org.eclipse.jgit.lib.Constants.PACKED_REFS; +import static org.eclipse.jgit.lib.Constants.R_HEADS; +import static org.eclipse.jgit.lib.Constants.R_REFS; +import static org.eclipse.jgit.lib.Constants.R_REMOTES; +import static org.eclipse.jgit.lib.Constants.R_TAGS; +import static org.eclipse.jgit.lib.Constants.encode; +import static org.eclipse.jgit.lib.Ref.Storage.LOOSE; +import static org.eclipse.jgit.lib.Ref.Storage.NEW; +import static org.eclipse.jgit.lib.Ref.Storage.PACKED; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.ObjectWritingException; +import org.eclipse.jgit.events.RefsChangedEvent; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.CoreConfig; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefComparator; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.RefWriter; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.RawParseUtils; +import org.eclipse.jgit.util.RefList; +import org.eclipse.jgit.util.RefMap; + +/** + * Traditional file system based {@link RefDatabase}. + *

+ * This is the classical reference database representation for a Git repository. + * References are stored in two formats: loose, and packed. + *

+ * Loose references are stored as individual files within the {@code refs/} + * directory. The file name matches the reference name and the file contents is + * the current {@link ObjectId} in string form. + *

+ * Packed references are stored in a single text file named {@code packed-refs}. + * In the packed format, each reference is stored on its own line. This file + * reduces the number of files needed for large reference spaces, reducing the + * overall size of a Git repository on disk. + */ +public class RefDirectory extends RefDatabase { + /** Magic string denoting the start of a symbolic reference file. */ + public static final String SYMREF = "ref: "; //$NON-NLS-1$ + + /** Magic string denoting the header of a packed-refs file. */ + public static final String PACKED_REFS_HEADER = "# pack-refs with:"; //$NON-NLS-1$ + + /** If in the header, denotes the file has peeled data. */ + public static final String PACKED_REFS_PEELED = " peeled"; //$NON-NLS-1$ + + private final FileRepository parent; + + private final File gitDir; + + private final File refsDir; + + private final File logsDir; + + private final File logsRefsDir; + + private final File packedRefsFile; + + /** + * Immutable sorted list of loose references. + *

+ * Symbolic references in this collection are stored unresolved, that is + * their target appears to be a new reference with no ObjectId. These are + * converted into resolved references during a get operation, ensuring the + * live value is always returned. + */ + private final AtomicReference> looseRefs = new AtomicReference>(); + + /** Immutable sorted list of packed references. */ + private final AtomicReference packedRefs = new AtomicReference(); + + /** + * Number of modifications made to this database. + *

+ * This counter is incremented when a change is made, or detected from the + * filesystem during a read operation. + */ + private final AtomicInteger modCnt = new AtomicInteger(); + + /** + * Last {@link #modCnt} that we sent to listeners. + *

+ * This value is compared to {@link #modCnt}, and a notification is sent to + * the listeners only when it differs. + */ + private final AtomicInteger lastNotifiedModCnt = new AtomicInteger(); + + RefDirectory(final FileRepository db) { + final FS fs = db.getFS(); + parent = db; + gitDir = db.getDirectory(); + refsDir = fs.resolve(gitDir, R_REFS); + logsDir = fs.resolve(gitDir, LOGS); + logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS); + packedRefsFile = fs.resolve(gitDir, PACKED_REFS); + + looseRefs.set(RefList. emptyList()); + packedRefs.set(PackedRefList.NO_PACKED_REFS); + } + + Repository getRepository() { + return parent; + } + + public void create() throws IOException { + refsDir.mkdir(); + logsDir.mkdir(); + logsRefsDir.mkdir(); + + new File(refsDir, R_HEADS.substring(R_REFS.length())).mkdir(); + new File(refsDir, R_TAGS.substring(R_REFS.length())).mkdir(); + new File(logsRefsDir, R_HEADS.substring(R_REFS.length())).mkdir(); + } + + @Override + public void close() { + // We have no resources to close. + } + + void rescan() { + looseRefs.set(RefList. emptyList()); + packedRefs.set(PackedRefList.NO_PACKED_REFS); + } + + @Override + public boolean isNameConflicting(String name) throws IOException { + RefList packed = getPackedRefs(); + RefList loose = getLooseRefs(); + + // Cannot be nested within an existing reference. + int lastSlash = name.lastIndexOf('/'); + while (0 < lastSlash) { + String needle = name.substring(0, lastSlash); + if (loose.contains(needle) || packed.contains(needle)) + return true; + lastSlash = name.lastIndexOf('/', lastSlash - 1); + } + + // Cannot be the container of an existing reference. + String prefix = name + '/'; + int idx; + + idx = -(packed.find(prefix) + 1); + if (idx < packed.size() && packed.get(idx).getName().startsWith(prefix)) + return true; + + idx = -(loose.find(prefix) + 1); + if (idx < loose.size() && loose.get(idx).getName().startsWith(prefix)) + return true; + + return false; + } + + private RefList getLooseRefs() { + final RefList oldLoose = looseRefs.get(); + + LooseScanner scan = new LooseScanner(oldLoose); + scan.scan(ALL); + + RefList loose; + if (scan.newLoose != null) { + loose = scan.newLoose.toRefList(); + if (looseRefs.compareAndSet(oldLoose, loose)) + modCnt.incrementAndGet(); + } else + loose = oldLoose; + return loose; + } + + @Override + public Ref getRef(final String needle) throws IOException { + final RefList packed = getPackedRefs(); + Ref ref = null; + for (String prefix : SEARCH_PATH) { + ref = readRef(prefix + needle, packed); + if (ref != null) { + ref = resolve(ref, 0, null, null, packed); + break; + } + } + fireRefsChanged(); + return ref; + } + + @Override + public Map getRefs(String prefix) throws IOException { + final RefList packed = getPackedRefs(); + final RefList oldLoose = looseRefs.get(); + + LooseScanner scan = new LooseScanner(oldLoose); + scan.scan(prefix); + + RefList loose; + if (scan.newLoose != null) { + loose = scan.newLoose.toRefList(); + if (looseRefs.compareAndSet(oldLoose, loose)) + modCnt.incrementAndGet(); + } else + loose = oldLoose; + fireRefsChanged(); + + RefList.Builder symbolic = scan.symbolic; + for (int idx = 0; idx < symbolic.size();) { + Ref ref = symbolic.get(idx); + ref = resolve(ref, 0, prefix, loose, packed); + if (ref != null && ref.getObjectId() != null) { + symbolic.set(idx, ref); + idx++; + } else { + // A broken symbolic reference, we have to drop it from the + // collections the client is about to receive. Should be a + // rare occurrence so pay a copy penalty. + loose = loose.remove(idx); + symbolic.remove(idx); + } + } + + return new RefMap(prefix, packed, upcast(loose), symbolic.toRefList()); + } + + @SuppressWarnings("unchecked") + private RefList upcast(RefList loose) { + return (RefList) loose; + } + + private class LooseScanner { + private final RefList curLoose; + + private int curIdx; + + final RefList.Builder symbolic = new RefList.Builder(4); + + RefList.Builder newLoose; + + LooseScanner(final RefList curLoose) { + this.curLoose = curLoose; + } + + void scan(String prefix) { + if (ALL.equals(prefix)) { + scanOne(HEAD); + scanTree(R_REFS, refsDir); + + // If any entries remain, they are deleted, drop them. + if (newLoose == null && curIdx < curLoose.size()) + newLoose = curLoose.copy(curIdx); + + } else if (prefix.startsWith(R_REFS) && prefix.endsWith("/")) { + curIdx = -(curLoose.find(prefix) + 1); + File dir = new File(refsDir, prefix.substring(R_REFS.length())); + scanTree(prefix, dir); + + // Skip over entries still within the prefix; these have + // been removed from the directory. + while (curIdx < curLoose.size()) { + if (!curLoose.get(curIdx).getName().startsWith(prefix)) + break; + if (newLoose == null) + newLoose = curLoose.copy(curIdx); + curIdx++; + } + + // Keep any entries outside of the prefix space, we + // do not know anything about their status. + if (newLoose != null) { + while (curIdx < curLoose.size()) + newLoose.add(curLoose.get(curIdx++)); + } + } + } + + private boolean scanTree(String prefix, File dir) { + final String[] entries = dir.list(LockFile.FILTER); + if (entries == null) // not a directory or an I/O error + return false; + if (0 < entries.length) { + Arrays.sort(entries); + for (String name : entries) { + File e = new File(dir, name); + if (!scanTree(prefix + name + '/', e)) + scanOne(prefix + name); + } + } + return true; + } + + private void scanOne(String name) { + LooseRef cur; + + if (curIdx < curLoose.size()) { + do { + cur = curLoose.get(curIdx); + int cmp = RefComparator.compareTo(cur, name); + if (cmp < 0) { + // Reference is not loose anymore, its been deleted. + // Skip the name in the new result list. + if (newLoose == null) + newLoose = curLoose.copy(curIdx); + curIdx++; + cur = null; + continue; + } + + if (cmp > 0) // Newly discovered loose reference. + cur = null; + break; + } while (curIdx < curLoose.size()); + } else + cur = null; // Newly discovered loose reference. + + LooseRef n; + try { + n = scanRef(cur, name); + } catch (IOException notValid) { + n = null; + } + + if (n != null) { + if (cur != n && newLoose == null) + newLoose = curLoose.copy(curIdx); + if (newLoose != null) + newLoose.add(n); + if (n.isSymbolic()) + symbolic.add(n); + } else if (cur != null) { + // Tragically, this file is no longer a loose reference. + // Kill our cached entry of it. + if (newLoose == null) + newLoose = curLoose.copy(curIdx); + } + + if (cur != null) + curIdx++; + } + } + + @Override + public Ref peel(final Ref ref) throws IOException { + final Ref leaf = ref.getLeaf(); + if (leaf.isPeeled() || leaf.getObjectId() == null) + return ref; + + RevWalk rw = new RevWalk(getRepository()); + RevObject obj = rw.parseAny(leaf.getObjectId()); + ObjectIdRef newLeaf; + if (obj instanceof RevTag) { + newLeaf = new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf + .getName(), leaf.getObjectId(), rw.peel(obj).copy()); + } else { + newLeaf = new ObjectIdRef.PeeledNonTag(leaf.getStorage(), leaf + .getName(), leaf.getObjectId()); + } + + // Try to remember this peeling in the cache, so we don't have to do + // it again in the future, but only if the reference is unchanged. + if (leaf.getStorage().isLoose()) { + RefList curList = looseRefs.get(); + int idx = curList.find(leaf.getName()); + if (0 <= idx && curList.get(idx) == leaf) { + LooseRef asPeeled = ((LooseRef) leaf).peel(newLeaf); + RefList newList = curList.set(idx, asPeeled); + looseRefs.compareAndSet(curList, newList); + } + } + + return recreate(ref, newLeaf); + } + + private static Ref recreate(final Ref old, final ObjectIdRef leaf) { + if (old.isSymbolic()) { + Ref dst = recreate(old.getTarget(), leaf); + return new SymbolicRef(old.getName(), dst); + } + return leaf; + } + + void storedSymbolicRef(RefDirectoryUpdate u, long modified, String target) { + putLooseRef(newSymbolicRef(modified, u.getRef().getName(), target)); + fireRefsChanged(); + } + + public RefDirectoryUpdate newUpdate(String name, boolean detach) + throws IOException { + final RefList packed = getPackedRefs(); + Ref ref = readRef(name, packed); + if (ref != null) + ref = resolve(ref, 0, null, null, packed); + if (ref == null) + ref = new ObjectIdRef.Unpeeled(NEW, name, null); + else if (detach && ref.isSymbolic()) + ref = new ObjectIdRef.Unpeeled(LOOSE, name, ref.getObjectId()); + return new RefDirectoryUpdate(this, ref); + } + + @Override + public RefDirectoryRename newRename(String fromName, String toName) + throws IOException { + RefDirectoryUpdate from = newUpdate(fromName, false); + RefDirectoryUpdate to = newUpdate(toName, false); + return new RefDirectoryRename(from, to); + } + + void stored(RefDirectoryUpdate update, long modified) { + final ObjectId target = update.getNewObjectId().copy(); + final Ref leaf = update.getRef().getLeaf(); + putLooseRef(new LooseUnpeeled(modified, leaf.getName(), target)); + } + + private void putLooseRef(LooseRef ref) { + RefList cList, nList; + do { + cList = looseRefs.get(); + nList = cList.put(ref); + } while (!looseRefs.compareAndSet(cList, nList)); + modCnt.incrementAndGet(); + fireRefsChanged(); + } + + void delete(RefDirectoryUpdate update) throws IOException { + Ref dst = update.getRef().getLeaf(); + String name = dst.getName(); + + // Write the packed-refs file using an atomic update. We might + // wind up reading it twice, before and after the lock, to ensure + // we don't miss an edit made externally. + final PackedRefList packed = getPackedRefs(); + if (packed.contains(name)) { + LockFile lck = new LockFile(packedRefsFile); + if (!lck.lock()) + throw new IOException(MessageFormat.format( + JGitText.get().cannotLockFile, packedRefsFile)); + try { + PackedRefList cur = readPackedRefs(0, 0); + int idx = cur.find(name); + if (0 <= idx) + commitPackedRefs(lck, cur.remove(idx), packed); + } finally { + lck.unlock(); + } + } + + RefList curLoose, newLoose; + do { + curLoose = looseRefs.get(); + int idx = curLoose.find(name); + if (idx < 0) + break; + newLoose = curLoose.remove(idx); + } while (!looseRefs.compareAndSet(curLoose, newLoose)); + + int levels = levelsIn(name) - 2; + delete(logFor(name), levels); + if (dst.getStorage().isLoose()) { + update.unlock(); + delete(fileFor(name), levels); + } + + modCnt.incrementAndGet(); + fireRefsChanged(); + } + + void log(final RefUpdate update, final String msg, final boolean deref) + throws IOException { + final ObjectId oldId = update.getOldObjectId(); + final ObjectId newId = update.getNewObjectId(); + final Ref ref = update.getRef(); + + PersonIdent ident = update.getRefLogIdent(); + if (ident == null) + ident = new PersonIdent(parent); + else + ident = new PersonIdent(ident); + + final StringBuilder r = new StringBuilder(); + r.append(ObjectId.toString(oldId)); + r.append(' '); + r.append(ObjectId.toString(newId)); + r.append(' '); + r.append(ident.toExternalString()); + r.append('\t'); + r.append(msg); + r.append('\n'); + final byte[] rec = encode(r.toString()); + + if (deref && ref.isSymbolic()) { + log(ref.getName(), rec); + log(ref.getLeaf().getName(), rec); + } else { + log(ref.getName(), rec); + } + } + + private void log(final String refName, final byte[] rec) throws IOException { + final File log = logFor(refName); + final boolean write; + if (isLogAllRefUpdates() && shouldAutoCreateLog(refName)) + write = true; + else if (log.isFile()) + write = true; + else + write = false; + + if (write) { + FileOutputStream out; + try { + out = new FileOutputStream(log, true); + } catch (FileNotFoundException err) { + final File dir = log.getParentFile(); + if (dir.exists()) + throw err; + if (!dir.mkdirs() && !dir.isDirectory()) + throw new IOException(MessageFormat.format(JGitText.get().cannotCreateDirectory, dir)); + out = new FileOutputStream(log, true); + } + try { + out.write(rec); + } finally { + out.close(); + } + } + } + + private boolean isLogAllRefUpdates() { + return parent.getConfig().get(CoreConfig.KEY).isLogAllRefUpdates(); + } + + private boolean shouldAutoCreateLog(final String refName) { + return refName.equals(HEAD) // + || refName.startsWith(R_HEADS) // + || refName.startsWith(R_REMOTES); + } + + private Ref resolve(final Ref ref, int depth, String prefix, + RefList loose, RefList packed) throws IOException { + if (ref.isSymbolic()) { + Ref dst = ref.getTarget(); + + if (MAX_SYMBOLIC_REF_DEPTH <= depth) + return null; // claim it doesn't exist + + // If the cached value can be assumed to be current due to a + // recent scan of the loose directory, use it. + if (loose != null && dst.getName().startsWith(prefix)) { + int idx; + if (0 <= (idx = loose.find(dst.getName()))) + dst = loose.get(idx); + else if (0 <= (idx = packed.find(dst.getName()))) + dst = packed.get(idx); + else + return ref; + } else { + dst = readRef(dst.getName(), packed); + if (dst == null) + return ref; + } + + dst = resolve(dst, depth + 1, prefix, loose, packed); + if (dst == null) + return null; + return new SymbolicRef(ref.getName(), dst); + } + return ref; + } + + private PackedRefList getPackedRefs() throws IOException { + long size = packedRefsFile.length(); + long mtime = size != 0 ? packedRefsFile.lastModified() : 0; + + final PackedRefList curList = packedRefs.get(); + if (size == curList.lastSize && mtime == curList.lastModified) + return curList; + + final PackedRefList newList = readPackedRefs(size, mtime); + if (packedRefs.compareAndSet(curList, newList)) + modCnt.incrementAndGet(); + return newList; + } + + private PackedRefList readPackedRefs(long size, long mtime) + throws IOException { + final BufferedReader br; + try { + br = new BufferedReader(new InputStreamReader(new FileInputStream( + packedRefsFile), CHARSET)); + } catch (FileNotFoundException noPackedRefs) { + // Ignore it and leave the new list empty. + return PackedRefList.NO_PACKED_REFS; + } + try { + return new PackedRefList(parsePackedRefs(br), size, mtime); + } finally { + br.close(); + } + } + + private RefList parsePackedRefs(final BufferedReader br) + throws IOException { + RefList.Builder all = new RefList.Builder(); + Ref last = null; + boolean peeled = false; + boolean needSort = false; + + String p; + while ((p = br.readLine()) != null) { + if (p.charAt(0) == '#') { + if (p.startsWith(PACKED_REFS_HEADER)) { + p = p.substring(PACKED_REFS_HEADER.length()); + peeled = p.contains(PACKED_REFS_PEELED); + } + continue; + } + + if (p.charAt(0) == '^') { + if (last == null) + throw new IOException(JGitText.get().peeledLineBeforeRef); + + ObjectId id = ObjectId.fromString(p.substring(1)); + last = new ObjectIdRef.PeeledTag(PACKED, last.getName(), last + .getObjectId(), id); + all.set(all.size() - 1, last); + continue; + } + + int sp = p.indexOf(' '); + ObjectId id = ObjectId.fromString(p.substring(0, sp)); + String name = copy(p, sp + 1, p.length()); + ObjectIdRef cur; + if (peeled) + cur = new ObjectIdRef.PeeledNonTag(PACKED, name, id); + else + cur = new ObjectIdRef.Unpeeled(PACKED, name, id); + if (last != null && RefComparator.compareTo(last, cur) > 0) + needSort = true; + all.add(cur); + last = cur; + } + + if (needSort) + all.sort(); + return all.toRefList(); + } + + private static String copy(final String src, final int off, final int end) { + // Don't use substring since it could leave a reference to the much + // larger existing string. Force construction of a full new object. + return new StringBuilder(end - off).append(src, off, end).toString(); + } + + private void commitPackedRefs(final LockFile lck, final RefList refs, + final PackedRefList oldPackedList) throws IOException { + new RefWriter(refs) { + @Override + protected void writeFile(String name, byte[] content) + throws IOException { + lck.setNeedStatInformation(true); + try { + lck.write(content); + } catch (IOException ioe) { + throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name), ioe); + } + try { + lck.waitForStatChange(); + } catch (InterruptedException e) { + lck.unlock(); + throw new ObjectWritingException(MessageFormat.format(JGitText.get().interruptedWriting, name)); + } + if (!lck.commit()) + throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name)); + + packedRefs.compareAndSet(oldPackedList, new PackedRefList(refs, + content.length, lck.getCommitLastModified())); + } + }.writePackedRefs(); + } + + private Ref readRef(String name, RefList packed) throws IOException { + final RefList curList = looseRefs.get(); + final int idx = curList.find(name); + if (0 <= idx) { + final LooseRef o = curList.get(idx); + final LooseRef n = scanRef(o, name); + if (n == null) { + if (looseRefs.compareAndSet(curList, curList.remove(idx))) + modCnt.incrementAndGet(); + return packed.get(name); + } + + if (o == n) + return n; + if (looseRefs.compareAndSet(curList, curList.set(idx, n))) + modCnt.incrementAndGet(); + return n; + } + + final LooseRef n = scanRef(null, name); + if (n == null) + return packed.get(name); + if (looseRefs.compareAndSet(curList, curList.add(idx, n))) + modCnt.incrementAndGet(); + return n; + } + + private LooseRef scanRef(LooseRef ref, String name) throws IOException { + final File path = fileFor(name); + final long modified = path.lastModified(); + + if (ref != null) { + if (ref.getLastModified() == modified) + return ref; + name = ref.getName(); + } else if (modified == 0) + return null; + + final byte[] buf; + try { + buf = IO.readFully(path, 4096); + } catch (FileNotFoundException noFile) { + return null; // doesn't exist; not a reference. + } + + int n = buf.length; + if (n == 0) + return null; // empty file; not a reference. + + if (isSymRef(buf, n)) { + // trim trailing whitespace + while (0 < n && Character.isWhitespace(buf[n - 1])) + n--; + if (n < 6) { + String content = RawParseUtils.decode(buf, 0, n); + throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content)); + } + final String target = RawParseUtils.decode(buf, 5, n); + return newSymbolicRef(modified, name, target); + } + + if (n < OBJECT_ID_STRING_LENGTH) + return null; // impossibly short object identifier; not a reference. + + final ObjectId id; + try { + id = ObjectId.fromString(buf, 0); + } catch (IllegalArgumentException notRef) { + while (0 < n && Character.isWhitespace(buf[n - 1])) + n--; + String content = RawParseUtils.decode(buf, 0, n); + throw new IOException(MessageFormat.format(JGitText.get().notARef, name, content)); + } + return new LooseUnpeeled(modified, name, id); + } + + private static boolean isSymRef(final byte[] buf, int n) { + if (n < 6) + return false; + return /**/buf[0] == 'r' // + && buf[1] == 'e' // + && buf[2] == 'f' // + && buf[3] == ':' // + && buf[4] == ' '; + } + + /** If the parent should fire listeners, fires them. */ + private void fireRefsChanged() { + final int last = lastNotifiedModCnt.get(); + final int curr = modCnt.get(); + if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr)) + parent.fireEvent(new RefsChangedEvent()); + } + + /** + * Create a reference update to write a temporary reference. + * + * @return an update for a new temporary reference. + * @throws IOException + * a temporary name cannot be allocated. + */ + RefDirectoryUpdate newTemporaryUpdate() throws IOException { + File tmp = File.createTempFile("renamed_", "_ref", refsDir); + String name = Constants.R_REFS + tmp.getName(); + Ref ref = new ObjectIdRef.Unpeeled(NEW, name, null); + return new RefDirectoryUpdate(this, ref); + } + + /** + * Locate the file on disk for a single reference name. + * + * @param name + * name of the ref, relative to the Git repository top level + * directory (so typically starts with refs/). + * @return the loose file location. + */ + File fileFor(String name) { + if (name.startsWith(R_REFS)) { + name = name.substring(R_REFS.length()); + return new File(refsDir, name); + } + return new File(gitDir, name); + } + + /** + * Locate the log file on disk for a single reference name. + * + * @param name + * name of the ref, relative to the Git repository top level + * directory (so typically starts with refs/). + * @return the log file location. + */ + File logFor(String name) { + if (name.startsWith(R_REFS)) { + name = name.substring(R_REFS.length()); + return new File(logsRefsDir, name); + } + return new File(logsDir, name); + } + + static int levelsIn(final String name) { + int count = 0; + for (int p = name.indexOf('/'); p >= 0; p = name.indexOf('/', p + 1)) + count++; + return count; + } + + static void delete(final File file, final int depth) throws IOException { + if (!file.delete() && file.isFile()) + throw new IOException(MessageFormat.format(JGitText.get().fileCannotBeDeleted, file)); + + File dir = file.getParentFile(); + for (int i = 0; i < depth; ++i) { + if (!dir.delete()) + break; // ignore problem here + dir = dir.getParentFile(); + } + } + + private static class PackedRefList extends RefList { + static final PackedRefList NO_PACKED_REFS = new PackedRefList(RefList + .emptyList(), 0, 0); + + /** Last length of the packed-refs file when we read it. */ + final long lastSize; + + /** Last modified time of the packed-refs file when we read it. */ + final long lastModified; + + PackedRefList(RefList src, long size, long mtime) { + super(src); + lastSize = size; + lastModified = mtime; + } + } + + private static LooseSymbolicRef newSymbolicRef(long lastModified, + String name, String target) { + Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null); + return new LooseSymbolicRef(lastModified, name, dst); + } + + private static interface LooseRef extends Ref { + long getLastModified(); + + LooseRef peel(ObjectIdRef newLeaf); + } + + private final static class LoosePeeledTag extends ObjectIdRef.PeeledTag + implements LooseRef { + private final long lastModified; + + LoosePeeledTag(long mtime, String refName, ObjectId id, ObjectId p) { + super(LOOSE, refName, id, p); + this.lastModified = mtime; + } + + public long getLastModified() { + return lastModified; + } + + public LooseRef peel(ObjectIdRef newLeaf) { + return this; + } + } + + private final static class LooseNonTag extends ObjectIdRef.PeeledNonTag + implements LooseRef { + private final long lastModified; + + LooseNonTag(long mtime, String refName, ObjectId id) { + super(LOOSE, refName, id); + this.lastModified = mtime; + } + + public long getLastModified() { + return lastModified; + } + + public LooseRef peel(ObjectIdRef newLeaf) { + return this; + } + } + + private final static class LooseUnpeeled extends ObjectIdRef.Unpeeled + implements LooseRef { + private final long lastModified; + + LooseUnpeeled(long mtime, String refName, ObjectId id) { + super(LOOSE, refName, id); + this.lastModified = mtime; + } + + public long getLastModified() { + return lastModified; + } + + public LooseRef peel(ObjectIdRef newLeaf) { + if (newLeaf.getPeeledObjectId() != null) + return new LoosePeeledTag(lastModified, getName(), + getObjectId(), newLeaf.getPeeledObjectId()); + else + return new LooseNonTag(lastModified, getName(), getObjectId()); + } + } + + private final static class LooseSymbolicRef extends SymbolicRef implements + LooseRef { + private final long lastModified; + + LooseSymbolicRef(long mtime, String refName, Ref target) { + super(refName, target); + this.lastModified = mtime; + } + + public long getLastModified() { + return lastModified; + } + + public LooseRef peel(ObjectIdRef newLeaf) { + // We should never try to peel the symbolic references. + throw new UnsupportedOperationException(); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java new file mode 100644 index 0000000000..b43b70f1e4 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2010, Google Inc. + * Copyright (C) 2009, Robin Rosenberg + * 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.IOException; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.RefRename; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.revwalk.RevWalk; + +/** + * Rename any reference stored by {@link RefDirectory}. + *

+ * This class works by first renaming the source reference to a temporary name, + * then renaming the temporary name to the final destination reference. + *

+ * This strategy permits switching a reference like {@code refs/heads/foo}, + * which is a file, to {@code refs/heads/foo/bar}, which is stored inside a + * directory that happens to match the source name. + */ +class RefDirectoryRename extends RefRename { + private final RefDirectory refdb; + + /** + * The value of the source reference at the start of the rename. + *

+ * At the end of the rename the destination reference must have this same + * value, otherwise we have a concurrent update and the rename must fail + * without making any changes. + */ + private ObjectId objId; + + /** True if HEAD must be moved to the destination reference. */ + private boolean updateHEAD; + + /** A reference we backup {@link #objId} into during the rename. */ + private RefDirectoryUpdate tmp; + + RefDirectoryRename(RefDirectoryUpdate src, RefDirectoryUpdate dst) { + super(src, dst); + refdb = src.getRefDatabase(); + } + + @Override + protected Result doRename() throws IOException { + if (source.getRef().isSymbolic()) + return Result.IO_FAILURE; // not supported + + final RevWalk rw = new RevWalk(refdb.getRepository()); + objId = source.getOldObjectId(); + updateHEAD = needToUpdateHEAD(); + tmp = refdb.newTemporaryUpdate(); + try { + // First backup the source so its never unreachable. + tmp.setNewObjectId(objId); + tmp.setForceUpdate(true); + tmp.disableRefLog(); + switch (tmp.update(rw)) { + case NEW: + case FORCED: + case NO_CHANGE: + break; + default: + return tmp.getResult(); + } + + // Save the source's log under the temporary name, we must do + // this before we delete the source, otherwise we lose the log. + if (!renameLog(source, tmp)) + return Result.IO_FAILURE; + + // If HEAD has to be updated, link it now to destination. + // We have to link before we delete, otherwise the delete + // fails because its the current branch. + RefUpdate dst = destination; + if (updateHEAD) { + if (!linkHEAD(destination)) { + renameLog(tmp, source); + return Result.LOCK_FAILURE; + } + + // Replace the update operation so HEAD will log the rename. + dst = refdb.newUpdate(Constants.HEAD, false); + dst.setRefLogIdent(destination.getRefLogIdent()); + dst.setRefLogMessage(destination.getRefLogMessage(), false); + } + + // Delete the source name so its path is free for replacement. + source.setExpectedOldObjectId(objId); + source.setForceUpdate(true); + source.disableRefLog(); + if (source.delete(rw) != Result.FORCED) { + renameLog(tmp, source); + if (updateHEAD) + linkHEAD(source); + return source.getResult(); + } + + // Move the log to the destination. + if (!renameLog(tmp, destination)) { + renameLog(tmp, source); + source.setExpectedOldObjectId(ObjectId.zeroId()); + source.setNewObjectId(objId); + source.update(rw); + if (updateHEAD) + linkHEAD(source); + return Result.IO_FAILURE; + } + + // Create the destination, logging the rename during the creation. + dst.setExpectedOldObjectId(ObjectId.zeroId()); + dst.setNewObjectId(objId); + if (dst.update(rw) != Result.NEW) { + // If we didn't create the destination we have to undo + // our work. Put the log back and restore source. + if (renameLog(destination, tmp)) + renameLog(tmp, source); + source.setExpectedOldObjectId(ObjectId.zeroId()); + source.setNewObjectId(objId); + source.update(rw); + if (updateHEAD) + linkHEAD(source); + return dst.getResult(); + } + + return Result.RENAMED; + } finally { + // Always try to free the temporary name. + try { + refdb.delete(tmp); + } catch (IOException err) { + refdb.fileFor(tmp.getName()).delete(); + } + } + } + + private boolean renameLog(RefUpdate src, RefUpdate dst) { + File srcLog = refdb.logFor(src.getName()); + File dstLog = refdb.logFor(dst.getName()); + + if (!srcLog.exists()) + return true; + + if (!rename(srcLog, dstLog)) + return false; + + try { + final int levels = RefDirectory.levelsIn(src.getName()) - 2; + RefDirectory.delete(srcLog, levels); + return true; + } catch (IOException e) { + rename(dstLog, srcLog); + return false; + } + } + + private static boolean rename(File src, File dst) { + if (src.renameTo(dst)) + return true; + + File dir = dst.getParentFile(); + if ((dir.exists() || !dir.mkdirs()) && !dir.isDirectory()) + return false; + return src.renameTo(dst); + } + + private boolean linkHEAD(RefUpdate target) { + try { + RefUpdate u = refdb.newUpdate(Constants.HEAD, false); + u.disableRefLog(); + switch (u.link(target.getName())) { + case NEW: + case FORCED: + case NO_CHANGE: + return true; + default: + return false; + } + } catch (IOException e) { + return false; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryUpdate.java new file mode 100644 index 0000000000..8d35ec34f6 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryUpdate.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2009-2010, Google Inc. + * Copyright (C) 2008, Shawn O. Pearce + * 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 static org.eclipse.jgit.lib.Constants.encode; + +import java.io.IOException; + +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; + +/** Updates any reference stored by {@link RefDirectory}. */ +class RefDirectoryUpdate extends RefUpdate { + private final RefDirectory database; + + private LockFile lock; + + RefDirectoryUpdate(final RefDirectory r, final Ref ref) { + super(ref); + database = r; + } + + @Override + protected RefDirectory getRefDatabase() { + return database; + } + + @Override + protected Repository getRepository() { + return database.getRepository(); + } + + @Override + protected boolean tryLock(boolean deref) throws IOException { + Ref dst = getRef(); + if (deref) + dst = dst.getLeaf(); + String name = dst.getName(); + lock = new LockFile(database.fileFor(name)); + if (lock.lock()) { + dst = database.getRef(name); + setOldObjectId(dst != null ? dst.getObjectId() : null); + return true; + } else { + return false; + } + } + + @Override + protected void unlock() { + if (lock != null) { + lock.unlock(); + lock = null; + } + } + + @Override + protected Result doUpdate(final Result status) throws IOException { + lock.setNeedStatInformation(true); + lock.write(getNewObjectId()); + + String msg = getRefLogMessage(); + if (msg != null) { + if (isRefLogIncludingResult()) { + String strResult = toResultString(status); + if (strResult != null) { + if (msg.length() > 0) + msg = msg + ": " + strResult; + else + msg = strResult; + } + } + database.log(this, msg, true); + } + if (!lock.commit()) + return Result.LOCK_FAILURE; + database.stored(this, lock.getCommitLastModified()); + return status; + } + + private String toResultString(final Result status) { + switch (status) { + case FORCED: + return "forced-update"; + case FAST_FORWARD: + return "fast forward"; + case NEW: + return "created"; + default: + return null; + } + } + + @Override + protected Result doDelete(final Result status) throws IOException { + if (getRef().getLeaf().getStorage() != Ref.Storage.NEW) + database.delete(this); + return status; + } + + @Override + protected Result doLink(final String target) throws IOException { + lock.setNeedStatInformation(true); + lock.write(encode(RefDirectory.SYMREF + target + '\n')); + + String msg = getRefLogMessage(); + if (msg != null) + database.log(this, msg, false); + if (!lock.commit()) + return Result.LOCK_FAILURE; + database.storedSymbolicRef(this, lock.getCommitLastModified(), target); + + if (getRef().getStorage() == Ref.Storage.NEW) + return Result.NEW; + return Result.FORCED; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ReflogReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ReflogReader.java new file mode 100644 index 0000000000..75214308d6 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ReflogReader.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2009, Robin Rosenberg + * Copyright (C) 2009, Robin Rosenberg + * 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.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.RawParseUtils; + +/** + * Utility for reading reflog entries + */ +public class ReflogReader { + /** + * Parsed reflog entry + */ + static public class Entry { + private ObjectId oldId; + + private ObjectId newId; + + private PersonIdent who; + + private String comment; + + Entry(byte[] raw, int pos) { + oldId = ObjectId.fromString(raw, pos); + pos += Constants.OBJECT_ID_STRING_LENGTH; + if (raw[pos++] != ' ') + throw new IllegalArgumentException( + JGitText.get().rawLogMessageDoesNotParseAsLogEntry); + newId = ObjectId.fromString(raw, pos); + pos += Constants.OBJECT_ID_STRING_LENGTH; + if (raw[pos++] != ' ') { + throw new IllegalArgumentException( + JGitText.get().rawLogMessageDoesNotParseAsLogEntry); + } + who = RawParseUtils.parsePersonIdentOnly(raw, pos); + int p0 = RawParseUtils.next(raw, pos, '\t'); // personident has no + // \t + if (p0 == -1) { + throw new IllegalArgumentException( + JGitText.get().rawLogMessageDoesNotParseAsLogEntry); + } + int p1 = RawParseUtils.nextLF(raw, p0); + if (p1 == -1) { + throw new IllegalArgumentException( + JGitText.get().rawLogMessageDoesNotParseAsLogEntry); + } + comment = RawParseUtils.decode(raw, p0, p1 - 1); + } + + /** + * @return the commit id before the change + */ + public ObjectId getOldId() { + return oldId; + } + + /** + * @return the commit id after the change + */ + public ObjectId getNewId() { + return newId; + } + + /** + * @return user performin the change + */ + public PersonIdent getWho() { + return who; + } + + /** + * @return textual description of the change + */ + public String getComment() { + return comment; + } + + @Override + public String toString() { + return "Entry[" + oldId.name() + ", " + newId.name() + ", " + getWho() + ", " + + getComment() + "]"; + } + } + + private File logName; + + ReflogReader(Repository db, String refname) { + logName = new File(db.getDirectory(), "logs/" + refname); + } + + /** + * Get the last entry in the reflog + * + * @return the latest reflog entry, or null if no log + * @throws IOException + */ + public Entry getLastEntry() throws IOException { + List entries = getReverseEntries(1); + return entries.size() > 0 ? entries.get(0) : null; + } + + /** + * @return all reflog entries in reverse order + * @throws IOException + */ + public List getReverseEntries() throws IOException { + return getReverseEntries(Integer.MAX_VALUE); + } + + /** + * @param max + * max numer of entries to read + * @return all reflog entries in reverse order + * @throws IOException + */ + public List getReverseEntries(int max) throws IOException { + final byte[] log; + try { + log = IO.readFully(logName); + } catch (FileNotFoundException e) { + return Collections.emptyList(); + } + + int rs = RawParseUtils.prevLF(log, log.length); + List ret = new ArrayList(); + while (rs >= 0 && max-- > 0) { + rs = RawParseUtils.prevLF(log, rs); + Entry entry = new Entry(log, rs < 0 ? 0 : rs + 2); + ret.add(entry); + } + return ret; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectCache.java new file mode 100644 index 0000000000..92f4824254 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectCache.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * 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.lang.ref.SoftReference; + +class UnpackedObjectCache { + private static final int CACHE_SZ = 1024; + + private static final SoftReference DEAD; + + private static int hash(final long position) { + return (((int) position) << 22) >>> 22; + } + + private static int maxByteCount; + + private static final Slot[] cache; + + private static Slot lruHead; + + private static Slot lruTail; + + private static int openByteCount; + + static { + DEAD = new SoftReference(null); + maxByteCount = new WindowCacheConfig().getDeltaBaseCacheLimit(); + + cache = new Slot[CACHE_SZ]; + for (int i = 0; i < CACHE_SZ; i++) + cache[i] = new Slot(); + } + + static synchronized void reconfigure(final WindowCacheConfig cfg) { + final int dbLimit = cfg.getDeltaBaseCacheLimit(); + if (maxByteCount != dbLimit) { + maxByteCount = dbLimit; + releaseMemory(); + } + } + + static synchronized Entry get(final PackFile pack, final long position) { + final Slot e = cache[hash(position)]; + if (e.provider == pack && e.position == position) { + final Entry buf = e.data.get(); + if (buf != null) { + moveToHead(e); + return buf; + } + } + return null; + } + + static synchronized void store(final PackFile pack, final long position, + final byte[] data, final int objectType) { + if (data.length > maxByteCount) + return; // Too large to cache. + + final Slot e = cache[hash(position)]; + clearEntry(e); + + openByteCount += data.length; + releaseMemory(); + + e.provider = pack; + e.position = position; + e.sz = data.length; + e.data = new SoftReference(new Entry(data, objectType)); + moveToHead(e); + } + + private static void releaseMemory() { + while (openByteCount > maxByteCount && lruTail != null) { + final Slot currOldest = lruTail; + final Slot nextOldest = currOldest.lruPrev; + + clearEntry(currOldest); + currOldest.lruPrev = null; + currOldest.lruNext = null; + + if (nextOldest == null) + lruHead = null; + else + nextOldest.lruNext = null; + lruTail = nextOldest; + } + } + + static synchronized void purge(final PackFile file) { + for (final Slot e : cache) { + if (e.provider == file) { + clearEntry(e); + unlink(e); + } + } + } + + private static void moveToHead(final Slot e) { + unlink(e); + e.lruPrev = null; + e.lruNext = lruHead; + if (lruHead != null) + lruHead.lruPrev = e; + else + lruTail = e; + lruHead = e; + } + + private static void unlink(final Slot e) { + final Slot prev = e.lruPrev; + final Slot next = e.lruNext; + if (prev != null) + prev.lruNext = next; + if (next != null) + next.lruPrev = prev; + } + + private static void clearEntry(final Slot e) { + openByteCount -= e.sz; + e.provider = null; + e.data = DEAD; + e.sz = 0; + } + + private UnpackedObjectCache() { + throw new UnsupportedOperationException(); + } + + static class Entry { + final byte[] data; + + final int type; + + Entry(final byte[] aData, final int aType) { + data = aData; + type = aType; + } + } + + private static class Slot { + Slot lruPrev; + + Slot lruNext; + + PackFile provider; + + long position; + + int sz; + + SoftReference data = DEAD; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java new file mode 100644 index 0000000000..054a4ae37e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.FileNotFoundException; +import java.io.IOException; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.InflaterCache; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.MutableInteger; +import org.eclipse.jgit.util.RawParseUtils; + +/** + * Loose object loader. This class loads an object not stored in a pack. + */ +public class UnpackedObjectLoader extends ObjectLoader { + private final int objectType; + + private final int objectSize; + + private final byte[] bytes; + + /** + * Construct an ObjectLoader to read from the file. + * + * @param path + * location of the loose object to read. + * @param id + * expected identity of the object being loaded, if known. + * @throws FileNotFoundException + * the loose object file does not exist. + * @throws IOException + * the loose object file exists, but is corrupt. + */ + public UnpackedObjectLoader(final File path, final AnyObjectId id) + throws IOException { + this(IO.readFully(path), id); + } + + /** + * Construct an ObjectLoader from a loose object's compressed form. + * + * @param compressed + * entire content of the loose object file. + * @throws CorruptObjectException + * The compressed data supplied does not match the format for a + * valid loose object. + */ + public UnpackedObjectLoader(final byte[] compressed) + throws CorruptObjectException { + this(compressed, null); + } + + private UnpackedObjectLoader(final byte[] compressed, final AnyObjectId id) + throws CorruptObjectException { + // Try to determine if this is a legacy format loose object or + // a new style loose object. The legacy format was completely + // compressed with zlib so the first byte must be 0x78 (15-bit + // window size, deflated) and the first 16 bit word must be + // evenly divisible by 31. Otherwise its a new style loose + // object. + // + final Inflater inflater = InflaterCache.get(); + try { + final int fb = compressed[0] & 0xff; + if (fb == 0x78 && (((fb << 8) | compressed[1] & 0xff) % 31) == 0) { + inflater.setInput(compressed); + final byte[] hdr = new byte[64]; + int avail = 0; + while (!inflater.finished() && avail < hdr.length) + try { + int uncompressed = inflater.inflate(hdr, avail, + hdr.length - avail); + if (uncompressed == 0) { + throw new CorruptObjectException(id, + JGitText.get().corruptObjectBadStreamCorruptHeader); + } + avail += uncompressed; + } catch (DataFormatException dfe) { + final CorruptObjectException coe; + coe = new CorruptObjectException(id, JGitText.get().corruptObjectBadStream); + coe.initCause(dfe); + throw coe; + } + if (avail < 5) + throw new CorruptObjectException(id, JGitText.get().corruptObjectNoHeader); + + final MutableInteger p = new MutableInteger(); + objectType = Constants.decodeTypeString(id, hdr, (byte) ' ', p); + objectSize = RawParseUtils.parseBase10(hdr, p.value, p); + if (objectSize < 0) + throw new CorruptObjectException(id, JGitText.get().corruptObjectNegativeSize); + if (hdr[p.value++] != 0) + throw new CorruptObjectException(id, JGitText.get().corruptObjectGarbageAfterSize); + bytes = new byte[objectSize]; + if (p.value < avail) + System.arraycopy(hdr, p.value, bytes, 0, avail - p.value); + decompress(id, inflater, avail - p.value); + } else { + int p = 0; + int c = compressed[p++] & 0xff; + final int typeCode = (c >> 4) & 7; + int size = c & 15; + int shift = 4; + while ((c & 0x80) != 0) { + c = compressed[p++] & 0xff; + size += (c & 0x7f) << shift; + shift += 7; + } + + switch (typeCode) { + case Constants.OBJ_COMMIT: + case Constants.OBJ_TREE: + case Constants.OBJ_BLOB: + case Constants.OBJ_TAG: + objectType = typeCode; + break; + default: + throw new CorruptObjectException(id, JGitText.get().corruptObjectInvalidType); + } + + objectSize = size; + bytes = new byte[objectSize]; + inflater.setInput(compressed, p, compressed.length - p); + decompress(id, inflater, 0); + } + } finally { + InflaterCache.release(inflater); + } + } + + private void decompress(final AnyObjectId id, final Inflater inf, int p) + throws CorruptObjectException { + try { + while (!inf.finished()) { + int uncompressed = inf.inflate(bytes, p, objectSize - p); + p += uncompressed; + if (uncompressed == 0 && !inf.finished()) { + throw new CorruptObjectException(id, + JGitText.get().corruptObjectBadStreamCorruptHeader); + } + } + } catch (DataFormatException dfe) { + final CorruptObjectException coe; + coe = new CorruptObjectException(id, JGitText.get().corruptObjectBadStream); + coe.initCause(dfe); + throw coe; + } + if (p != objectSize) + throw new CorruptObjectException(id, JGitText.get().corruptObjectIncorrectLength); + } + + @Override + public int getType() { + return objectType; + } + + @Override + public long getSize() { + return objectSize; + } + + @Override + public byte[] getCachedBytes() { + return bytes; + } + + @Override + public int getRawType() { + return objectType; + } + + @Override + public long getRawSize() { + return objectSize; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java new file mode 100644 index 0000000000..ba7781a0f1 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.IOException; +import java.text.MessageFormat; +import java.util.zip.DataFormatException; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; + +/** Reader for a non-delta (just deflated) object in a pack file. */ +class WholePackedObjectLoader extends PackedObjectLoader { + private static final int OBJ_COMMIT = Constants.OBJ_COMMIT; + + WholePackedObjectLoader(final PackFile pr, final long objectOffset, + final int headerSize, final int type, final int size) { + super(pr, objectOffset, headerSize); + objectType = type; + objectSize = size; + } + + @Override + void materialize(final WindowCursor curs) throws IOException { + if (cachedBytes != null) { + return; + } + + if (objectType != OBJ_COMMIT) { + UnpackedObjectCache.Entry cache = pack.readCache(objectOffset); + if (cache != null) { + curs.release(); + cachedBytes = cache.data; + return; + } + } + + try { + cachedBytes = pack.decompress(objectOffset + headerSize, + objectSize, curs); + curs.release(); + if (objectType != OBJ_COMMIT) + pack.saveCache(objectOffset, cachedBytes, objectType); + } catch (DataFormatException dfe) { + final CorruptObjectException coe; + coe = new CorruptObjectException(MessageFormat.format(JGitText.get().objectAtHasBadZlibStream, + objectOffset, pack.getPackFile())); + coe.initCause(dfe); + throw coe; + } + } + + @Override + public int getRawType() { + return objectType; + } + + @Override + public long getRawSize() { + return objectSize; + } + + @Override + ObjectId getDeltaBase() { + return null; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCache.java new file mode 100644 index 0000000000..39633ee5ef --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCache.java @@ -0,0 +1,624 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Shawn O. Pearce + * 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.IOException; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.locks.ReentrantLock; + +import org.eclipse.jgit.JGitText; + +/** + * Caches slices of a {@link PackFile} in memory for faster read access. + *

+ * The WindowCache serves as a Java based "buffer cache", loading segments of a + * PackFile into the JVM heap prior to use. As JGit often wants to do reads of + * only tiny slices of a file, the WindowCache tries to smooth out these tiny + * reads into larger block-sized IO operations. + *

+ * Whenever a cache miss occurs, {@link #load(PackFile, long)} is invoked by + * exactly one thread for the given (PackFile,position) key tuple. + * This is ensured by an array of locks, with the tuple hashed to a lock + * instance. + *

+ * During a miss, older entries are evicted from the cache so long as + * {@link #isFull()} returns true. + *

+ * Its too expensive during object access to be 100% accurate with a least + * recently used (LRU) algorithm. Strictly ordering every read is a lot of + * overhead that typically doesn't yield a corresponding benefit to the + * application. + *

+ * This cache implements a loose LRU policy by randomly picking a window + * comprised of roughly 10% of the cache, and evicting the oldest accessed entry + * within that window. + *

+ * Entities created by the cache are held under SoftReferences, permitting the + * Java runtime's garbage collector to evict entries when heap memory gets low. + * Most JREs implement a loose least recently used algorithm for this eviction. + *

+ * The internal hash table does not expand at runtime, instead it is fixed in + * size at cache creation time. The internal lock table used to gate load + * invocations is also fixed in size. + *

+ * The key tuple is passed through to methods as a pair of parameters rather + * than as a single Object, thus reducing the transient memory allocations of + * callers. It is more efficient to avoid the allocation, as we can't be 100% + * sure that a JIT would be able to stack-allocate a key tuple. + *

+ * This cache has an implementation rule such that: + *

    + *
  • {@link #load(PackFile, long)} is invoked by at most one thread at a time + * for a given (PackFile,position) tuple.
  • + *
  • For every load() invocation there is exactly one + * {@link #createRef(PackFile, long, ByteWindow)} invocation to wrap a + * SoftReference around the cached entity.
  • + *
  • For every Reference created by createRef() there will be + * exactly one call to {@link #clear(Ref)} to cleanup any resources associated + * with the (now expired) cached entity.
  • + *
+ *

+ * Therefore, it is safe to perform resource accounting increments during the + * {@link #load(PackFile, long)} or + * {@link #createRef(PackFile, long, ByteWindow)} methods, and matching + * decrements during {@link #clear(Ref)}. Implementors may need to override + * {@link #createRef(PackFile, long, ByteWindow)} in order to embed additional + * accounting information into an implementation specific {@link Ref} subclass, + * as the cached entity may have already been evicted by the JRE's garbage + * collector. + *

+ * To maintain higher concurrency workloads, during eviction only one thread + * performs the eviction work, while other threads can continue to insert new + * objects in parallel. This means that the cache can be temporarily over limit, + * especially if the nominated eviction thread is being starved relative to the + * other threads. + */ +public class WindowCache { + private static final int bits(int newSize) { + if (newSize < 4096) + throw new IllegalArgumentException(JGitText.get().invalidWindowSize); + if (Integer.bitCount(newSize) != 1) + throw new IllegalArgumentException(JGitText.get().windowSizeMustBePowerOf2); + return Integer.numberOfTrailingZeros(newSize); + } + + private static final Random rng = new Random(); + + private static volatile WindowCache cache; + + static { + reconfigure(new WindowCacheConfig()); + } + + /** + * Modify the configuration of the window cache. + *

+ * The new configuration is applied immediately. If the new limits are + * smaller than what what is currently cached, older entries will be purged + * as soon as possible to allow the cache to meet the new limit. + * + * @param packedGitLimit + * maximum number of bytes to hold within this instance. + * @param packedGitWindowSize + * number of bytes per window within the cache. + * @param packedGitMMAP + * true to enable use of mmap when creating windows. + * @param deltaBaseCacheLimit + * number of bytes to hold in the delta base cache. + * @deprecated Use {@link WindowCacheConfig} instead. + */ + public static void reconfigure(final int packedGitLimit, + final int packedGitWindowSize, final boolean packedGitMMAP, + final int deltaBaseCacheLimit) { + final WindowCacheConfig c = new WindowCacheConfig(); + c.setPackedGitLimit(packedGitLimit); + c.setPackedGitWindowSize(packedGitWindowSize); + c.setPackedGitMMAP(packedGitMMAP); + c.setDeltaBaseCacheLimit(deltaBaseCacheLimit); + reconfigure(c); + } + + /** + * Modify the configuration of the window cache. + *

+ * The new configuration is applied immediately. If the new limits are + * smaller than what what is currently cached, older entries will be purged + * as soon as possible to allow the cache to meet the new limit. + * + * @param cfg + * the new window cache configuration. + * @throws IllegalArgumentException + * the cache configuration contains one or more invalid + * settings, usually too low of a limit. + */ + public static void reconfigure(final WindowCacheConfig cfg) { + final WindowCache nc = new WindowCache(cfg); + final WindowCache oc = cache; + if (oc != null) + oc.removeAll(); + cache = nc; + UnpackedObjectCache.reconfigure(cfg); + } + + static WindowCache getInstance() { + return cache; + } + + static final ByteWindow get(final PackFile pack, final long offset) + throws IOException { + final WindowCache c = cache; + final ByteWindow r = c.getOrLoad(pack, c.toStart(offset)); + if (c != cache) { + // The cache was reconfigured while we were using the old one + // to load this window. The window is still valid, but our + // cache may think its still live. Ensure the window is removed + // from the old cache so resources can be released. + // + c.removeAll(); + } + return r; + } + + static final void purge(final PackFile pack) { + cache.removeAll(pack); + } + + /** ReferenceQueue to cleanup released and garbage collected windows. */ + private final ReferenceQueue queue; + + /** Number of entries in {@link #table}. */ + private final int tableSize; + + /** Access clock for loose LRU. */ + private final AtomicLong clock; + + /** Hash bucket directory; entries are chained below. */ + private final AtomicReferenceArray table; + + /** Locks to prevent concurrent loads for same (PackFile,position). */ + private final Lock[] locks; + + /** Lock to elect the eviction thread after a load occurs. */ + private final ReentrantLock evictLock; + + /** Number of {@link #table} buckets to scan for an eviction window. */ + private final int evictBatch; + + private final int maxFiles; + + private final long maxBytes; + + private final boolean mmap; + + private final int windowSizeShift; + + private final int windowSize; + + private final AtomicInteger openFiles; + + private final AtomicLong openBytes; + + private WindowCache(final WindowCacheConfig cfg) { + tableSize = tableSize(cfg); + final int lockCount = lockCount(cfg); + if (tableSize < 1) + throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1); + if (lockCount < 1) + throw new IllegalArgumentException(JGitText.get().lockCountMustBeGreaterOrEqual1); + + queue = new ReferenceQueue(); + clock = new AtomicLong(1); + table = new AtomicReferenceArray(tableSize); + locks = new Lock[lockCount]; + for (int i = 0; i < locks.length; i++) + locks[i] = new Lock(); + evictLock = new ReentrantLock(); + + int eb = (int) (tableSize * .1); + if (64 < eb) + eb = 64; + else if (eb < 4) + eb = 4; + if (tableSize < eb) + eb = tableSize; + evictBatch = eb; + + maxFiles = cfg.getPackedGitOpenFiles(); + maxBytes = cfg.getPackedGitLimit(); + mmap = cfg.isPackedGitMMAP(); + windowSizeShift = bits(cfg.getPackedGitWindowSize()); + windowSize = 1 << windowSizeShift; + + openFiles = new AtomicInteger(); + openBytes = new AtomicLong(); + + if (maxFiles < 1) + throw new IllegalArgumentException(JGitText.get().openFilesMustBeAtLeast1); + if (maxBytes < windowSize) + throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit); + } + + int getOpenFiles() { + return openFiles.get(); + } + + long getOpenBytes() { + return openBytes.get(); + } + + private int hash(final int packHash, final long off) { + return packHash + (int) (off >>> windowSizeShift); + } + + private ByteWindow load(final PackFile pack, final long offset) + throws IOException { + if (pack.beginWindowCache()) + openFiles.incrementAndGet(); + try { + if (mmap) + return pack.mmap(offset, windowSize); + return pack.read(offset, windowSize); + } catch (IOException e) { + close(pack); + throw e; + } catch (RuntimeException e) { + close(pack); + throw e; + } catch (Error e) { + close(pack); + throw e; + } + } + + private Ref createRef(final PackFile p, final long o, final ByteWindow v) { + final Ref ref = new Ref(p, o, v, queue); + openBytes.addAndGet(ref.size); + return ref; + } + + private void clear(final Ref ref) { + openBytes.addAndGet(-ref.size); + close(ref.pack); + } + + private void close(final PackFile pack) { + if (pack.endWindowCache()) + openFiles.decrementAndGet(); + } + + private boolean isFull() { + return maxFiles < openFiles.get() || maxBytes < openBytes.get(); + } + + private long toStart(final long offset) { + return (offset >>> windowSizeShift) << windowSizeShift; + } + + private static int tableSize(final WindowCacheConfig cfg) { + final int wsz = cfg.getPackedGitWindowSize(); + final long limit = cfg.getPackedGitLimit(); + if (wsz <= 0) + throw new IllegalArgumentException(JGitText.get().invalidWindowSize); + if (limit < wsz) + throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit); + return (int) Math.min(5 * (limit / wsz) / 2, 2000000000); + } + + private static int lockCount(final WindowCacheConfig cfg) { + return Math.max(cfg.getPackedGitOpenFiles(), 32); + } + + /** + * Lookup a cached object, creating and loading it if it doesn't exist. + * + * @param pack + * the pack that "contains" the cached object. + * @param position + * offset within pack of the object. + * @return the object reference. + * @throws IOException + * the object reference was not in the cache and could not be + * obtained by {@link #load(PackFile, long)}. + */ + private ByteWindow getOrLoad(final PackFile pack, final long position) + throws IOException { + final int slot = slot(pack, position); + final Entry e1 = table.get(slot); + ByteWindow v = scan(e1, pack, position); + if (v != null) + return v; + + synchronized (lock(pack, position)) { + Entry e2 = table.get(slot); + if (e2 != e1) { + v = scan(e2, pack, position); + if (v != null) + return v; + } + + v = load(pack, position); + final Ref ref = createRef(pack, position, v); + hit(ref); + for (;;) { + final Entry n = new Entry(clean(e2), ref); + if (table.compareAndSet(slot, e2, n)) + break; + e2 = table.get(slot); + } + } + + if (evictLock.tryLock()) { + try { + gc(); + evict(); + } finally { + evictLock.unlock(); + } + } + + return v; + } + + private ByteWindow scan(Entry n, final PackFile pack, final long position) { + for (; n != null; n = n.next) { + final Ref r = n.ref; + if (r.pack == pack && r.position == position) { + final ByteWindow v = r.get(); + if (v != null) { + hit(r); + return v; + } + n.kill(); + break; + } + } + return null; + } + + private void hit(final Ref r) { + // We don't need to be 100% accurate here. Its sufficient that at least + // one thread performs the increment. Any other concurrent access at + // exactly the same time can simply use the same clock value. + // + // Consequently we attempt the set, but we don't try to recover should + // it fail. This is why we don't use getAndIncrement() here. + // + final long c = clock.get(); + clock.compareAndSet(c, c + 1); + r.lastAccess = c; + } + + private void evict() { + while (isFull()) { + int ptr = rng.nextInt(tableSize); + Entry old = null; + int slot = 0; + for (int b = evictBatch - 1; b >= 0; b--, ptr++) { + if (tableSize <= ptr) + ptr = 0; + for (Entry e = table.get(ptr); e != null; e = e.next) { + if (e.dead) + continue; + if (old == null || e.ref.lastAccess < old.ref.lastAccess) { + old = e; + slot = ptr; + } + } + } + if (old != null) { + old.kill(); + gc(); + final Entry e1 = table.get(slot); + table.compareAndSet(slot, e1, clean(e1)); + } + } + } + + /** + * Clear every entry from the cache. + *

+ * This is a last-ditch effort to clear out the cache, such as before it + * gets replaced by another cache that is configured differently. This + * method tries to force every cached entry through {@link #clear(Ref)} to + * ensure that resources are correctly accounted for and cleaned up by the + * subclass. A concurrent reader loading entries while this method is + * running may cause resource accounting failures. + */ + private void removeAll() { + for (int s = 0; s < tableSize; s++) { + Entry e1; + do { + e1 = table.get(s); + for (Entry e = e1; e != null; e = e.next) + e.kill(); + } while (!table.compareAndSet(s, e1, null)); + } + gc(); + } + + /** + * Clear all entries related to a single file. + *

+ * Typically this method is invoked during {@link PackFile#close()}, when we + * know the pack is never going to be useful to us again (for example, it no + * longer exists on disk). A concurrent reader loading an entry from this + * same pack may cause the pack to become stuck in the cache anyway. + * + * @param pack + * the file to purge all entries of. + */ + private void removeAll(final PackFile pack) { + for (int s = 0; s < tableSize; s++) { + final Entry e1 = table.get(s); + boolean hasDead = false; + for (Entry e = e1; e != null; e = e.next) { + if (e.ref.pack == pack) { + e.kill(); + hasDead = true; + } else if (e.dead) + hasDead = true; + } + if (hasDead) + table.compareAndSet(s, e1, clean(e1)); + } + gc(); + } + + @SuppressWarnings("unchecked") + private void gc() { + Ref r; + while ((r = (Ref) queue.poll()) != null) { + // Sun's Java 5 and 6 implementation have a bug where a Reference + // can be enqueued and dequeued twice on the same reference queue + // due to a race condition within ReferenceQueue.enqueue(Reference). + // + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6837858 + // + // We CANNOT permit a Reference to come through us twice, as it will + // skew the resource counters we maintain. Our canClear() check here + // provides a way to skip the redundant dequeues, if any. + // + if (r.canClear()) { + clear(r); + + boolean found = false; + final int s = slot(r.pack, r.position); + final Entry e1 = table.get(s); + for (Entry n = e1; n != null; n = n.next) { + if (n.ref == r) { + n.dead = true; + found = true; + break; + } + } + if (found) + table.compareAndSet(s, e1, clean(e1)); + } + } + } + + private int slot(final PackFile pack, final long position) { + return (hash(pack.hash, position) >>> 1) % tableSize; + } + + private Lock lock(final PackFile pack, final long position) { + return locks[(hash(pack.hash, position) >>> 1) % locks.length]; + } + + private static Entry clean(Entry top) { + while (top != null && top.dead) { + top.ref.enqueue(); + top = top.next; + } + if (top == null) + return null; + final Entry n = clean(top.next); + return n == top.next ? top : new Entry(n, top.ref); + } + + private static class Entry { + /** Next entry in the hash table's chain list. */ + final Entry next; + + /** The referenced object. */ + final Ref ref; + + /** + * Marked true when ref.get() returns null and the ref is dead. + *

+ * A true here indicates that the ref is no longer accessible, and that + * we therefore need to eventually purge this Entry object out of the + * bucket's chain. + */ + volatile boolean dead; + + Entry(final Entry n, final Ref r) { + next = n; + ref = r; + } + + final void kill() { + dead = true; + ref.enqueue(); + } + } + + /** A soft reference wrapped around a cached object. */ + private static class Ref extends SoftReference { + final PackFile pack; + + final long position; + + final int size; + + long lastAccess; + + private boolean cleared; + + protected Ref(final PackFile pack, final long position, + final ByteWindow v, final ReferenceQueue queue) { + super(v, queue); + this.pack = pack; + this.position = position; + this.size = v.size(); + } + + final synchronized boolean canClear() { + if (cleared) + return false; + cleared = true; + return true; + } + } + + private static final class Lock { + // Used only for its implicit monitor. + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java new file mode 100644 index 0000000000..48d7018e42 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2009, Google Inc. + * 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 org.eclipse.jgit.lib.Config; + +/** Configuration parameters for {@link WindowCache}. */ +public class WindowCacheConfig { + /** 1024 (number of bytes in one kibibyte/kilobyte) */ + public static final int KB = 1024; + + /** 1024 {@link #KB} (number of bytes in one mebibyte/megabyte) */ + public static final int MB = 1024 * KB; + + private int packedGitOpenFiles; + + private long packedGitLimit; + + private int packedGitWindowSize; + + private boolean packedGitMMAP; + + private int deltaBaseCacheLimit; + + /** Create a default configuration. */ + public WindowCacheConfig() { + packedGitOpenFiles = 128; + packedGitLimit = 10 * MB; + packedGitWindowSize = 8 * KB; + packedGitMMAP = false; + deltaBaseCacheLimit = 10 * MB; + } + + /** + * @return maximum number of streams to open at a time. Open packs count + * against the process limits. Default is 128. + */ + public int getPackedGitOpenFiles() { + return packedGitOpenFiles; + } + + /** + * @param fdLimit + * maximum number of streams to open at a time. Open packs count + * against the process limits + */ + public void setPackedGitOpenFiles(final int fdLimit) { + packedGitOpenFiles = fdLimit; + } + + /** + * @return maximum number bytes of heap memory to dedicate to caching pack + * file data. Default is 10 MB. + */ + public long getPackedGitLimit() { + return packedGitLimit; + } + + /** + * @param newLimit + * maximum number bytes of heap memory to dedicate to caching + * pack file data. + */ + public void setPackedGitLimit(final long newLimit) { + packedGitLimit = newLimit; + } + + /** + * @return size in bytes of a single window mapped or read in from the pack + * file. Default is 8 KB. + */ + public int getPackedGitWindowSize() { + return packedGitWindowSize; + } + + /** + * @param newSize + * size in bytes of a single window read in from the pack file. + */ + public void setPackedGitWindowSize(final int newSize) { + packedGitWindowSize = newSize; + } + + /** + * @return true enables use of Java NIO virtual memory mapping for windows; + * false reads entire window into a byte[] with standard read calls. + * Default false. + */ + public boolean isPackedGitMMAP() { + return packedGitMMAP; + } + + /** + * @param usemmap + * true enables use of Java NIO virtual memory mapping for + * windows; false reads entire window into a byte[] with standard + * read calls. + */ + public void setPackedGitMMAP(final boolean usemmap) { + packedGitMMAP = usemmap; + } + + /** + * @return maximum number of bytes to cache in {@link UnpackedObjectCache} + * for inflated, recently accessed objects, without delta chains. + * Default 10 MB. + */ + public int getDeltaBaseCacheLimit() { + return deltaBaseCacheLimit; + } + + /** + * @param newLimit + * maximum number of bytes to cache in + * {@link UnpackedObjectCache} for inflated, recently accessed + * objects, without delta chains. + */ + public void setDeltaBaseCacheLimit(final int newLimit) { + deltaBaseCacheLimit = newLimit; + } + + /** + * Update properties by setting fields from the configuration. + *

+ * If a property is not defined in the configuration, then it is left + * unmodified. + * + * @param rc configuration to read properties from. + */ + public void fromConfig(final Config rc) { + setPackedGitOpenFiles(rc.getInt("core", null, "packedgitopenfiles", getPackedGitOpenFiles())); + setPackedGitLimit(rc.getLong("core", null, "packedgitlimit", getPackedGitLimit())); + setPackedGitWindowSize(rc.getInt("core", null, "packedgitwindowsize", getPackedGitWindowSize())); + setPackedGitMMAP(rc.getBoolean("core", null, "packedgitmmap", isPackedGitMMAP())); + setDeltaBaseCacheLimit(rc.getInt("core", null, "deltabasecachelimit", getDeltaBaseCacheLimit())); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java new file mode 100644 index 0000000000..a88261162d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.IOException; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.InflaterCache; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.ObjectReuseAsIs; +import org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.lib.PackOutputStream; +import org.eclipse.jgit.lib.PackWriter; +import org.eclipse.jgit.revwalk.RevObject; + +/** Active handle to a ByteWindow. */ +final class WindowCursor extends ObjectReader implements ObjectReuseAsIs { + /** Temporary buffer large enough for at least one raw object id. */ + final byte[] tempId = new byte[Constants.OBJECT_ID_LENGTH]; + + private Inflater inf; + + private ByteWindow window; + + private final FileObjectDatabase db; + + WindowCursor(FileObjectDatabase db) { + this.db = db; + } + + public boolean hasObject(AnyObjectId objectId) throws IOException { + return db.hasObject(objectId); + } + + public ObjectLoader openObject(AnyObjectId objectId, int typeHint) + throws MissingObjectException, IOException { + final ObjectLoader ldr = db.openObject(this, objectId); + if (ldr == null) { + if (typeHint == OBJ_ANY) + throw new MissingObjectException(objectId.copy(), "unknown"); + throw new MissingObjectException(objectId.copy(), typeHint); + } + return ldr; + } + + public LocalObjectToPack newObjectToPack(RevObject obj) { + return new LocalObjectToPack(obj); + } + + public void selectObjectRepresentation(PackWriter packer, ObjectToPack otp) + throws IOException, MissingObjectException { + db.selectObjectRepresentation(packer, otp, this); + } + + public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp) + throws IOException, StoredObjectRepresentationNotAvailableException { + LocalObjectToPack src = (LocalObjectToPack) otp; + src.copyFromPack.copyAsIs(out, src, this); + } + + /** + * Copy bytes from the window to a caller supplied buffer. + * + * @param pack + * the file the desired window is stored within. + * @param position + * position within the file to read from. + * @param dstbuf + * destination buffer to copy into. + * @param dstoff + * offset within dstbuf to start copying into. + * @param cnt + * number of bytes to copy. This value may exceed the number of + * bytes remaining in the window starting at offset + * pos. + * @return number of bytes actually copied; this may be less than + * cnt if cnt exceeded the number of bytes + * available. + * @throws IOException + * this cursor does not match the provider or id and the proper + * window could not be acquired through the provider's cache. + */ + int copy(final PackFile pack, long position, final byte[] dstbuf, + int dstoff, final int cnt) throws IOException { + final long length = pack.length; + int need = cnt; + while (need > 0 && position < length) { + pin(pack, position); + final int r = window.copy(position, dstbuf, dstoff, need); + position += r; + dstoff += r; + need -= r; + } + return cnt - need; + } + + /** + * Pump bytes into the supplied inflater as input. + * + * @param pack + * the file the desired window is stored within. + * @param position + * position within the file to read from. + * @param dstbuf + * destination buffer the inflater should output decompressed + * data to. + * @param dstoff + * current offset within dstbuf to inflate into. + * @return updated dstoff based on the number of bytes + * successfully inflated into dstbuf. + * @throws IOException + * this cursor does not match the provider or id and the proper + * window could not be acquired through the provider's cache. + * @throws DataFormatException + * the inflater encountered an invalid chunk of data. Data + * stream corruption is likely. + */ + int inflate(final PackFile pack, long position, final byte[] dstbuf, + int dstoff) throws IOException, DataFormatException { + prepareInflater(); + for (;;) { + pin(pack, position); + dstoff = window.inflate(position, dstbuf, dstoff, inf); + if (inf.finished()) + return dstoff; + position = window.end; + } + } + + ByteArrayWindow quickCopy(PackFile p, long pos, long cnt) + throws IOException { + pin(p, pos); + if (window instanceof ByteArrayWindow + && window.contains(p, pos + (cnt - 1))) + return (ByteArrayWindow) window; + return null; + } + + Inflater inflater() { + prepareInflater(); + return inf; + } + + private void prepareInflater() { + if (inf == null) + inf = InflaterCache.get(); + else + inf.reset(); + } + + private void pin(final PackFile pack, final long position) + throws IOException { + final ByteWindow w = window; + if (w == null || !w.contains(pack, position)) { + // If memory is low, we may need what is in our window field to + // be cleaned up by the GC during the get for the next window. + // So we always clear it, even though we are just going to set + // it again. + // + window = null; + window = WindowCache.get(pack, position); + } + } + + /** Release the current window cursor. */ + public void release() { + window = null; + try { + InflaterCache.release(inf); + } finally { + inf = null; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java index 2819ae26de..8c336c5255 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java @@ -61,7 +61,6 @@ 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.PackLock; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Config.SectionParser; @@ -73,6 +72,7 @@ import org.eclipse.jgit.revwalk.RevSort; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter; import org.eclipse.jgit.revwalk.filter.RevFilter; +import org.eclipse.jgit.storage.file.PackLock; import org.eclipse.jgit.transport.PacketLineIn.AckNackResult; import org.eclipse.jgit.util.TemporaryBuffer; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java index 3b97dfc0d6..98ecc5540f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java @@ -68,13 +68,13 @@ import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; -import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.PackLock; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java index 50c0866f23..9dc54da00b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java @@ -51,9 +51,9 @@ import java.util.Set; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.storage.file.PackLock; /** * Lists known refs from the remote and copies objects of selected refs. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java index 27505bef65..72d73eb59b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java @@ -63,14 +63,14 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.LockFile; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.LockFile; +import org.eclipse.jgit.storage.file.PackLock; class FetchProcess { /** Transport we will fetch over. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java index 0cd6733695..5a37825cea 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java @@ -75,11 +75,11 @@ import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdSubclassMap; import org.eclipse.jgit.lib.ObjectLoader; -import org.eclipse.jgit.lib.PackIndexWriter; -import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.storage.file.PackIndexWriter; +import org.eclipse.jgit.storage.file.PackLock; import org.eclipse.jgit.util.NB; /** Indexes Git pack files for local use. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java index 91105cc8a3..1597a35fe9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java @@ -73,7 +73,6 @@ 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.PackLock; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; @@ -86,6 +85,7 @@ import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.PackLock; import org.eclipse.jgit.transport.ReceiveCommand.Result; import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; import org.eclipse.jgit.util.io.InterruptTimer; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java index 71e7bf285e..0f4c1314a3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java @@ -85,10 +85,10 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.RefDirectory; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.SymbolicRef; import org.eclipse.jgit.lib.Config.SectionParser; +import org.eclipse.jgit.storage.file.RefDirectory; import org.eclipse.jgit.util.HttpSupport; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java index ae54a155a1..c9b18be1f8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java @@ -60,8 +60,8 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.io.MessageWriter; import org.eclipse.jgit.util.io.StreamCopyThread; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java index c9d2e9e193..89dd6b4904 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java @@ -68,15 +68,11 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectChecker; -import org.eclipse.jgit.lib.ObjectDirectory; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.PackIndex; -import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.UnpackedObjectLoader; import org.eclipse.jgit.revwalk.DateRevQueue; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; @@ -84,6 +80,10 @@ import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.ObjectDirectory; +import org.eclipse.jgit.storage.file.PackIndex; +import org.eclipse.jgit.storage.file.PackLock; +import org.eclipse.jgit.storage.file.UnpackedObjectLoader; import org.eclipse.jgit.treewalk.TreeWalk; /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java index f1743b378d..0e2adae503 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java @@ -62,7 +62,7 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.RefDirectory; +import org.eclipse.jgit.storage.file.RefDirectory; import org.eclipse.jgit.util.IO; /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java index 9d7feb08f2..f4382eb186 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java @@ -50,7 +50,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.TimeZone; -import org.eclipse.jgit.lib.FileBasedConfig; +import org.eclipse.jgit.storage.file.FileBasedConfig; /** * Interface to read values from the system. -- cgit v1.2.3