diff options
author | Shawn O. Pearce <spearce@spearce.org> | 2010-07-22 11:17:00 -0700 |
---|---|---|
committer | Shawn O. Pearce <spearce@spearce.org> | 2010-07-22 14:56:34 -0700 |
commit | fa9b225e06f16ca7fd7ffca5689f4af0942a73e1 (patch) | |
tree | d85054426a826f9b1495186d41252d0130f33407 | |
parent | ab062caa2255054cca413985bbaa3ad7c1c9c383 (diff) | |
parent | 12fe0f2d1eb18aab2964532e99d11d4311d558eb (diff) | |
download | jgit-fa9b225e06f16ca7fd7ffca5689f4af0942a73e1.tar.gz jgit-fa9b225e06f16ca7fd7ffca5689f4af0942a73e1.zip |
Merge branch 'delta'
* delta: (103 commits)
Discard the uncompressed delta as soon as its compressed
Honor pack.windowlimit to cap memory usage during packing
Honor pack.threads and perform delta search in parallel
Cache small deltas during packing
Implement delta generation during packing
debug-show-packdelta: Dump a pack delta to the console
Initial pack format delta generator
Add debugging toString() method to ObjectToPack
Make ObjectToPack clearReuseAsIs signal available to subclasses
Correctly classify the compressing objects phase
Refactor ObjectToPack's delta depth setting
Configure core.bigFileThreshold into PackWriter
Add doNotDelta flag to ObjectToPack
Add more configuration options to PackWriter
Save object path hash codes during packing
Add path hash code to ObjectWalk
Add getObjectSize to ObjectReader
Allow TemporaryBuffer.Heap to allocate smaller than 8 KiB
Define a constant for 127 in DeltaEncoder
Cap delta copy instructions at 64k
...
Conflicts:
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java
org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties
org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java
org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java
Change-Id: I7c7a05e443a48d32c836173a409ee7d340c70796
242 files changed, 13969 insertions, 5573 deletions
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/InfoRefsServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java index f667ce95a7..647919e065 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java @@ -74,30 +74,35 @@ class InfoRefsServlet extends HttpServlet { final Repository db = getRepository(req); final RevWalk walk = new RevWalk(db); - final RevFlag ADVERTISED = walk.newFlag("ADVERTISED"); + try { + final RevFlag ADVERTISED = walk.newFlag("ADVERTISED"); - final OutputStreamWriter out = new OutputStreamWriter( - new SmartOutputStream(req, rsp), Constants.CHARSET); - final RefAdvertiser adv = new RefAdvertiser() { - @Override - protected void writeOne(final CharSequence line) throws IOException { - // Whoever decided that info/refs should use a different - // delimiter than the native git:// protocol shouldn't - // be allowed to design this sort of stuff. :-( - out.append(line.toString().replace(' ', '\t')); - } + final OutputStreamWriter out = new OutputStreamWriter( + new SmartOutputStream(req, rsp), Constants.CHARSET); + final RefAdvertiser adv = new RefAdvertiser() { + @Override + protected void writeOne(final CharSequence line) + throws IOException { + // Whoever decided that info/refs should use a different + // delimiter than the native git:// protocol shouldn't + // be allowed to design this sort of stuff. :-( + out.append(line.toString().replace(' ', '\t')); + } - @Override - protected void end() { - // No end marker required for info/refs format. - } - }; - adv.init(walk, ADVERTISED); - adv.setDerefTags(true); + @Override + protected void end() { + // No end marker required for info/refs format. + } + }; + adv.init(walk, ADVERTISED); + adv.setDerefTags(true); - Map<String, Ref> refs = db.getAllRefs(); - refs.remove(Constants.HEAD); - adv.send(refs); - out.close(); + Map<String, Ref> refs = db.getAllRefs(); + refs.remove(Constants.HEAD); + adv.send(refs); + out.close(); + } finally { + walk.release(); + } } } 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.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java index 49fd535a71..4bc05c1886 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java @@ -83,7 +83,12 @@ class ReceivePackServlet extends HttpServlet { protected void advertise(HttpServletRequest req, Repository db, PacketLineOutRefAdvertiser pck) throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException { - receivePackFactory.create(req, db).sendAdvertisedRefs(pck); + ReceivePack rp = receivePackFactory.create(req, db); + try { + rp.sendAdvertisedRefs(pck); + } finally { + rp.getRevWalk().release(); + } } } diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java index 5bf5546cf7..650059bd38 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java @@ -80,6 +80,8 @@ class TextFileServlet extends HttpServlet { private byte[] read(final HttpServletRequest req) throws IOException { final File gitdir = getRepository(req).getDirectory(); + if (gitdir == null) + throw new FileNotFoundException(fileName); return IO.readFully(new File(gitdir, fileName)); } } diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java index 92d41a0caf..602d66a90c 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java @@ -83,7 +83,12 @@ class UploadPackServlet extends HttpServlet { protected void advertise(HttpServletRequest req, Repository db, PacketLineOutRefAdvertiser pck) throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException { - uploadPackFactory.create(req, db).sendAdvertisedRefs(pck); + UploadPack up = uploadPackFactory.create(req, db); + try { + up.sendAdvertisedRefs(pck); + } finally { + up.getRevWalk().release(); + } } } @@ -107,7 +112,12 @@ class UploadPackServlet extends HttpServlet { up.setBiDirectionalPipe(false); rsp.setContentType(RSP_TYPE); - final SmartOutputStream out = new SmartOutputStream(req, rsp); + final SmartOutputStream out = new SmartOutputStream(req, rsp) { + @Override + public void flush() throws IOException { + doFlush(); + } + }; up.upload(getInputStream(req), out, null); out.close(); diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/FileResolver.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/FileResolver.java index cc062dbe88..296725b678 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/FileResolver.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/FileResolver.java @@ -138,8 +138,10 @@ public class FileResolver implements RepositoryResolver { Repository db) throws IOException { if (isExportAll()) return true; - else + else if (db.getDirectory() != null) return new File(db.getDirectory(), "git-daemon-export-ok").exists(); + else + return false; } private static boolean isUnreasonableName(final String name) { 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 47d7806a1e..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 @@ -62,23 +62,24 @@ import org.eclipse.jgit.lib.Constants; 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.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; 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<FileRepository> src = createTestRepository(); final String srcName = src.getRepository().getDirectory().getName(); ServletContextHandler app = server.addContext("/git"); @@ -114,7 +115,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..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 @@ -64,9 +64,10 @@ import org.eclipse.jgit.lib.Constants; 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.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; @@ -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<FileRepository> src = createTestRepository(); final String srcName = src.getRepository().getDirectory().getName(); ServletContextHandler app = server.addContext("/git"); @@ -124,7 +125,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/HttpClientTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java index 729466df3b..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 @@ -66,12 +66,13 @@ 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; public class HttpClientTests extends HttpTestCase { - private TestRepository remoteRepository; + private TestRepository<FileRepository> 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 f7b3bdb203..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 @@ -79,11 +79,12 @@ import org.eclipse.jgit.lib.Constants; 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.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; @@ -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<FileRepository> 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<FileRepository> 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; @@ -547,7 +548,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(); } 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..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 @@ -61,6 +61,7 @@ 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; @@ -82,8 +83,9 @@ public abstract class HttpTestCase extends LocalDiskRepositoryTestCase { super.tearDown(); } - protected TestRepository createTestRepository() throws Exception { - return new TestRepository(createBareRepository()); + protected TestRepository<FileRepository> createTestRepository() + throws IOException { + return new TestRepository<FileRepository>(createBareRepository()); } protected URIish toURIish(String path) throws URISyntaxException { 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/IpLogGenerator.java b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/IpLogGenerator.java index f64c329847..433d4338df 100644 --- a/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/IpLogGenerator.java +++ b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/IpLogGenerator.java @@ -78,15 +78,13 @@ import org.eclipse.jgit.diff.EditList; import org.eclipse.jgit.diff.MyersDiff; import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.iplog.Committer.ActiveRange; import org.eclipse.jgit.lib.BlobBasedConfig; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.MutableObjectId; -import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.WindowCursor; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; @@ -144,7 +142,7 @@ public class IpLogGenerator { private NameConflictTreeWalk tw; - private final WindowCursor curs = new WindowCursor(); + private ObjectReader curs; private final MutableObjectId idbuf = new MutableObjectId(); @@ -184,8 +182,9 @@ public class IpLogGenerator { throws IOException, ConfigInvalidException { try { db = repo; - rw = new RevWalk(db); - tw = new NameConflictTreeWalk(db); + curs = db.newObjectReader(); + rw = new RevWalk(curs); + tw = new NameConflictTreeWalk(curs); RevCommit c = rw.parseCommit(startCommit); @@ -194,7 +193,7 @@ public class IpLogGenerator { scanProjectCommits(meta.getProjects().get(0), c); commits.add(c); } finally { - WindowCursor.release(curs); + curs.release(); db = null; rw = null; tw = null; @@ -417,10 +416,7 @@ public class IpLogGenerator { private byte[] openBlob(int side) throws IOException { tw.getObjectId(idbuf, side); - ObjectLoader ldr = db.openObject(curs, idbuf); - if (ldr == null) - throw new MissingObjectException(idbuf.copy(), Constants.OBJ_BLOB); - return ldr.getCachedBytes(); + return curs.open(idbuf, Constants.OBJ_BLOB).getCachedBytes(); } /** 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..7f4dcfddd6 100644 --- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF @@ -18,6 +18,8 @@ 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.storage.pack;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 001deb262b..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,12 +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.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; @@ -259,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 */); } @@ -270,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 */); } @@ -284,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 Repository(gitdir); + FileRepository db = new FileRepository(gitdir); assertFalse(gitdir.exists()); db.create(); @@ -323,7 +324,7 @@ public abstract class LocalDiskRepositoryTestCase extends TestCase { putPersonIdent(env, "AUTHOR", author); putPersonIdent(env, "COMMITTER", committer); - final File cwd = db.getWorkDir(); + final File cwd = db.getWorkTree(); final Process p = Runtime.getRuntime().exec(argv, toEnvArray(env), cwd); p.getOutputStream().close(); p.getErrorStream().close(); 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 59504aa780..3c58271257 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,22 +73,16 @@ 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.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; -import org.eclipse.jgit.lib.PackFile; -import org.eclipse.jgit.lib.PackWriter; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.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,11 +90,22 @@ 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.storage.pack.PackWriter; 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 <R> + * type of Repository the test data is stored on. + */ +public class TestRepository<R extends Repository> { private static final PersonIdent author; private static final PersonIdent committer; @@ -119,11 +124,11 @@ public class TestRepository { committer = new PersonIdent(cn, ce, now, tz); } - private final Repository db; + private final R db; private final RevWalk pool; - private final ObjectWriter writer; + private final ObjectInserter inserter; private long now; @@ -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,17 +150,17 @@ 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); + this.inserter = db.newObjectInserter(); this.now = 1236977987000L; } /** @return the repository this helper class operates against. */ - public Repository getRepository() { + public R getRepository() { return db; } @@ -200,7 +205,14 @@ public class TestRepository { * @throws Exception */ public RevBlob blob(final byte[] content) throws Exception { - return pool.lookupBlob(writer.writeBlob(content)); + ObjectId id; + try { + id = inserter.insert(Constants.OBJ_BLOB, content); + inserter.flush(); + } finally { + inserter.release(); + } + return pool.lookupBlob(id); } /** @@ -236,7 +248,14 @@ public class TestRepository { for (final DirCacheEntry e : entries) b.add(e); b.finish(); - return pool.lookupTree(dc.writeTree(writer)); + ObjectId root; + try { + root = dc.writeTree(inserter); + inserter.flush(); + } finally { + inserter.release(); + } + return pool.lookupTree(root); } /** @@ -253,7 +272,7 @@ public class TestRepository { */ public RevObject get(final RevTree tree, final String path) throws AssertionFailedError, Exception { - final TreeWalk tw = new TreeWalk(db); + final TreeWalk tw = new TreeWalk(pool.getObjectReader()); tw.setFilter(PathFilterGroup.createFromStrings(Collections .singleton(path))); tw.reset(tree); @@ -346,7 +365,14 @@ public class TestRepository { c.setAuthor(new PersonIdent(author, new Date(now))); c.setCommitter(new PersonIdent(committer, new Date(now))); c.setMessage(""); - return pool.lookupCommit(writer.writeCommit(c)); + ObjectId id; + try { + id = inserter.insert(Constants.OBJ_COMMIT, inserter.format(c)); + inserter.flush(); + } finally { + inserter.release(); + } + return pool.lookupCommit(id); } /** @return a new commit builder. */ @@ -377,7 +403,14 @@ public class TestRepository { t.setTag(name); t.setTagger(new PersonIdent(committer, new Date(now))); t.setMessage(""); - return (RevTag) pool.lookupAny(writer.writeTag(t), Constants.OBJ_TAG); + ObjectId id; + try { + id = inserter.insert(Constants.OBJ_TAG, inserter.format(t)); + inserter.flush(); + } finally { + inserter.release(); + } + return (RevTag) pool.lookupAny(id, Constants.OBJ_TAG); } /** @@ -443,25 +476,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())); } } @@ -528,7 +563,7 @@ public class TestRepository { if (o == null) break; - final byte[] bin = db.openObject(o).getCachedBytes(); + final byte[] bin = db.open(o, o.getType()).getCachedBytes(); oc.checkCommit(bin); assertHash(o, bin); } @@ -538,7 +573,7 @@ public class TestRepository { if (o == null) break; - final byte[] bin = db.openObject(o).getCachedBytes(); + final byte[] bin = db.open(o, o.getType()).getCachedBytes(); oc.check(o.getType(), bin); assertHash(o, bin); } @@ -563,38 +598,46 @@ 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<ObjectId> all = new HashSet<ObjectId>(); - for (Ref r : db.getAllRefs().values()) - all.add(r.getObjectId()); - pw.preparePack(all, Collections.<ObjectId> 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(); + if (db.getObjectDatabase() instanceof ObjectDirectory) { + ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase(); + NullProgressMonitor m = NullProgressMonitor.INSTANCE; + + final File pack, idx; + PackWriter pw = new PackWriter(db); + try { + Set<ObjectId> all = new HashSet<ObjectId>(); + for (Ref r : db.getAllRefs().values()) + all.add(r.getObjectId()); + pw.preparePack(m, all, Collections.<ObjectId> emptySet()); + + final ObjectId name = pw.computeName(); + OutputStream out; + + pack = nameFor(odb, name, ".pack"); + out = new BufferedOutputStream(new FileOutputStream(pack)); + try { + pw.writePack(m, m, out); + } finally { + out.close(); + } + pack.setReadOnly(); + + idx = nameFor(odb, name, ".idx"); + out = new BufferedOutputStream(new FileOutputStream(idx)); + try { + pw.writeIndex(out); + } finally { + out.close(); + } + idx.setReadOnly(); + } finally { + pw.release(); + } - 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 +652,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); @@ -711,7 +753,8 @@ public class TestRepository { if (parents.isEmpty()) { DirCacheBuilder b = tree.builder(); parseBody(p); - b.addTree(new byte[0], DirCacheEntry.STAGE_0, db, p.getTree()); + b.addTree(new byte[0], DirCacheEntry.STAGE_0, pool + .getObjectReader(), p.getTree()); b.finish(); } parents.add(p); @@ -769,13 +812,21 @@ public class TestRepository { TestRepository.this.tick(tick); final Commit c = new Commit(db); - c.setTreeId(pool.lookupTree(tree.writeTree(writer))); c.setParentIds(parents.toArray(new RevCommit[parents.size()])); c.setAuthor(new PersonIdent(author, new Date(now))); c.setCommitter(new PersonIdent(committer, new Date(now))); c.setMessage(message); - self = pool.lookupCommit(writer.writeCommit(c)); + ObjectId commitId; + try { + c.setTreeId(tree.writeTree(inserter)); + commitId = inserter.insert(Constants.OBJ_COMMIT, inserter + .format(c)); + inserter.flush(); + } finally { + inserter.release(); + } + self = pool.lookupCommit(commitId); if (branch != null) branch.update(self); diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF index 8628896780..d6eac232e1 100644 --- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF @@ -17,6 +17,8 @@ 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.storage.pack;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/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin index 0d4a140e62..075cadef79 100644 --- a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin +++ b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin @@ -30,6 +30,7 @@ org.eclipse.jgit.pgm.debug.RebuildCommitGraph org.eclipse.jgit.pgm.debug.ShowCacheTree org.eclipse.jgit.pgm.debug.ShowCommands org.eclipse.jgit.pgm.debug.ShowDirCache +org.eclipse.jgit.pgm.debug.ShowPackDelta org.eclipse.jgit.pgm.debug.WriteDirCache org.eclipse.jgit.pgm.eclipse.Iplog 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 b0f51ec58a..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 @@ -61,10 +61,10 @@ import org.eclipse.jgit.lib.GitIndex; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefComparator; import org.eclipse.jgit.lib.RefUpdate; -import org.eclipse.jgit.lib.Repository; 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; @@ -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 Repository(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, @@ -180,7 +183,7 @@ class Clone extends AbstractFetchCommand { final Tree tree = commit.getTree(); final WorkDirCheckout co; - co = new WorkDirCheckout(db, db.getWorkDir(), index, tree); + co = new WorkDirCheckout(db, db.getWorkTree(), index, tree); co.checkout(); index.write(); } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java index 3dfd8ff62d..ae11f67317 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java @@ -125,10 +125,12 @@ class Glog extends RevWalkTextBuiltin { } private String repoName() { - final File f = db.getDirectory(); - String n = f.getName(); + final File gitDir = db.getDirectory(); + if (gitDir == null) + return db.toString(); + String n = gitDir.getName(); if (Constants.DOT_GIT.equals(n)) - n = f.getParentFile().getName(); + n = gitDir.getParentFile().getName(); return n; } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/IndexPack.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/IndexPack.java index 35fd2a5971..640c8ef348 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/IndexPack.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/IndexPack.java @@ -49,6 +49,7 @@ import java.io.File; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; +import org.eclipse.jgit.lib.CoreConfig; import org.eclipse.jgit.lib.TextProgressMonitor; class IndexPack extends TextBuiltin { @@ -64,7 +65,8 @@ class IndexPack extends TextBuiltin { @Override protected void run() throws Exception { if (indexVersion == -1) - indexVersion = db.getConfig().getCore().getPackIndexVersion(); + indexVersion = db.getConfig().get(CoreConfig.KEY) + .getPackIndexVersion(); final BufferedInputStream in; final org.eclipse.jgit.transport.IndexPack ip; in = new BufferedInputStream(System.in); 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 d8c7bdfb4a..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.Repository; +import org.eclipse.jgit.storage.file.FileRepository; @Command(common = true, usage = "usage_CreateAnEmptyGitRepository") class Init extends TextBuiltin { @@ -66,7 +66,7 @@ class Init extends TextBuiltin { protected void run() throws Exception { if (gitdir == null) gitdir = new File(bare ? "." : Constants.DOT_GIT); - db = new Repository(gitdir); + db = new FileRepository(gitdir); db.create(bare); out.println(MessageFormat.format(CLIText.get().initializedEmptyGitRepositoryIn, gitdir.getAbsolutePath())); } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java index 306ac816d8..ab11062cc2 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java @@ -51,20 +51,15 @@ import java.net.MalformedURLException; import java.net.URL; import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; import java.util.List; -import java.util.Set; import org.eclipse.jgit.awtui.AwtAuthenticator; import org.eclipse.jgit.awtui.AwtSshSessionFactory; import org.eclipse.jgit.errors.TransportException; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryBuilder; import org.eclipse.jgit.pgm.opt.CmdLineParser; import org.eclipse.jgit.pgm.opt.SubcommandHandler; import org.eclipse.jgit.util.CachedAuthenticator; -import org.eclipse.jgit.util.SystemReader; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.ExampleMode; @@ -168,51 +163,17 @@ public class Main { final TextBuiltin cmd = subcommand; if (cmd.requiresRepository()) { - if (gitdir == null) { - String gitDirEnv = SystemReader.getInstance().getenv(Constants.GIT_DIR_KEY); - if (gitDirEnv != null) - gitdir = new File(gitDirEnv); - } - if (gitdir == null) - gitdir = findGitDir(); - - File gitworktree; - String gitWorkTreeEnv = SystemReader.getInstance().getenv(Constants.GIT_WORK_TREE_KEY); - if (gitWorkTreeEnv != null) - gitworktree = new File(gitWorkTreeEnv); - else - gitworktree = null; - - File indexfile; - String indexFileEnv = SystemReader.getInstance().getenv(Constants.GIT_INDEX_KEY); - if (indexFileEnv != null) - indexfile = new File(indexFileEnv); - else - indexfile = null; - - File objectdir; - String objectDirEnv = SystemReader.getInstance().getenv(Constants.GIT_OBJECT_DIRECTORY_KEY); - if (objectDirEnv != null) - objectdir = new File(objectDirEnv); - else - objectdir = null; - - File[] altobjectdirs; - String altObjectDirEnv = SystemReader.getInstance().getenv(Constants.GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY); - if (altObjectDirEnv != null) { - String[] parserdAltObjectDirEnv = altObjectDirEnv.split(File.pathSeparator); - altobjectdirs = new File[parserdAltObjectDirEnv.length]; - for (int i = 0; i < parserdAltObjectDirEnv.length; i++) - altobjectdirs[i] = new File(parserdAltObjectDirEnv[i]); - } else - altobjectdirs = null; - - if (gitdir == null || !gitdir.isDirectory()) { + RepositoryBuilder rb = new RepositoryBuilder() // + .setGitDir(gitdir) // + .readEnvironment() // + .findGitDir(); + if (rb.getGitDir() == null) { writer.println(CLIText.get().cantFindGitDirectory); writer.flush(); System.exit(1); } - cmd.init(new Repository(gitdir, gitworktree, objectdir, altobjectdirs, indexfile), gitdir); + + cmd.init(rb.build(), null); } else { cmd.init(null, gitdir); } @@ -224,27 +185,6 @@ public class Main { } } - private static File findGitDir() { - Set<String> ceilingDirectories = new HashSet<String>(); - String ceilingDirectoriesVar = SystemReader.getInstance().getenv( - Constants.GIT_CEILING_DIRECTORIES_KEY); - if (ceilingDirectoriesVar != null) { - ceilingDirectories.addAll(Arrays.asList(ceilingDirectoriesVar - .split(File.pathSeparator))); - } - File current = new File("").getAbsoluteFile(); - while (current != null) { - final File gitDir = new File(current, Constants.DOT_GIT); - if (gitDir.isDirectory()) - return gitDir; - current = current.getParentFile(); - if (current != null - && ceilingDirectories.contains(current.getPath())) - break; - } - return null; - } - private static boolean installConsole() { try { install("org.eclipse.jgit.console.ConsoleAuthenticator"); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ReceivePack.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ReceivePack.java index 09a9f2b580..7a27617220 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ReceivePack.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ReceivePack.java @@ -47,9 +47,10 @@ package org.eclipse.jgit.pgm; import java.io.File; import java.text.MessageFormat; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.lib.RepositoryCache.FileKey; +import org.eclipse.jgit.util.FS; import org.kohsuke.args4j.Argument; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.Repository; @Command(common = false, usage = "usage_ServerSideBackendForJgitPush") class ReceivePack extends TextBuiltin { @@ -65,11 +66,14 @@ class ReceivePack extends TextBuiltin { protected void run() throws Exception { final org.eclipse.jgit.transport.ReceivePack rp; - if (new File(dstGitdir, Constants.DOT_GIT).isDirectory()) - dstGitdir = new File(dstGitdir, Constants.DOT_GIT); - db = new Repository(dstGitdir); - if (!db.getObjectsDirectory().isDirectory()) - throw die(MessageFormat.format(CLIText.get().notAGitRepository, dstGitdir.getPath())); + try { + FileKey key = FileKey.lenient(dstGitdir, FS.DETECTED); + db = key.open(true /* must exist */); + } catch (RepositoryNotFoundException notFound) { + throw die(MessageFormat.format(CLIText.get().notAGitRepository, + dstGitdir.getPath())); + } + rp = new org.eclipse.jgit.transport.ReceivePack(db); rp.receive(System.in, System.out, System.err); } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java index 1b8711dc9d..9f577ff05e 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java @@ -67,9 +67,9 @@ class Rm extends TextBuiltin { @Override protected void run() throws Exception { - root = db.getWorkDir(); + root = db.getWorkTree(); - final DirCache dirc = DirCache.lock(db); + final DirCache dirc = db.lockDirCache(); final DirCacheBuilder edit = dirc.builder(); final TreeWalk walk = new TreeWalk(db); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java index 63d26eacae..c798950a2e 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java @@ -49,13 +49,12 @@ package org.eclipse.jgit.pgm; import java.text.MessageFormat; -import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.Option; -import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.PersonIdent; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; @Command(common = true, usage = "usage_CreateATag") class Tag extends TextBuiltin { @@ -86,9 +85,7 @@ class Tag extends TextBuiltin { , tagName.substring(Constants.R_TAGS.length()))); } - final ObjectLoader ldr = db.openObject(object); - if (ldr == null) - throw new MissingObjectException(object, "any"); + final ObjectLoader ldr = db.open(object); org.eclipse.jgit.lib.Tag tag = new org.eclipse.jgit.lib.Tag(db); tag.setObjId(object); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/UploadPack.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/UploadPack.java index 52d2488f70..d4e2bcec76 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/UploadPack.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/UploadPack.java @@ -47,10 +47,11 @@ package org.eclipse.jgit.pgm; import java.io.File; import java.text.MessageFormat; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.lib.RepositoryCache.FileKey; +import org.eclipse.jgit.util.FS; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.Repository; @Command(common = false, usage = "usage_ServerSideBackendForJgitFetch") class UploadPack extends TextBuiltin { @@ -67,16 +68,19 @@ class UploadPack extends TextBuiltin { @Override protected void run() throws Exception { - final org.eclipse.jgit.transport.UploadPack rp; + final org.eclipse.jgit.transport.UploadPack up; - if (new File(srcGitdir, Constants.DOT_GIT).isDirectory()) - srcGitdir = new File(srcGitdir, Constants.DOT_GIT); - db = new Repository(srcGitdir); - if (!db.getObjectsDirectory().isDirectory()) - throw die(MessageFormat.format(CLIText.get().notAGitRepository, srcGitdir.getPath())); - rp = new org.eclipse.jgit.transport.UploadPack(db); + try { + FileKey key = FileKey.lenient(srcGitdir, FS.DETECTED); + db = key.open(true /* must exist */); + } catch (RepositoryNotFoundException notFound) { + throw die(MessageFormat.format(CLIText.get().notAGitRepository, + srcGitdir.getPath())); + } + + up = new org.eclipse.jgit.transport.UploadPack(db); if (0 <= timeout) - rp.setTimeout(timeout); - rp.upload(System.in, System.out, System.err); + up.setTimeout(timeout); + up.upload(System.in, System.out, System.err); } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/MakeCacheTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/MakeCacheTree.java index d772ffe23f..709b45a17b 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/MakeCacheTree.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/MakeCacheTree.java @@ -54,7 +54,7 @@ import org.eclipse.jgit.pgm.TextBuiltin; class MakeCacheTree extends TextBuiltin { @Override protected void run() throws Exception { - final DirCache cache = DirCache.read(db); + final DirCache cache = db.readDirCache(); final DirCacheTree tree = cache.getCacheTree(true); show(tree); } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadDirCache.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadDirCache.java index 2a1079b313..0ca050880e 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadDirCache.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadDirCache.java @@ -46,7 +46,6 @@ package org.eclipse.jgit.pgm.debug; import java.text.MessageFormat; -import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.pgm.CLIText; import org.eclipse.jgit.pgm.TextBuiltin; @@ -56,7 +55,7 @@ class ReadDirCache extends TextBuiltin { final int cnt = 100; final long start = System.currentTimeMillis(); for (int i = 0; i < cnt; i++) - DirCache.read(db); + db.readDirCache(); final long end = System.currentTimeMillis(); out.print(" "); out.println(MessageFormat.format(CLIText.get().averageMSPerRead, (end - start) / cnt)); 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..5b75c1b5c5 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. @@ -297,6 +297,7 @@ class RebuildCommitGraph extends TextBuiltin { name, id)); } } finally { + rw.release(); br.close(); } return refs; diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCacheTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCacheTree.java index 09796edb30..c49aefbf2f 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCacheTree.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCacheTree.java @@ -54,7 +54,7 @@ import org.eclipse.jgit.pgm.TextBuiltin; class ShowCacheTree extends TextBuiltin { @Override protected void run() throws Exception { - final DirCache cache = DirCache.read(db); + final DirCache cache = db.readDirCache(); final DirCacheTree tree = cache.getCacheTree(false); if (tree == null) throw die(CLIText.get().noTREESectionInIndex); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java index 854596c97b..a94d37ff87 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java @@ -59,7 +59,7 @@ class ShowDirCache extends TextBuiltin { final SimpleDateFormat fmt; fmt = new SimpleDateFormat("yyyyMMdd,HHmmss.SSS"); - final DirCache cache = DirCache.read(db); + final DirCache cache = db.readDirCache(); for (int i = 0; i < cache.getEntryCount(); i++) { final DirCacheEntry ent = cache.getEntry(i); final FileMode mode = FileMode.fromBits(ent.getRawMode()); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java new file mode 100644 index 0000000000..1718ef30f2 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java @@ -0,0 +1,127 @@ +/* + * 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.pgm.debug; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.InflaterInputStream; + +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.pgm.TextBuiltin; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.pack.BinaryDelta; +import org.eclipse.jgit.storage.pack.ObjectReuseAsIs; +import org.eclipse.jgit.storage.pack.ObjectToPack; +import org.eclipse.jgit.storage.pack.PackOutputStream; +import org.eclipse.jgit.storage.pack.PackWriter; +import org.eclipse.jgit.storage.pack.StoredObjectRepresentation; +import org.eclipse.jgit.util.TemporaryBuffer; +import org.kohsuke.args4j.Argument; + +class ShowPackDelta extends TextBuiltin { + @Argument(index = 0) + private ObjectId objectId; + + @Override + protected void run() throws Exception { + ObjectReader reader = db.newObjectReader(); + RevObject obj = new RevWalk(reader).parseAny(objectId); + byte[] delta = getDelta(reader, obj); + + // We're crossing our fingers that this will be a delta. Double + // check the size field in the header, it should match. + // + long size = reader.getObjectSize(obj, obj.getType()); + try { + if (BinaryDelta.getResultSize(delta) != size) + throw die("Object " + obj.name() + " is not a delta"); + } catch (ArrayIndexOutOfBoundsException bad) { + throw die("Object " + obj.name() + " is not a delta"); + } + + out.println(BinaryDelta.format(delta)); + } + + private byte[] getDelta(ObjectReader reader, RevObject obj) + throws IOException, MissingObjectException, + StoredObjectRepresentationNotAvailableException { + ObjectReuseAsIs asis = (ObjectReuseAsIs) reader; + ObjectToPack target = asis.newObjectToPack(obj); + + PackWriter pw = new PackWriter(reader) { + @Override + public void select(ObjectToPack otp, StoredObjectRepresentation next) { + otp.select(next); + } + }; + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + asis.selectObjectRepresentation(pw, target); + asis.copyObjectAsIs(new PackOutputStream(NullProgressMonitor.INSTANCE, + buf, pw), target); + + // At this point the object header has no delta information, + // because it was output as though it were a whole object. + // Skip over the header and inflate. + // + byte[] bufArray = buf.toByteArray(); + int ptr = 0; + while ((bufArray[ptr] & 0x80) != 0) + ptr++; + ptr++; + + TemporaryBuffer.Heap raw = new TemporaryBuffer.Heap(bufArray.length); + InflaterInputStream inf = new InflaterInputStream( + new ByteArrayInputStream(bufArray, ptr, bufArray.length)); + raw.copy(inf); + inf.close(); + return raw.toByteArray(); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteDirCache.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteDirCache.java index cee5966a03..142dbeecc8 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteDirCache.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteDirCache.java @@ -51,7 +51,7 @@ import org.eclipse.jgit.pgm.TextBuiltin; class WriteDirCache extends TextBuiltin { @Override protected void run() throws Exception { - final DirCache cache = DirCache.read(db); + final DirCache cache = db.readDirCache(); if (!cache.lock()) throw die(CLIText.get().failedToLockIndex); cache.read(); 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.pgm/src/org/eclipse/jgit/pgm/eclipse/Ipzilla.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/eclipse/Ipzilla.java index 4f0e338e8d..b563f07910 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/eclipse/Ipzilla.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/eclipse/Ipzilla.java @@ -91,7 +91,7 @@ class Ipzilla extends TextBuiltin { } if (output == null) - output = new File(db.getWorkDir(), IpLogMeta.IPLOG_CONFIG_FILE); + output = new File(db.getWorkTree(), IpLogMeta.IPLOG_CONFIG_FILE); IpLogMeta meta = new IpLogMeta(); meta.syncCQs(output, ipzilla, username, password); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java index 2043ac2090..01981600dd 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java @@ -59,7 +59,7 @@ import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.WindowCursor; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.pgm.CLIText; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.CanonicalTreeParser; @@ -121,9 +121,9 @@ public class AbstractTreeIteratorHandler extends throw new CmdLineException(MessageFormat.format(CLIText.get().notATree, name)); final CanonicalTreeParser p = new CanonicalTreeParser(); - final WindowCursor curs = new WindowCursor(); + final ObjectReader curs = clp.getRepository().newObjectReader(); try { - p.reset(clp.getRepository(), clp.getRevWalk().parseTree(id), curs); + p.reset(curs, clp.getRevWalk().parseTree(id)); } catch (MissingObjectException e) { throw new CmdLineException(MessageFormat.format(CLIText.get().notATree, name)); } catch (IncorrectObjectTypeException e) { diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index 3aaa8a45e8..d6128afc97 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -29,6 +29,8 @@ 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.storage.pack;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/exttst/org/eclipse/jgit/lib/T0007_GitIndexTest.java b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/T0007_GitIndexTest.java index dd3b51efc7..c5591b9bfd 100644 --- a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/T0007_GitIndexTest.java +++ b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/T0007_GitIndexTest.java @@ -116,7 +116,7 @@ public class T0007_GitIndexTest extends LocalDiskRepositoryTestCase { protected void setUp() throws Exception { super.setUp(); db = createWorkRepository(); - trash = db.getWorkDir(); + trash = db.getWorkTree(); } public void testCreateEmptyIndex() throws Exception { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java index ab011807cb..9e195b4974 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java @@ -78,7 +78,7 @@ public class AddCommandTest extends RepositoryTestCase { } public void testAddExistingSingleFile() throws IOException, NoFilepatternException { - File file = new File(db.getWorkDir(), "a.txt"); + File file = new File(db.getWorkTree(), "a.txt"); file.createNewFile(); PrintWriter writer = new PrintWriter(file); writer.print("content"); @@ -98,8 +98,8 @@ public class AddCommandTest extends RepositoryTestCase { } public void testAddExistingSingleFileInSubDir() throws IOException, NoFilepatternException { - new File(db.getWorkDir(), "sub").mkdir(); - File file = new File(db.getWorkDir(), "sub/a.txt"); + new File(db.getWorkTree(), "sub").mkdir(); + File file = new File(db.getWorkTree(), "sub/a.txt"); file.createNewFile(); PrintWriter writer = new PrintWriter(file); writer.print("content"); @@ -119,7 +119,7 @@ public class AddCommandTest extends RepositoryTestCase { } public void testAddExistingSingleFileTwice() throws IOException, NoFilepatternException { - File file = new File(db.getWorkDir(), "a.txt"); + File file = new File(db.getWorkTree(), "a.txt"); file.createNewFile(); PrintWriter writer = new PrintWriter(file); writer.print("content"); @@ -143,7 +143,7 @@ public class AddCommandTest extends RepositoryTestCase { } public void testAddExistingSingleFileTwiceWithCommit() throws Exception { - File file = new File(db.getWorkDir(), "a.txt"); + File file = new File(db.getWorkTree(), "a.txt"); file.createNewFile(); PrintWriter writer = new PrintWriter(file); writer.print("content"); @@ -169,7 +169,7 @@ public class AddCommandTest extends RepositoryTestCase { } public void testAddRemovedFile() throws Exception { - File file = new File(db.getWorkDir(), "a.txt"); + File file = new File(db.getWorkTree(), "a.txt"); file.createNewFile(); PrintWriter writer = new PrintWriter(file); writer.print("content"); @@ -191,7 +191,7 @@ public class AddCommandTest extends RepositoryTestCase { } public void testAddRemovedCommittedFile() throws Exception { - File file = new File(db.getWorkDir(), "a.txt"); + File file = new File(db.getWorkTree(), "a.txt"); file.createNewFile(); PrintWriter writer = new PrintWriter(file); writer.print("content"); @@ -217,20 +217,20 @@ public class AddCommandTest extends RepositoryTestCase { public void testAddWithConflicts() throws Exception { // prepare conflict - File file = new File(db.getWorkDir(), "a.txt"); + File file = new File(db.getWorkTree(), "a.txt"); file.createNewFile(); PrintWriter writer = new PrintWriter(file); writer.print("content"); writer.close(); - File file2 = new File(db.getWorkDir(), "b.txt"); + File file2 = new File(db.getWorkTree(), "b.txt"); file2.createNewFile(); writer = new PrintWriter(file2); writer.print("content b"); writer.close(); ObjectWriter ow = new ObjectWriter(db); - DirCache dc = DirCache.lock(db); + DirCache dc = db.lockDirCache(); DirCacheBuilder builder = dc.builder(); addEntryToBuilder("b.txt", file2, ow, builder, 0); @@ -264,13 +264,13 @@ public class AddCommandTest extends RepositoryTestCase { } public void testAddTwoFiles() throws Exception { - File file = new File(db.getWorkDir(), "a.txt"); + File file = new File(db.getWorkTree(), "a.txt"); file.createNewFile(); PrintWriter writer = new PrintWriter(file); writer.print("content"); writer.close(); - File file2 = new File(db.getWorkDir(), "b.txt"); + File file2 = new File(db.getWorkTree(), "b.txt"); file2.createNewFile(); writer = new PrintWriter(file2); writer.print("content b"); @@ -287,14 +287,14 @@ public class AddCommandTest extends RepositoryTestCase { } public void testAddFolder() throws Exception { - new File(db.getWorkDir(), "sub").mkdir(); - File file = new File(db.getWorkDir(), "sub/a.txt"); + new File(db.getWorkTree(), "sub").mkdir(); + File file = new File(db.getWorkTree(), "sub/a.txt"); file.createNewFile(); PrintWriter writer = new PrintWriter(file); writer.print("content"); writer.close(); - File file2 = new File(db.getWorkDir(), "sub/b.txt"); + File file2 = new File(db.getWorkTree(), "sub/b.txt"); file2.createNewFile(); writer = new PrintWriter(file2); writer.print("content b"); @@ -311,20 +311,20 @@ public class AddCommandTest extends RepositoryTestCase { } public void testAddIgnoredFile() throws Exception { - new File(db.getWorkDir(), "sub").mkdir(); - File file = new File(db.getWorkDir(), "sub/a.txt"); + new File(db.getWorkTree(), "sub").mkdir(); + File file = new File(db.getWorkTree(), "sub/a.txt"); file.createNewFile(); PrintWriter writer = new PrintWriter(file); writer.print("content"); writer.close(); - File ignoreFile = new File(db.getWorkDir(), ".gitignore"); + File ignoreFile = new File(db.getWorkTree(), ".gitignore"); ignoreFile.createNewFile(); writer = new PrintWriter(ignoreFile); writer.print("sub/b.txt"); writer.close(); - File file2 = new File(db.getWorkDir(), "sub/b.txt"); + File file2 = new File(db.getWorkTree(), "sub/b.txt"); file2.createNewFile(); writer = new PrintWriter(file2); writer.print("content b"); @@ -339,14 +339,14 @@ public class AddCommandTest extends RepositoryTestCase { } public void testAddWholeRepo() throws Exception { - new File(db.getWorkDir(), "sub").mkdir(); - File file = new File(db.getWorkDir(), "sub/a.txt"); + new File(db.getWorkTree(), "sub").mkdir(); + File file = new File(db.getWorkTree(), "sub/a.txt"); file.createNewFile(); PrintWriter writer = new PrintWriter(file); writer.print("content"); writer.close(); - File file2 = new File(db.getWorkDir(), "sub/b.txt"); + File file2 = new File(db.getWorkTree(), "sub/b.txt"); file2.createNewFile(); writer = new PrintWriter(file2); writer.print("content b"); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java index c965c67664..773d2f0556 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java @@ -100,20 +100,20 @@ public class MergeCommandTest extends RepositoryTestCase { addNewFileToIndex("file1"); RevCommit first = git.commit().setMessage("initial commit").call(); - assertTrue(new File(db.getWorkDir(), "file1").exists()); + assertTrue(new File(db.getWorkTree(), "file1").exists()); createBranch(first, "refs/heads/branch1"); addNewFileToIndex("file2"); RevCommit second = git.commit().setMessage("second commit").call(); - assertTrue(new File(db.getWorkDir(), "file2").exists()); + assertTrue(new File(db.getWorkTree(), "file2").exists()); checkoutBranch("refs/heads/branch1"); - assertFalse(new File(db.getWorkDir(), "file2").exists()); + assertFalse(new File(db.getWorkTree(), "file2").exists()); MergeResult result = git.merge().include(db.getRef(Constants.MASTER)).call(); - assertTrue(new File(db.getWorkDir(), "file1").exists()); - assertTrue(new File(db.getWorkDir(), "file2").exists()); + assertTrue(new File(db.getWorkTree(), "file1").exists()); + assertTrue(new File(db.getWorkTree(), "file2").exists()); assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus()); assertEquals(second, result.getNewHead()); } @@ -132,8 +132,8 @@ public class MergeCommandTest extends RepositoryTestCase { git.commit().setMessage("third commit").call(); checkoutBranch("refs/heads/branch1"); - assertFalse(new File(db.getWorkDir(), "file2").exists()); - assertFalse(new File(db.getWorkDir(), "file3").exists()); + assertFalse(new File(db.getWorkTree(), "file2").exists()); + assertFalse(new File(db.getWorkTree(), "file3").exists()); MergeCommand merge = git.merge(); merge.include(second.getId()); @@ -152,7 +152,7 @@ public class MergeCommandTest extends RepositoryTestCase { } private void checkoutBranch(String branchName) throws Exception { - File workDir = db.getWorkDir(); + File workDir = db.getWorkTree(); if (workDir != null) { WorkDirCheckout workDirCheckout = new WorkDirCheckout(db, workDir, db.mapCommit(Constants.HEAD).getTree(), @@ -176,7 +176,7 @@ public class MergeCommandTest extends RepositoryTestCase { File writeTrashFile = writeTrashFile(filename, filename); GitIndex index = db.getIndex(); - Entry entry = index.add(db.getWorkDir(), writeTrashFile); + Entry entry = index.add(db.getWorkTree(), writeTrashFile); entry.update(writeTrashFile); index.write(); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/SimilarityIndexTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/SimilarityIndexTest.java index d6915eb872..7e42e53586 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/SimilarityIndexTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/SimilarityIndexTest.java @@ -43,12 +43,15 @@ package org.eclipse.jgit.diff; +import java.io.ByteArrayInputStream; +import java.io.IOException; + import junit.framework.TestCase; import org.eclipse.jgit.lib.Constants; public class SimilarityIndexTest extends TestCase { - public void testIndexing() { + public void testIndexingSmallObject() { SimilarityIndex si = hash("" // + "A\n" // + "B\n" // @@ -67,6 +70,17 @@ public class SimilarityIndexTest extends TestCase { assertEquals(2, si.count(si.findIndex(key_D))); } + public void testIndexingLargeObject() throws IOException { + byte[] in = ("" // + + "A\n" // + + "B\n" // + + "B\n" // + + "B\n").getBytes("UTF-8"); + SimilarityIndex si = new SimilarityIndex(); + si.hash(new ByteArrayInputStream(in), in.length); + assertEquals(2, si.size()); + } + public void testCommonScore_SameFiles() { String text = "" // + "A\n" // diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java index f4692b168d..c3ac952a11 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java @@ -54,7 +54,7 @@ public class DirCacheBasicTest extends RepositoryTestCase { final File idx = new File(db.getDirectory(), "index"); assertFalse(idx.exists()); - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); assertNotNull(dc); assertEquals(0, dc.getEntryCount()); } @@ -74,7 +74,7 @@ public class DirCacheBasicTest extends RepositoryTestCase { assertFalse(idx.exists()); assertFalse(lck.exists()); - final DirCache dc = DirCache.lock(db); + final DirCache dc = db.lockDirCache(); assertNotNull(dc); assertFalse(idx.exists()); assertTrue(lck.exists()); @@ -108,7 +108,7 @@ public class DirCacheBasicTest extends RepositoryTestCase { assertFalse(idx.exists()); assertFalse(lck.exists()); - final DirCache dc = DirCache.lock(db); + final DirCache dc = db.lockDirCache(); assertEquals(0, lck.length()); dc.write(); assertEquals(12 + 20, lck.length()); @@ -124,7 +124,7 @@ public class DirCacheBasicTest extends RepositoryTestCase { assertFalse(idx.exists()); assertFalse(lck.exists()); - final DirCache dc = DirCache.lock(db); + final DirCache dc = db.lockDirCache(); assertEquals(0, lck.length()); dc.write(); assertEquals(12 + 20, lck.length()); @@ -141,13 +141,13 @@ public class DirCacheBasicTest extends RepositoryTestCase { assertFalse(idx.exists()); assertFalse(lck.exists()); { - final DirCache dc = DirCache.lock(db); + final DirCache dc = db.lockDirCache(); dc.write(); assertTrue(dc.commit()); assertTrue(idx.exists()); } { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); assertEquals(0, dc.getEntryCount()); } } @@ -158,13 +158,13 @@ public class DirCacheBasicTest extends RepositoryTestCase { assertFalse(idx.exists()); assertFalse(lck.exists()); { - final DirCache dc = DirCache.lock(db); + final DirCache dc = db.lockDirCache(); dc.write(); assertTrue(dc.commit()); assertTrue(idx.exists()); } { - final DirCache dc = DirCache.lock(db); + final DirCache dc = db.lockDirCache(); assertEquals(0, dc.getEntryCount()); assertTrue(idx.exists()); assertTrue(lck.exists()); @@ -173,7 +173,7 @@ public class DirCacheBasicTest extends RepositoryTestCase { } public void testBuildThenClear() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final String[] paths = { "a.", "a.b", "a/b", "a0b" }; final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; @@ -195,7 +195,7 @@ public class DirCacheBasicTest extends RepositoryTestCase { } public void testDetectUnmergedPaths() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final DirCacheEntry[] ents = new DirCacheEntry[3]; ents[0] = new DirCacheEntry("a", 1); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java index 03bb7f5e83..a09f8e86c4 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java @@ -52,7 +52,7 @@ import org.eclipse.jgit.treewalk.filter.PathFilterGroup; public class DirCacheBuilderIteratorTest extends RepositoryTestCase { public void testPathFilterGroup_DoesNotSkipTail() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final FileMode mode = FileMode.REGULAR_FILE; final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" }; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java index e919e41f4d..81ffab9148 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java @@ -52,7 +52,7 @@ import org.eclipse.jgit.lib.RepositoryTestCase; public class DirCacheBuilderTest extends RepositoryTestCase { public void testBuildEmpty() throws Exception { { - final DirCache dc = DirCache.lock(db); + final DirCache dc = db.lockDirCache(); final DirCacheBuilder b = dc.builder(); assertNotNull(b); b.finish(); @@ -60,7 +60,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase { assertTrue(dc.commit()); } { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); assertEquals(0, dc.getEntryCount()); } } @@ -86,7 +86,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase { final int length = 1342; final DirCacheEntry entOrig; { - final DirCache dc = DirCache.lock(db); + final DirCache dc = db.lockDirCache(); final DirCacheBuilder b = dc.builder(); assertNotNull(b); @@ -113,7 +113,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase { assertTrue(dc.commit()); } { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); assertEquals(1, dc.getEntryCount()); final DirCacheEntry entRead = dc.getEntry(0); @@ -135,7 +135,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase { final int length = 1342; final DirCacheEntry entOrig; { - final DirCache dc = DirCache.lock(db); + final DirCache dc = db.lockDirCache(); final DirCacheBuilder b = dc.builder(); assertNotNull(b); @@ -160,7 +160,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase { assertFalse(new File(db.getDirectory(), "index.lock").exists()); } { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); assertEquals(1, dc.getEntryCount()); final DirCacheEntry entRead = dc.getEntry(0); @@ -177,7 +177,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase { public void testFindSingleFile() throws Exception { final String path = "a-file-path"; - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final DirCacheBuilder b = dc.builder(); assertNotNull(b); @@ -202,7 +202,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase { } public void testAdd_InGitSortOrder() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final String[] paths = { "a.", "a.b", "a/b", "a0b" }; final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; @@ -226,7 +226,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase { } public void testAdd_ReverseGitSortOrder() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final String[] paths = { "a.", "a.b", "a/b", "a0b" }; final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; @@ -250,7 +250,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase { } public void testBuilderClear() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final String[] paths = { "a.", "a.b", "a/b", "a0b" }; final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheFindTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheFindTest.java index d5a632c48c..5533fe358a 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheFindTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheFindTest.java @@ -48,7 +48,7 @@ import org.eclipse.jgit.lib.RepositoryTestCase; public class DirCacheFindTest extends RepositoryTestCase { public void testEntriesWithin() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" }; final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java index efea117388..24e3c34ddf 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java @@ -52,7 +52,7 @@ import org.eclipse.jgit.treewalk.filter.PathFilterGroup; public class DirCacheIteratorTest extends RepositoryTestCase { public void testEmptyTree_NoTreeWalk() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); assertEquals(0, dc.getEntryCount()); final DirCacheIterator i = new DirCacheIterator(dc); @@ -60,7 +60,7 @@ public class DirCacheIteratorTest extends RepositoryTestCase { } public void testEmptyTree_WithTreeWalk() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); assertEquals(0, dc.getEntryCount()); final TreeWalk tw = new TreeWalk(db); @@ -70,7 +70,7 @@ public class DirCacheIteratorTest extends RepositoryTestCase { } public void testNoSubtree_NoTreeWalk() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final String[] paths = { "a.", "a0b" }; final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; @@ -95,7 +95,7 @@ public class DirCacheIteratorTest extends RepositoryTestCase { } public void testNoSubtree_WithTreeWalk() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final String[] paths = { "a.", "a0b" }; final FileMode[] modes = { FileMode.EXECUTABLE_FILE, FileMode.GITLINK }; @@ -128,7 +128,7 @@ public class DirCacheIteratorTest extends RepositoryTestCase { } public void testSingleSubtree_NoRecursion() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" }; final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; @@ -172,7 +172,7 @@ public class DirCacheIteratorTest extends RepositoryTestCase { } public void testSingleSubtree_Recursive() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final FileMode mode = FileMode.REGULAR_FILE; final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" }; @@ -207,7 +207,7 @@ public class DirCacheIteratorTest extends RepositoryTestCase { } public void testTwoLevelSubtree_Recursive() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final FileMode mode = FileMode.REGULAR_FILE; final String[] paths = { "a.", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" }; @@ -241,7 +241,7 @@ public class DirCacheIteratorTest extends RepositoryTestCase { } public void testTwoLevelSubtree_FilterPath() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final FileMode mode = FileMode.REGULAR_FILE; final String[] paths = { "a.", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" }; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheLargePathTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheLargePathTest.java index 0926ab9899..a6d7e39eb5 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheLargePathTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheLargePathTest.java @@ -85,7 +85,7 @@ public class DirCacheLargePathTest extends RepositoryTestCase { assertEquals(shortPath, shortEnt.getPathString()); { - final DirCache dc1 = DirCache.lock(db); + final DirCache dc1 = db.lockDirCache(); { final DirCacheBuilder b = dc1.builder(); b.add(longEnt); @@ -97,7 +97,7 @@ public class DirCacheLargePathTest extends RepositoryTestCase { assertSame(shortEnt, dc1.getEntry(1)); } { - final DirCache dc2 = DirCache.read(db); + final DirCache dc2 = db.readDirCache(); assertEquals(2, dc2.getEntryCount()); assertNotSame(longEnt, dc2.getEntry(0)); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheTreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheTreeTest.java index 8345c5d83d..dfca2fb298 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheTreeTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheTreeTest.java @@ -51,12 +51,12 @@ import org.eclipse.jgit.lib.RepositoryTestCase; public class DirCacheTreeTest extends RepositoryTestCase { public void testEmptyCache_NoCacheTree() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); assertNull(dc.getCacheTree(false)); } public void testEmptyCache_CreateEmptyCacheTree() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final DirCacheTree tree = dc.getCacheTree(true); assertNotNull(tree); assertSame(tree, dc.getCacheTree(false)); @@ -69,7 +69,7 @@ public class DirCacheTreeTest extends RepositoryTestCase { } public void testEmptyCache_Clear_NoCacheTree() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final DirCacheTree tree = dc.getCacheTree(true); assertNotNull(tree); dc.clear(); @@ -78,7 +78,7 @@ public class DirCacheTreeTest extends RepositoryTestCase { } public void testSingleSubtree() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" }; final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; @@ -115,7 +115,7 @@ public class DirCacheTreeTest extends RepositoryTestCase { } public void testTwoLevelSubtree() throws Exception { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); final String[] paths = { "a.", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" }; final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; @@ -172,7 +172,7 @@ public class DirCacheTreeTest extends RepositoryTestCase { * @throws IOException */ public void testWriteReadTree() throws CorruptObjectException, IOException { - final DirCache dc = DirCache.lock(db); + final DirCache dc = db.lockDirCache(); final String A = String.format("a%2000s", "a"); final String B = String.format("b%2000s", "b"); @@ -188,7 +188,7 @@ public class DirCacheTreeTest extends RepositoryTestCase { b.add(ents[i]); b.commit(); - DirCache read = DirCache.read(db); + DirCache read = db.readDirCache(); assertEquals(paths.length, read.getEntryCount()); assertEquals(1, read.getCacheTree(true).getChildCount()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java index 02bfdc1df5..2fa45c8211 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java @@ -45,11 +45,9 @@ */ package org.eclipse.jgit.lib; -import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -131,13 +129,15 @@ public abstract class ReadTreeTest extends RepositoryTestCase { } ObjectId genSha1(String data) { - InputStream is = new ByteArrayInputStream(data.getBytes()); - ObjectWriter objectWriter = new ObjectWriter(db); + ObjectInserter w = db.newObjectInserter(); try { - return objectWriter.writeObject(Constants.OBJ_BLOB, data - .getBytes().length, is, true); + ObjectId id = w.insert(Constants.OBJ_BLOB, data.getBytes()); + w.flush(); + return id; } catch (IOException e) { fail(e.toString()); + } finally { + w.release(); } return null; } @@ -623,7 +623,7 @@ public abstract class ReadTreeTest extends RepositoryTestCase { expectedValue = i.get(path); assertNotNull("found unexpected file for path " + path + " in workdir", expectedValue); - File file = new File(db.getWorkDir(), path); + File file = new File(db.getWorkTree(), path); assertTrue(file.exists()); if (file.isFile()) { FileInputStream is = new FileInputStream(file); @@ -661,8 +661,8 @@ public abstract class ReadTreeTest extends RepositoryTestCase { assertTrue("unexpected content for path " + path + " in index. Expected: <" + expectedValue + ">", Arrays.equals( - db.openBlob(theIndex.getMembers()[j].getObjectId()) - .getBytes(), i.get(path).getBytes())); + db.open(theIndex.getMembers()[j].getObjectId()) + .getCachedBytes(), i.get(path).getBytes())); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java index 88bcf76710..d78892b89d 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java @@ -70,7 +70,7 @@ public class ReflogConfigTest extends RepositoryTestCase { // set the logAllRefUpdates parameter to true and check it db.getConfig().setBoolean("core", null, "logallrefupdates", true); - assertTrue(db.getConfig().getCore().isLogAllRefUpdates()); + assertTrue(db.getConfig().get(CoreConfig.KEY).isLogAllRefUpdates()); // do one commit and check that reflog size is increased to 1 addFileToTree(t, "i-am-another-file", "and this is other data in me\n"); @@ -83,7 +83,7 @@ public class ReflogConfigTest extends RepositoryTestCase { // set the logAllRefUpdates parameter to false and check it db.getConfig().setBoolean("core", null, "logallrefupdates", false); - assertFalse(db.getConfig().getCore().isLogAllRefUpdates()); + assertFalse(db.getConfig().get(CoreConfig.KEY).isLogAllRefUpdates()); // do one commit and check that reflog size is 2 addFileToTree(t, "i-am-anotheranother-file", 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..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. @@ -83,7 +84,7 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase { protected File writeTrashFile(final String name, final String data) throws IOException { - File path = new File(db.getWorkDir(), name); + File path = new File(db.getWorkTree(), name); write(path, data); return path; } @@ -102,7 +103,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; @@ -111,6 +112,6 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase { protected void setUp() throws Exception { super.setUp(); db = createWorkRepository(); - trash = db.getWorkDir(); + trash = db.getWorkTree(); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SampleDataRepositoryTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SampleDataRepositoryTestCase.java index 10c005679b..7bc9bb22e7 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SampleDataRepositoryTestCase.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SampleDataRepositoryTestCase.java @@ -65,7 +65,7 @@ public abstract class SampleDataRepositoryTestCase extends RepositoryTestCase { "pack-e6d07037cbcf13376308a0a995d1fa48f8f76aaa", "pack-3280af9c07ee18a87705ef50b0cc4cd20266cf12" }; - final File packDir = new File(db.getObjectsDirectory(), "pack"); + final File packDir = new File(db.getObjectDatabase().getDirectory(), "pack"); for (String n : packs) { copyFile(JGitTestUtil.getTestResourceFile(n + ".pack"), new File(packDir, n + ".pack")); copyFile(JGitTestUtil.getTestResourceFile(n + ".idx"), new File(packDir, n + ".idx")); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WorkDirCheckout_ReadTreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WorkDirCheckout_ReadTreeTest.java index ecaac5846e..7b5c3cdc57 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WorkDirCheckout_ReadTreeTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WorkDirCheckout_ReadTreeTest.java @@ -52,12 +52,12 @@ import java.util.HashMap; public class WorkDirCheckout_ReadTreeTest extends ReadTreeTest { private WorkDirCheckout wdc; public void prescanTwoTrees(Tree head, Tree merge) throws IllegalStateException, IOException { - wdc = new WorkDirCheckout(db, db.getWorkDir(), head, db.getIndex(), merge); + wdc = new WorkDirCheckout(db, db.getWorkTree(), head, db.getIndex(), merge); wdc.prescanTwoTrees(); } public void checkout() throws IOException { - wdc = new WorkDirCheckout(db, db.getWorkDir(), theHead, db.getIndex(), theMerge); + wdc = new WorkDirCheckout(db, db.getWorkTree(), theHead, db.getIndex(), theMerge); wdc.checkout(); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CherryPickTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CherryPickTest.java index 42e653be37..1cd1261636 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CherryPickTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CherryPickTest.java @@ -44,7 +44,8 @@ package org.eclipse.jgit.merge; -import java.io.ByteArrayInputStream; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuilder; @@ -53,7 +54,7 @@ import org.eclipse.jgit.lib.Commit; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectWriter; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.RepositoryTestCase; import org.eclipse.jgit.treewalk.TreeWalk; @@ -66,10 +67,10 @@ public class CherryPickTest extends RepositoryTestCase { // Cherry-pick "T" onto "O". This shouldn't introduce "p-fail", which // was created by "P", nor should it modify "a", which was done by "P". // - final DirCache treeB = DirCache.read(db); - final DirCache treeO = DirCache.read(db); - final DirCache treeP = DirCache.read(db); - final DirCache treeT = DirCache.read(db); + final DirCache treeB = db.readDirCache(); + final DirCache treeO = db.readDirCache(); + final DirCache treeP = db.readDirCache(); + final DirCache treeT = db.readDirCache(); { final DirCacheBuilder b = treeB.builder(); final DirCacheBuilder o = treeO.builder(); @@ -93,7 +94,7 @@ public class CherryPickTest extends RepositoryTestCase { t.finish(); } - final ObjectWriter ow = new ObjectWriter(db); + final ObjectInserter ow = db.newObjectInserter(); final ObjectId B = commit(ow, treeB, new ObjectId[] {}); final ObjectId O = commit(ow, treeO, new ObjectId[] { B }); final ObjectId P = commit(ow, treeP, new ObjectId[] { B }); @@ -128,15 +129,17 @@ public class CherryPickTest extends RepositoryTestCase { .getObjectId(0)); } - private ObjectId commit(final ObjectWriter ow, final DirCache treeB, + private ObjectId commit(final ObjectInserter odi, final DirCache treeB, final ObjectId[] parentIds) throws Exception { final Commit c = new Commit(db); - c.setTreeId(treeB.writeTree(ow)); + c.setTreeId(treeB.writeTree(odi)); c.setAuthor(new PersonIdent("A U Thor", "a.u.thor", 1L, 0)); c.setCommitter(c.getAuthor()); c.setParentIds(parentIds); c.setMessage("Tree " + c.getTreeId().name()); - return ow.writeCommit(c); + ObjectId id = odi.insert(OBJ_COMMIT, odi.format(c)); + odi.flush(); + return id; } private DirCacheEntry makeEntry(final String path, final FileMode mode) @@ -148,9 +151,8 @@ public class CherryPickTest extends RepositoryTestCase { final String content) throws Exception { final DirCacheEntry ent = new DirCacheEntry(path); ent.setFileMode(mode); - final byte[] contentBytes = Constants.encode(content); - ent.setObjectId(new ObjectWriter(db).computeBlobSha1( - contentBytes.length, new ByteArrayInputStream(contentBytes))); + ent.setObjectId(new ObjectInserter.Formatter().idFor(OBJ_BLOB, + Constants.encode(content))); return ent; } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java index 690b166cbc..8657c52b16 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java @@ -44,7 +44,9 @@ package org.eclipse.jgit.merge; -import java.io.ByteArrayInputStream; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; + import java.io.IOException; import org.eclipse.jgit.dircache.DirCache; @@ -54,7 +56,7 @@ import org.eclipse.jgit.lib.Commit; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectWriter; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.SampleDataRepositoryTestCase; import org.eclipse.jgit.treewalk.TreeWalk; @@ -103,9 +105,9 @@ public class SimpleMergeTest extends SampleDataRepositoryTestCase { } public void testTrivialTwoWay_validSubtreeSort() throws Exception { - final DirCache treeB = DirCache.read(db); - final DirCache treeO = DirCache.read(db); - final DirCache treeT = DirCache.read(db); + final DirCache treeB = db.readDirCache(); + final DirCache treeO = db.readDirCache(); + final DirCache treeT = db.readDirCache(); { final DirCacheBuilder b = treeB.builder(); final DirCacheBuilder o = treeO.builder(); @@ -126,7 +128,7 @@ public class SimpleMergeTest extends SampleDataRepositoryTestCase { t.finish(); } - final ObjectWriter ow = new ObjectWriter(db); + final ObjectInserter ow = db.newObjectInserter(); final ObjectId b = commit(ow, treeB, new ObjectId[] {}); final ObjectId o = commit(ow, treeO, new ObjectId[] { b }); final ObjectId t = commit(ow, treeT, new ObjectId[] { b }); @@ -155,9 +157,9 @@ public class SimpleMergeTest extends SampleDataRepositoryTestCase { } public void testTrivialTwoWay_concurrentSubtreeChange() throws Exception { - final DirCache treeB = DirCache.read(db); - final DirCache treeO = DirCache.read(db); - final DirCache treeT = DirCache.read(db); + final DirCache treeB = db.readDirCache(); + final DirCache treeO = db.readDirCache(); + final DirCache treeT = db.readDirCache(); { final DirCacheBuilder b = treeB.builder(); final DirCacheBuilder o = treeO.builder(); @@ -177,7 +179,7 @@ public class SimpleMergeTest extends SampleDataRepositoryTestCase { t.finish(); } - final ObjectWriter ow = new ObjectWriter(db); + final ObjectInserter ow = db.newObjectInserter(); final ObjectId b = commit(ow, treeB, new ObjectId[] {}); final ObjectId o = commit(ow, treeO, new ObjectId[] { b }); final ObjectId t = commit(ow, treeT, new ObjectId[] { b }); @@ -202,9 +204,9 @@ public class SimpleMergeTest extends SampleDataRepositoryTestCase { } public void testTrivialTwoWay_conflictSubtreeChange() throws Exception { - final DirCache treeB = DirCache.read(db); - final DirCache treeO = DirCache.read(db); - final DirCache treeT = DirCache.read(db); + final DirCache treeB = db.readDirCache(); + final DirCache treeO = db.readDirCache(); + final DirCache treeT = db.readDirCache(); { final DirCacheBuilder b = treeB.builder(); final DirCacheBuilder o = treeO.builder(); @@ -224,7 +226,7 @@ public class SimpleMergeTest extends SampleDataRepositoryTestCase { t.finish(); } - final ObjectWriter ow = new ObjectWriter(db); + final ObjectInserter ow = db.newObjectInserter(); final ObjectId b = commit(ow, treeB, new ObjectId[] {}); final ObjectId o = commit(ow, treeO, new ObjectId[] { b }); final ObjectId t = commit(ow, treeT, new ObjectId[] { b }); @@ -235,9 +237,9 @@ public class SimpleMergeTest extends SampleDataRepositoryTestCase { } public void testTrivialTwoWay_leftDFconflict1() throws Exception { - final DirCache treeB = DirCache.read(db); - final DirCache treeO = DirCache.read(db); - final DirCache treeT = DirCache.read(db); + final DirCache treeB = db.readDirCache(); + final DirCache treeO = db.readDirCache(); + final DirCache treeT = db.readDirCache(); { final DirCacheBuilder b = treeB.builder(); final DirCacheBuilder o = treeO.builder(); @@ -256,7 +258,7 @@ public class SimpleMergeTest extends SampleDataRepositoryTestCase { t.finish(); } - final ObjectWriter ow = new ObjectWriter(db); + final ObjectInserter ow = db.newObjectInserter(); final ObjectId b = commit(ow, treeB, new ObjectId[] {}); final ObjectId o = commit(ow, treeO, new ObjectId[] { b }); final ObjectId t = commit(ow, treeT, new ObjectId[] { b }); @@ -267,9 +269,9 @@ public class SimpleMergeTest extends SampleDataRepositoryTestCase { } public void testTrivialTwoWay_rightDFconflict1() throws Exception { - final DirCache treeB = DirCache.read(db); - final DirCache treeO = DirCache.read(db); - final DirCache treeT = DirCache.read(db); + final DirCache treeB = db.readDirCache(); + final DirCache treeO = db.readDirCache(); + final DirCache treeT = db.readDirCache(); { final DirCacheBuilder b = treeB.builder(); final DirCacheBuilder o = treeO.builder(); @@ -288,7 +290,7 @@ public class SimpleMergeTest extends SampleDataRepositoryTestCase { t.finish(); } - final ObjectWriter ow = new ObjectWriter(db); + final ObjectInserter ow = db.newObjectInserter(); final ObjectId b = commit(ow, treeB, new ObjectId[] {}); final ObjectId o = commit(ow, treeO, new ObjectId[] { b }); final ObjectId t = commit(ow, treeT, new ObjectId[] { b }); @@ -299,9 +301,9 @@ public class SimpleMergeTest extends SampleDataRepositoryTestCase { } public void testTrivialTwoWay_leftDFconflict2() throws Exception { - final DirCache treeB = DirCache.read(db); - final DirCache treeO = DirCache.read(db); - final DirCache treeT = DirCache.read(db); + final DirCache treeB = db.readDirCache(); + final DirCache treeO = db.readDirCache(); + final DirCache treeT = db.readDirCache(); { final DirCacheBuilder b = treeB.builder(); final DirCacheBuilder o = treeO.builder(); @@ -318,7 +320,7 @@ public class SimpleMergeTest extends SampleDataRepositoryTestCase { t.finish(); } - final ObjectWriter ow = new ObjectWriter(db); + final ObjectInserter ow = db.newObjectInserter(); final ObjectId b = commit(ow, treeB, new ObjectId[] {}); final ObjectId o = commit(ow, treeO, new ObjectId[] { b }); final ObjectId t = commit(ow, treeT, new ObjectId[] { b }); @@ -329,9 +331,9 @@ public class SimpleMergeTest extends SampleDataRepositoryTestCase { } public void testTrivialTwoWay_rightDFconflict2() throws Exception { - final DirCache treeB = DirCache.read(db); - final DirCache treeO = DirCache.read(db); - final DirCache treeT = DirCache.read(db); + final DirCache treeB = db.readDirCache(); + final DirCache treeO = db.readDirCache(); + final DirCache treeT = db.readDirCache(); { final DirCacheBuilder b = treeB.builder(); final DirCacheBuilder o = treeO.builder(); @@ -348,7 +350,7 @@ public class SimpleMergeTest extends SampleDataRepositoryTestCase { t.finish(); } - final ObjectWriter ow = new ObjectWriter(db); + final ObjectInserter ow = db.newObjectInserter(); final ObjectId b = commit(ow, treeB, new ObjectId[] {}); final ObjectId o = commit(ow, treeO, new ObjectId[] { b }); final ObjectId t = commit(ow, treeT, new ObjectId[] { b }); @@ -363,15 +365,17 @@ public class SimpleMergeTest extends SampleDataRepositoryTestCase { .getObjectId(0)); } - private ObjectId commit(final ObjectWriter ow, final DirCache treeB, + private ObjectId commit(final ObjectInserter odi, final DirCache treeB, final ObjectId[] parentIds) throws Exception { final Commit c = new Commit(db); - c.setTreeId(treeB.writeTree(ow)); + c.setTreeId(treeB.writeTree(odi)); c.setAuthor(new PersonIdent("A U Thor", "a.u.thor", 1L, 0)); c.setCommitter(c.getAuthor()); c.setParentIds(parentIds); c.setMessage("Tree " + c.getTreeId().name()); - return ow.writeCommit(c); + ObjectId id = odi.insert(OBJ_COMMIT, odi.format(c)); + odi.flush(); + return id; } private DirCacheEntry makeEntry(final String path, final FileMode mode) @@ -383,9 +387,8 @@ public class SimpleMergeTest extends SampleDataRepositoryTestCase { final String content) throws Exception { final DirCacheEntry ent = new DirCacheEntry(path); ent.setFileMode(mode); - final byte[] contentBytes = Constants.encode(content); - ent.setObjectId(new ObjectWriter(db).computeBlobSha1( - contentBytes.length, new ByteArrayInputStream(contentBytes))); + ent.setObjectId(new ObjectInserter.Formatter().idFor(OBJ_BLOB, + Constants.encode(content))); return ent; } } 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<Repository> util; protected RevWalk rw; @Override public void setUp() throws Exception { super.setUp(); - util = new TestRepository(db, createRevWalk()); + util = new TestRepository<Repository>(db, createRevWalk()); rw = util.getRevWalk(); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java index 69430ed334..c8f2aad759 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java @@ -42,7 +42,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.BufferedOutputStream; import java.io.File; @@ -53,8 +53,17 @@ 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.Repository; +import org.eclipse.jgit.lib.RepositoryTestCase; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.pack.PackWriter; public class ConcurrentRepackTest extends RepositoryTestCase { public void setUp() throws Exception { @@ -125,10 +134,11 @@ public class ConcurrentRepackTest extends RepositoryTestCase { // within the pack has been modified. // final RevObject o2 = writeBlob(eden, "o2"); - final PackWriter pw = new PackWriter(eden, NullProgressMonitor.INSTANCE); + final PackWriter pw = new PackWriter(eden); pw.addObject(o2); pw.addObject(o1); write(out1, pw); + pw.release(); // 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. @@ -148,7 +158,7 @@ public class ConcurrentRepackTest extends RepositoryTestCase { final File[] out1 = pack(eden, o1); assertEquals(o1.name(), parse(o1).name()); - final ObjectLoader load1 = db.openBlob(o1); + final ObjectLoader load1 = db.open(o1, Constants.OBJ_BLOB); assertNotNull(load1); final RevObject o2 = writeBlob(eden, "o2"); @@ -163,7 +173,7 @@ public class ConcurrentRepackTest extends RepositoryTestCase { // earlier still resolve the object, even though its underlying // pack is gone, but the object still exists. // - final ObjectLoader load2 = db.openBlob(o1); + final ObjectLoader load2 = db.open(o1, Constants.OBJ_BLOB); assertNotNull(load2); assertNotSame(load1, load2); @@ -189,7 +199,7 @@ public class ConcurrentRepackTest extends RepositoryTestCase { private File[] pack(final Repository src, final RevObject... list) throws IOException { - final PackWriter pw = new PackWriter(src, NullProgressMonitor.INSTANCE); + final PackWriter pw = new PackWriter(src); for (final RevObject o : list) { pw.addObject(o); } @@ -199,17 +209,19 @@ public class ConcurrentRepackTest extends RepositoryTestCase { final File idxFile = fullPackFileName(name, ".idx"); final File[] files = new File[] { packFile, idxFile }; write(files, pw); + pw.release(); return files; } private static void write(final File[] files, final PackWriter pw) throws IOException { final long begin = files[0].getParentFile().lastModified(); + NullProgressMonitor m = NullProgressMonitor.INSTANCE; OutputStream out; out = new BufferedOutputStream(new FileOutputStream(files[0])); try { - pw.writePack(out); + pw.writePack(m, m, out); } finally { out.close(); } @@ -245,7 +257,7 @@ public class ConcurrentRepackTest extends RepositoryTestCase { } private File fullPackFileName(final ObjectId name, final String suffix) { - final File packdir = new File(db.getObjectsDirectory(), "pack"); + final File packdir = new File(db.getObjectDatabase().getDirectory(), "pack"); return new File(packdir, "pack-" + name.name() + suffix); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackFileTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackFileTest.java new file mode 100644 index 0000000000..1a40b8e79c --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackFileTest.java @@ -0,0 +1,387 @@ +/* + * 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.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.MessageDigest; +import java.util.Arrays; +import java.util.zip.Deflater; + +import org.eclipse.jgit.errors.LargeObjectException; +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.junit.TestRng; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectStream; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.storage.pack.DeltaEncoder; +import org.eclipse.jgit.transport.IndexPack; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.TemporaryBuffer; + +public class PackFileTest extends LocalDiskRepositoryTestCase { + private TestRng rng; + + private FileRepository repo; + + private TestRepository<FileRepository> tr; + + private WindowCursor wc; + + protected void setUp() throws Exception { + super.setUp(); + rng = new TestRng(getName()); + repo = createBareRepository(); + tr = new TestRepository<FileRepository>(repo); + wc = (WindowCursor) repo.newObjectReader(); + } + + protected void tearDown() throws Exception { + if (wc != null) + wc.release(); + super.tearDown(); + } + + public void testWhole_SmallObject() throws Exception { + final int type = Constants.OBJ_BLOB; + byte[] data = rng.nextBytes(300); + RevBlob id = tr.blob(data); + tr.branch("master").commit().add("A", id).create(); + tr.packAndPrune(); + assertTrue("has blob", wc.has(id)); + + ObjectLoader ol = wc.open(id); + assertNotNull("created loader", ol); + assertEquals(type, ol.getType()); + assertEquals(data.length, ol.getSize()); + assertFalse("is not large", ol.isLarge()); + assertTrue("same content", Arrays.equals(data, ol.getCachedBytes())); + + ObjectStream in = ol.openStream(); + assertNotNull("have stream", in); + assertEquals(type, in.getType()); + assertEquals(data.length, in.getSize()); + byte[] data2 = new byte[data.length]; + IO.readFully(in, data2, 0, data.length); + assertTrue("same content", Arrays.equals(data2, data)); + assertEquals("stream at EOF", -1, in.read()); + in.close(); + } + + public void testWhole_LargeObject() throws Exception { + final int type = Constants.OBJ_BLOB; + byte[] data = rng.nextBytes(ObjectLoader.STREAM_THRESHOLD + 5); + RevBlob id = tr.blob(data); + tr.branch("master").commit().add("A", id).create(); + tr.packAndPrune(); + assertTrue("has blob", wc.has(id)); + + ObjectLoader ol = wc.open(id); + assertNotNull("created loader", ol); + assertEquals(type, ol.getType()); + assertEquals(data.length, ol.getSize()); + assertTrue("is large", ol.isLarge()); + try { + ol.getCachedBytes(); + fail("Should have thrown LargeObjectException"); + } catch (LargeObjectException tooBig) { + assertEquals(id.name(), tooBig.getMessage()); + } + + ObjectStream in = ol.openStream(); + assertNotNull("have stream", in); + assertEquals(type, in.getType()); + assertEquals(data.length, in.getSize()); + byte[] data2 = new byte[data.length]; + IO.readFully(in, data2, 0, data.length); + assertTrue("same content", Arrays.equals(data2, data)); + assertEquals("stream at EOF", -1, in.read()); + in.close(); + } + + public void testDelta_SmallObjectChain() throws Exception { + ObjectInserter.Formatter fmt = new ObjectInserter.Formatter(); + byte[] data0 = new byte[512]; + Arrays.fill(data0, (byte) 0xf3); + ObjectId id0 = fmt.idFor(Constants.OBJ_BLOB, data0); + + TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64 * 1024); + packHeader(pack, 4); + objectHeader(pack, Constants.OBJ_BLOB, data0.length); + deflate(pack, data0); + + byte[] data1 = clone(0x01, data0); + byte[] delta1 = delta(data0, data1); + ObjectId id1 = fmt.idFor(Constants.OBJ_BLOB, data1); + objectHeader(pack, Constants.OBJ_REF_DELTA, delta1.length); + id0.copyRawTo(pack); + deflate(pack, delta1); + + byte[] data2 = clone(0x02, data1); + byte[] delta2 = delta(data1, data2); + ObjectId id2 = fmt.idFor(Constants.OBJ_BLOB, data2); + objectHeader(pack, Constants.OBJ_REF_DELTA, delta2.length); + id1.copyRawTo(pack); + deflate(pack, delta2); + + byte[] data3 = clone(0x03, data2); + byte[] delta3 = delta(data2, data3); + ObjectId id3 = fmt.idFor(Constants.OBJ_BLOB, data3); + objectHeader(pack, Constants.OBJ_REF_DELTA, delta3.length); + id2.copyRawTo(pack); + deflate(pack, delta3); + + digest(pack); + final byte[] raw = pack.toByteArray(); + IndexPack ip = IndexPack.create(repo, new ByteArrayInputStream(raw)); + ip.setFixThin(true); + ip.index(NullProgressMonitor.INSTANCE); + ip.renameAndOpenPack(); + + assertTrue("has blob", wc.has(id3)); + + ObjectLoader ol = wc.open(id3); + assertNotNull("created loader", ol); + assertEquals(Constants.OBJ_BLOB, ol.getType()); + assertEquals(data3.length, ol.getSize()); + assertFalse("is large", ol.isLarge()); + assertNotNull(ol.getCachedBytes()); + assertTrue(Arrays.equals(data3, ol.getCachedBytes())); + + ObjectStream in = ol.openStream(); + assertNotNull("have stream", in); + assertEquals(Constants.OBJ_BLOB, in.getType()); + assertEquals(data3.length, in.getSize()); + byte[] act = new byte[data3.length]; + IO.readFully(in, act, 0, data3.length); + assertTrue("same content", Arrays.equals(act, data3)); + assertEquals("stream at EOF", -1, in.read()); + in.close(); + } + + public void testDelta_LargeObjectChain() throws Exception { + ObjectInserter.Formatter fmt = new ObjectInserter.Formatter(); + byte[] data0 = new byte[ObjectLoader.STREAM_THRESHOLD + 5]; + Arrays.fill(data0, (byte) 0xf3); + ObjectId id0 = fmt.idFor(Constants.OBJ_BLOB, data0); + + TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64 * 1024); + packHeader(pack, 4); + objectHeader(pack, Constants.OBJ_BLOB, data0.length); + deflate(pack, data0); + + byte[] data1 = clone(0x01, data0); + byte[] delta1 = delta(data0, data1); + ObjectId id1 = fmt.idFor(Constants.OBJ_BLOB, data1); + objectHeader(pack, Constants.OBJ_REF_DELTA, delta1.length); + id0.copyRawTo(pack); + deflate(pack, delta1); + + byte[] data2 = clone(0x02, data1); + byte[] delta2 = delta(data1, data2); + ObjectId id2 = fmt.idFor(Constants.OBJ_BLOB, data2); + objectHeader(pack, Constants.OBJ_REF_DELTA, delta2.length); + id1.copyRawTo(pack); + deflate(pack, delta2); + + byte[] data3 = clone(0x03, data2); + byte[] delta3 = delta(data2, data3); + ObjectId id3 = fmt.idFor(Constants.OBJ_BLOB, data3); + objectHeader(pack, Constants.OBJ_REF_DELTA, delta3.length); + id2.copyRawTo(pack); + deflate(pack, delta3); + + digest(pack); + final byte[] raw = pack.toByteArray(); + IndexPack ip = IndexPack.create(repo, new ByteArrayInputStream(raw)); + ip.setFixThin(true); + ip.index(NullProgressMonitor.INSTANCE); + ip.renameAndOpenPack(); + + assertTrue("has blob", wc.has(id3)); + + ObjectLoader ol = wc.open(id3); + assertNotNull("created loader", ol); + assertEquals(Constants.OBJ_BLOB, ol.getType()); + assertEquals(data3.length, ol.getSize()); + assertTrue("is large", ol.isLarge()); + try { + ol.getCachedBytes(); + fail("Should have thrown LargeObjectException"); + } catch (LargeObjectException tooBig) { + assertEquals(id3.name(), tooBig.getMessage()); + } + + ObjectStream in = ol.openStream(); + assertNotNull("have stream", in); + assertEquals(Constants.OBJ_BLOB, in.getType()); + assertEquals(data3.length, in.getSize()); + byte[] act = new byte[data3.length]; + IO.readFully(in, act, 0, data3.length); + assertTrue("same content", Arrays.equals(act, data3)); + assertEquals("stream at EOF", -1, in.read()); + in.close(); + } + + public void testDelta_LargeInstructionStream() throws Exception { + ObjectInserter.Formatter fmt = new ObjectInserter.Formatter(); + byte[] data0 = new byte[32]; + Arrays.fill(data0, (byte) 0xf3); + ObjectId id0 = fmt.idFor(Constants.OBJ_BLOB, data0); + + byte[] data3 = rng.nextBytes(ObjectLoader.STREAM_THRESHOLD + 5); + ByteArrayOutputStream tmp = new ByteArrayOutputStream(); + DeltaEncoder de = new DeltaEncoder(tmp, data0.length, data3.length); + de.insert(data3, 0, data3.length); + byte[] delta3 = tmp.toByteArray(); + assertTrue(delta3.length > ObjectLoader.STREAM_THRESHOLD); + + TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64 * 1024); + packHeader(pack, 2); + objectHeader(pack, Constants.OBJ_BLOB, data0.length); + deflate(pack, data0); + + ObjectId id3 = fmt.idFor(Constants.OBJ_BLOB, data3); + objectHeader(pack, Constants.OBJ_REF_DELTA, delta3.length); + id0.copyRawTo(pack); + deflate(pack, delta3); + + digest(pack); + final byte[] raw = pack.toByteArray(); + IndexPack ip = IndexPack.create(repo, new ByteArrayInputStream(raw)); + ip.setFixThin(true); + ip.index(NullProgressMonitor.INSTANCE); + ip.renameAndOpenPack(); + + assertTrue("has blob", wc.has(id3)); + + ObjectLoader ol = wc.open(id3); + assertNotNull("created loader", ol); + assertEquals(Constants.OBJ_BLOB, ol.getType()); + assertEquals(data3.length, ol.getSize()); + assertTrue("is large", ol.isLarge()); + try { + ol.getCachedBytes(); + fail("Should have thrown LargeObjectException"); + } catch (LargeObjectException tooBig) { + assertEquals(id3.name(), tooBig.getMessage()); + } + + ObjectStream in = ol.openStream(); + assertNotNull("have stream", in); + assertEquals(Constants.OBJ_BLOB, in.getType()); + assertEquals(data3.length, in.getSize()); + byte[] act = new byte[data3.length]; + IO.readFully(in, act, 0, data3.length); + assertTrue("same content", Arrays.equals(act, data3)); + assertEquals("stream at EOF", -1, in.read()); + in.close(); + } + + private byte[] clone(int first, byte[] base) { + byte[] r = new byte[base.length]; + System.arraycopy(base, 1, r, 1, r.length - 1); + r[0] = (byte) first; + return r; + } + + private byte[] delta(byte[] base, byte[] dest) throws IOException { + ByteArrayOutputStream tmp = new ByteArrayOutputStream(); + DeltaEncoder de = new DeltaEncoder(tmp, base.length, dest.length); + de.insert(dest, 0, 1); + de.copy(1, base.length - 1); + return tmp.toByteArray(); + } + + private void packHeader(TemporaryBuffer.Heap pack, int cnt) + throws IOException { + final byte[] hdr = new byte[8]; + NB.encodeInt32(hdr, 0, 2); + NB.encodeInt32(hdr, 4, cnt); + pack.write(Constants.PACK_SIGNATURE); + pack.write(hdr, 0, 8); + } + + private void objectHeader(TemporaryBuffer.Heap pack, int type, int sz) + throws IOException { + byte[] buf = new byte[8]; + int nextLength = sz >>> 4; + buf[0] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (type << 4) | (sz & 0x0F)); + sz = nextLength; + int n = 1; + while (sz > 0) { + nextLength >>>= 7; + buf[n++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (sz & 0x7F)); + sz = nextLength; + } + pack.write(buf, 0, n); + } + + private void deflate(TemporaryBuffer.Heap pack, final byte[] content) + throws IOException { + final Deflater deflater = new Deflater(); + final byte[] buf = new byte[128]; + deflater.setInput(content, 0, content.length); + deflater.finish(); + do { + final int n = deflater.deflate(buf, 0, buf.length); + if (n > 0) + pack.write(buf, 0, n); + } while (!deflater.finished()); + deflater.end(); + } + + private void digest(TemporaryBuffer.Heap buf) throws IOException { + MessageDigest md = Constants.newMessageDigest(); + md.update(buf.toByteArray()); + buf.write(md.digest()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexTestCase.java index d1c5c5eccd..9884142e5c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexTestCase.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexTestCase.java @@ -41,14 +41,15 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +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.PackIndex.MutableEntry; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.storage.file.PackIndex.MutableEntry; public abstract class PackIndexTestCase extends RepositoryTestCase { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV1Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV1Test.java index f3082fb294..303eeff72d 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV1Test.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV1Test.java @@ -43,11 +43,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +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 { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV2Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV2Test.java index c5669f9d24..2d3ec7b729 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV2Test.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV2Test.java @@ -43,11 +43,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +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 { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackReverseIndexTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackReverseIndexTest.java index 19b705813f..07a40a425f 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackReverseIndexTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackReverseIndexTest.java @@ -42,10 +42,11 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.lib.PackIndex.MutableEntry; +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 { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java index 76b663a29b..9e663d7b4c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackWriterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java @@ -41,7 +41,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -59,9 +59,14 @@ 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.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.SampleDataRepositoryTestCase; +import org.eclipse.jgit.lib.TextProgressMonitor; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.PackIndex.MutableEntry; +import org.eclipse.jgit.storage.pack.PackWriter; import org.eclipse.jgit.transport.IndexPack; import org.eclipse.jgit.util.JGitTestUtil; @@ -91,7 +96,7 @@ public class PackWriterTest extends SampleDataRepositoryTestCase { 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()); + writer = new PackWriter(db); } /** @@ -238,7 +243,7 @@ public class PackWriterTest extends SampleDataRepositoryTestCase { * @throws IOException */ public void testWritePack2DeltasCRC32Copy() throws IOException { - final File packDir = new File(db.getObjectsDirectory(), "pack"); + final File packDir = new File(db.getObjectDatabase().getDirectory(), "pack"); final File crc32Pack = new File(packDir, "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.pack"); final File crc32Idx = new File(packDir, @@ -476,17 +481,21 @@ public class PackWriterTest extends SampleDataRepositoryTestCase { final Collection<ObjectId> uninterestings, final boolean thin, final boolean ignoreMissingUninteresting) throws MissingObjectException, IOException { + NullProgressMonitor m = NullProgressMonitor.INSTANCE; writer.setThin(thin); writer.setIgnoreMissingUninteresting(ignoreMissingUninteresting); - writer.preparePack(interestings, uninterestings); - writer.writePack(os); + writer.preparePack(m, interestings, uninterestings); + writer.writePack(m, m, os); + writer.release(); verifyOpenPack(thin); } private void createVerifyOpenPack(final Iterator<RevObject> objectSource) throws MissingObjectException, IOException { + NullProgressMonitor m = NullProgressMonitor.INSTANCE; writer.preparePack(objectSource); - writer.writePack(os); + writer.writePack(m, m, os); + writer.release(); verifyOpenPack(false); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefDirectoryTest.java index a2812901bc..6e98541603 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefDirectoryTest.java @@ -41,7 +41,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import static org.eclipse.jgit.lib.Constants.HEAD; import static org.eclipse.jgit.lib.Constants.R_HEADS; @@ -55,6 +55,10 @@ 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; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefUpdateTest.java index 8a9bb52633..875c2e96f5 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefUpdateTest.java @@ -43,7 +43,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.File; import java.io.IOException; @@ -51,9 +51,22 @@ 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 { @@ -103,14 +116,14 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase { assertNotSame(newid, r.getObjectId()); assertSame(ObjectId.class, r.getObjectId().getClass()); assertEquals(newid.copy(), r.getObjectId()); - List<org.eclipse.jgit.lib.ReflogReader.Entry> reverseEntries1 = db.getReflogReader("refs/heads/abc").getReverseEntries(); - org.eclipse.jgit.lib.ReflogReader.Entry entry1 = reverseEntries1.get(0); + List<org.eclipse.jgit.storage.file.ReflogReader.Entry> 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<org.eclipse.jgit.lib.ReflogReader.Entry> reverseEntries2 = db.getReflogReader("HEAD").getReverseEntries(); + List<org.eclipse.jgit.storage.file.ReflogReader.Entry> reverseEntries2 = db.getReflogReader("HEAD").getReverseEntries(); assertEquals(0, reverseEntries2.size()); } @@ -326,7 +339,7 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase { // 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); + 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()); @@ -355,7 +368,7 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase { // 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); + 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()); @@ -664,7 +677,7 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase { // Create new Repository instance, to reread caches and make sure our // assumptions are persistent. - Repository ndb = new Repository(db.getDirectory()); + Repository ndb = new FileRepository(db.getDirectory()); assertEquals(rb2, ndb.resolve("refs/heads/new/name")); assertNull(ndb.resolve("refs/heads/b")); } @@ -677,9 +690,9 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase { ObjectId oldHeadId = db.resolve(Constants.HEAD); writeReflog(db, oldfromId, oldfromId, "Just a message", fromName); - List<org.eclipse.jgit.lib.ReflogReader.Entry> oldFromLog = db + List<org.eclipse.jgit.storage.file.ReflogReader.Entry> oldFromLog = db .getReflogReader(fromName).getReverseEntries(); - List<org.eclipse.jgit.lib.ReflogReader.Entry> oldHeadLog = oldHeadId != null ? db + List<org.eclipse.jgit.storage.file.ReflogReader.Entry> oldHeadLog = oldHeadId != null ? db .getReflogReader(Constants.HEAD).getReverseEntries() : null; assertTrue("internal check, we have a log", new File(db.getDirectory(), diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogReaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ReflogReaderTest.java index 6144851fcd..1d268a4740 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogReaderTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ReflogReaderTest.java @@ -42,7 +42,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.File; import java.io.FileNotFoundException; @@ -51,7 +51,10 @@ import java.io.IOException; import java.text.SimpleDateFormat; import java.util.List; -import org.eclipse.jgit.lib.ReflogReader.Entry; +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 { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java index 6e5e0054b8..4f6d5b3bd6 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java @@ -42,12 +42,17 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.File; import java.io.IOException; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.errors.NoWorkTreeException; 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 @@ -57,12 +62,12 @@ public class RepositorySetupWorkDirTest extends LocalDiskRepositoryTestCase { public void testIsBare_CreateRepositoryFromArbitraryGitDir() throws Exception { File gitDir = getFile("workdir"); - assertTrue(new Repository(gitDir).isBare()); + assertTrue(new FileRepository(gitDir).isBare()); } public void testNotBare_CreateRepositoryFromDotGitGitDir() throws Exception { File gitDir = getFile("workdir", Constants.DOT_GIT); - Repository repo = new Repository(gitDir); + Repository repo = new FileRepository(gitDir); assertFalse(repo.isBare()); assertWorkdirPath(repo, "workdir"); assertGitdirPath(repo, "workdir", Constants.DOT_GIT); @@ -71,14 +76,14 @@ public class RepositorySetupWorkDirTest extends LocalDiskRepositoryTestCase { public void testWorkdirIsParentDir_CreateRepositoryFromDotGitGitDir() throws Exception { File gitDir = getFile("workdir", Constants.DOT_GIT); - Repository repo = new Repository(gitDir); - String workdir = repo.getWorkDir().getName(); + Repository repo = new FileRepository(gitDir); + String workdir = repo.getWorkTree().getName(); assertEquals(workdir, "workdir"); } public void testNotBare_CreateRepositoryFromWorkDirOnly() throws Exception { File workdir = getFile("workdir", "repo"); - Repository repo = new Repository(null, workdir); + FileRepository repo = new FileRepositoryBuilder().setWorkTree(workdir).build(); assertFalse(repo.isBare()); assertWorkdirPath(repo, "workdir", "repo"); assertGitdirPath(repo, "workdir", "repo", Constants.DOT_GIT); @@ -87,7 +92,7 @@ public class RepositorySetupWorkDirTest extends LocalDiskRepositoryTestCase { public void testWorkdirIsDotGit_CreateRepositoryFromWorkDirOnly() throws Exception { File workdir = getFile("workdir", "repo"); - Repository repo = new Repository(null, workdir); + FileRepository repo = new FileRepositoryBuilder().setWorkTree(workdir).build(); assertGitdirPath(repo, "workdir", "repo", Constants.DOT_GIT); } @@ -96,7 +101,7 @@ public class RepositorySetupWorkDirTest extends LocalDiskRepositoryTestCase { File gitDir = getFile("workdir", "repoWithConfig"); File workTree = getFile("workdir", "treeRoot"); setWorkTree(gitDir, workTree); - Repository repo = new Repository(gitDir, null); + FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); assertFalse(repo.isBare()); assertWorkdirPath(repo, "workdir", "treeRoot"); assertGitdirPath(repo, "workdir", "repoWithConfig"); @@ -106,7 +111,7 @@ public class RepositorySetupWorkDirTest extends LocalDiskRepositoryTestCase { throws Exception { File gitDir = getFile("workdir", "repoWithConfig"); setBare(gitDir, true); - Repository repo = new Repository(gitDir, null); + FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); assertTrue(repo.isBare()); } @@ -114,7 +119,7 @@ public class RepositorySetupWorkDirTest extends LocalDiskRepositoryTestCase { throws Exception { File gitDir = getFile("workdir", "repoWithBareConfigTrue", "child"); setBare(gitDir, false); - Repository repo = new Repository(gitDir, null); + FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); assertWorkdirPath(repo, "workdir", "repoWithBareConfigTrue"); } @@ -122,27 +127,18 @@ public class RepositorySetupWorkDirTest extends LocalDiskRepositoryTestCase { throws Exception { File gitDir = getFile("workdir", "repoWithBareConfigFalse", "child"); setBare(gitDir, false); - Repository repo = new Repository(gitDir, null); + FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); assertFalse(repo.isBare()); assertWorkdirPath(repo, "workdir", "repoWithBareConfigFalse"); assertGitdirPath(repo, "workdir", "repoWithBareConfigFalse", "child"); } - public void testNotBare_MakeBareUnbareBySetWorkdir() throws Exception { - File gitDir = getFile("gitDir"); - Repository repo = new Repository(gitDir); - repo.setWorkDir(getFile("workingDir")); - assertFalse(repo.isBare()); - assertWorkdirPath(repo, "workingDir"); - assertGitdirPath(repo, "gitDir"); - } - public void testExceptionThrown_BareRepoGetWorkDir() throws Exception { File gitDir = getFile("workdir"); try { - new Repository(gitDir).getWorkDir(); - fail("Expected IllegalStateException missing"); - } catch (IllegalStateException e) { + new FileRepository(gitDir).getWorkTree(); + fail("Expected NoWorkTreeException missing"); + } catch (NoWorkTreeException e) { // expected } } @@ -150,9 +146,9 @@ public class RepositorySetupWorkDirTest extends LocalDiskRepositoryTestCase { public void testExceptionThrown_BareRepoGetIndex() throws Exception { File gitDir = getFile("workdir"); try { - new Repository(gitDir).getIndex(); - fail("Expected IllegalStateException missing"); - } catch (IllegalStateException e) { + new FileRepository(gitDir).getIndex(); + fail("Expected NoWorkTreeException missing"); + } catch (NoWorkTreeException e) { // expected } } @@ -160,9 +156,9 @@ public class RepositorySetupWorkDirTest extends LocalDiskRepositoryTestCase { public void testExceptionThrown_BareRepoGetIndexFile() throws Exception { File gitDir = getFile("workdir"); try { - new Repository(gitDir).getIndexFile(); - fail("Expected Exception missing"); - } catch (IllegalStateException e) { + new FileRepository(gitDir).getIndexFile(); + fail("Expected NoWorkTreeException missing"); + } catch (NoWorkTreeException e) { // expected } } @@ -176,20 +172,28 @@ public class RepositorySetupWorkDirTest extends LocalDiskRepositoryTestCase { return result; } - private void setBare(File gitDir, boolean bare) throws IOException { - Repository repo = new Repository(gitDir, null); - repo.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + 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); - repo.getConfig().save(); + 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 void setWorkTree(File gitDir, File workTree) throws IOException { - Repository repo = new Repository(gitDir, null); - repo.getConfig() - .setString(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_WORKTREE, - workTree.getAbsolutePath()); - repo.getConfig().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) @@ -202,7 +206,7 @@ public class RepositorySetupWorkDirTest extends LocalDiskRepositoryTestCase { private void assertWorkdirPath(Repository repo, String... expected) throws IOException { File exp = getFile(expected).getCanonicalFile(); - File act = repo.getWorkDir().getCanonicalFile(); + File act = repo.getWorkTree().getCanonicalFile(); assertEquals("Wrong working Directory", exp, act); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0003_Basic.java index ce8a79ef96..477b0dfb50 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0003_Basic.java @@ -43,7 +43,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.ByteArrayInputStream; import java.io.File; @@ -53,7 +53,23 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; +import org.eclipse.jgit.JGitText; 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() { @@ -80,11 +96,11 @@ public class T0003_Basic extends SampleDataRepositoryTestCase { public void test000_openRepoBadArgs() throws IOException { try { - new Repository(null, null); + 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", + JGitText.get().eitherGitDirOrWorkTreeRequired, e.getMessage()); } } @@ -97,16 +113,16 @@ public class T0003_Basic extends SampleDataRepositoryTestCase { */ public void test000_openrepo_default_gitDirSet() throws IOException { File repo1Parent = new File(trash.getParentFile(), "r1"); - Repository repo1initial = new Repository(new File(repo1Parent, Constants.DOT_GIT)); + Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); repo1initial.create(); repo1initial.close(); File theDir = new File(repo1Parent, Constants.DOT_GIT); - Repository r = new Repository(theDir, null); + FileRepository r = new FileRepositoryBuilder().setGitDir(theDir).build(); assertEqualsPath(theDir, r.getDirectory()); - assertEqualsPath(repo1Parent, r.getWorkDir()); + assertEqualsPath(repo1Parent, r.getWorkTree()); assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); - assertEqualsPath(new File(theDir, "objects"), r.getObjectsDirectory()); + assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory()); } /** @@ -117,16 +133,17 @@ public class T0003_Basic extends SampleDataRepositoryTestCase { */ public void test000_openrepo_default_gitDirAndWorkTreeSet() throws IOException { File repo1Parent = new File(trash.getParentFile(), "r1"); - Repository repo1initial = new Repository(new File(repo1Parent, Constants.DOT_GIT)); + Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); repo1initial.create(); repo1initial.close(); File theDir = new File(repo1Parent, Constants.DOT_GIT); - Repository r = new Repository(theDir, repo1Parent.getParentFile()); + FileRepository r = new FileRepositoryBuilder().setGitDir(theDir) + .setWorkTree(repo1Parent.getParentFile()).build(); assertEqualsPath(theDir, r.getDirectory()); - assertEqualsPath(repo1Parent.getParentFile(), r.getWorkDir()); + assertEqualsPath(repo1Parent.getParentFile(), r.getWorkTree()); assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); - assertEqualsPath(new File(theDir, "objects"), r.getObjectsDirectory()); + assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory()); } /** @@ -137,16 +154,16 @@ public class T0003_Basic extends SampleDataRepositoryTestCase { */ public void test000_openrepo_default_workDirSet() throws IOException { File repo1Parent = new File(trash.getParentFile(), "r1"); - Repository repo1initial = new Repository(new File(repo1Parent, Constants.DOT_GIT)); + Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); repo1initial.create(); repo1initial.close(); File theDir = new File(repo1Parent, Constants.DOT_GIT); - Repository r = new Repository(null, repo1Parent); + FileRepository r = new FileRepositoryBuilder().setWorkTree(repo1Parent).build(); assertEqualsPath(theDir, r.getDirectory()); - assertEqualsPath(repo1Parent, r.getWorkDir()); + assertEqualsPath(repo1Parent, r.getWorkTree()); assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); - assertEqualsPath(new File(theDir, "objects"), r.getObjectsDirectory()); + assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory()); } /** @@ -159,7 +176,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 Repository(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()); @@ -167,11 +184,11 @@ public class T0003_Basic extends SampleDataRepositoryTestCase { repo1initial.close(); File theDir = new File(repo1Parent, Constants.DOT_GIT); - Repository r = new Repository(theDir, null); + FileRepository r = new FileRepositoryBuilder().setGitDir(theDir).build(); assertEqualsPath(theDir, r.getDirectory()); - assertEqualsPath(workdir, r.getWorkDir()); + assertEqualsPath(workdir, r.getWorkTree()); assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); - assertEqualsPath(new File(theDir, "objects"), r.getObjectsDirectory()); + assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory()); } /** @@ -184,7 +201,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 Repository(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"); @@ -192,11 +209,11 @@ public class T0003_Basic extends SampleDataRepositoryTestCase { repo1initial.close(); File theDir = new File(repo1Parent, Constants.DOT_GIT); - Repository r = new Repository(theDir, null); + FileRepository r = new FileRepositoryBuilder().setGitDir(theDir).build(); assertEqualsPath(theDir, r.getDirectory()); - assertEqualsPath(workdir, r.getWorkDir()); + assertEqualsPath(workdir, r.getWorkTree()); assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); - assertEqualsPath(new File(theDir, "objects"), r.getObjectsDirectory()); + assertEqualsPath(new File(theDir, "objects"), r.getObjectDatabase().getDirectory()); } /** @@ -210,18 +227,21 @@ public class T0003_Basic extends SampleDataRepositoryTestCase { File repo1Parent = new File(trash.getParentFile(), "r1"); File indexFile = new File(trash, "idx"); File objDir = new File(trash, "../obj"); - File[] altObjDirs = new File[] { db.getObjectsDirectory() }; - Repository repo1initial = new Repository(new File(repo1Parent, Constants.DOT_GIT)); + 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); - Repository r = new Repository(theDir, null, objDir, altObjDirs, - indexFile); + FileRepository r = new FileRepositoryBuilder() // + .setGitDir(theDir).setObjectDirectory(objDir) // + .addAlternateObjectDirectory(altObjDir) // + .setIndexFile(indexFile) // + .build(); assertEqualsPath(theDir, r.getDirectory()); - assertEqualsPath(theDir.getParentFile(), r.getWorkDir()); + assertEqualsPath(theDir.getParentFile(), r.getWorkTree()); assertEqualsPath(indexFile, r.getIndexFile()); - assertEqualsPath(objDir, r.getObjectsDirectory()); + 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. @@ -283,7 +303,7 @@ public class T0003_Basic extends SampleDataRepositoryTestCase { } public void test005_ReadSimpleConfig() { - final RepositoryConfig c = db.getConfig(); + final Config c = db.getConfig(); assertNotNull(c); assertEquals("0", c.getString("core", null, "repositoryformatversion")); assertEquals("0", c.getString("CoRe", null, "REPOSITORYFoRmAtVeRsIoN")); @@ -294,8 +314,8 @@ public class T0003_Basic extends SampleDataRepositoryTestCase { public void test006_ReadUglyConfig() throws IOException, ConfigInvalidException { - final RepositoryConfig c = db.getConfig(); 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" @@ -321,9 +341,9 @@ public class T0003_Basic extends SampleDataRepositoryTestCase { } public void test007_Open() throws IOException { - final Repository db2 = new Repository(db.getDirectory()); + final FileRepository db2 = new FileRepository(db.getDirectory()); assertEquals(db.getDirectory(), db2.getDirectory()); - assertEquals(db.getObjectsDirectory(), db2.getObjectsDirectory()); + assertEquals(db.getObjectDatabase().getDirectory(), db2.getObjectDatabase().getDirectory()); assertNotSame(db.getConfig(), db2.getConfig()); } @@ -337,7 +357,7 @@ public class T0003_Basic extends SampleDataRepositoryTestCase { pw.close(); try { - new Repository(db.getDirectory()); + new FileRepository(db.getDirectory()); fail("incorrectly opened a bad repository"); } catch (IOException ioe) { assertTrue(ioe.getMessage().indexOf("format") > 0); @@ -345,11 +365,7 @@ public class T0003_Basic extends SampleDataRepositoryTestCase { } } - public void test009_CreateCommitOldFormat() throws IOException, - ConfigInvalidException { - writeTrashFile(".git/config", "[core]\n" + "legacyHeaders=1\n"); - db.getConfig().load(); - + 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"); @@ -369,8 +385,10 @@ public class T0003_Basic extends SampleDataRepositoryTestCase { assertEquals(cmtid, c.getCommitId()); // Verify the commit we just wrote is in the correct format. - final XInputStream xis = new XInputStream(new FileInputStream(db - .toFile(cmtid))); + 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()); @@ -724,10 +742,10 @@ public class T0003_Basic extends SampleDataRepositoryTestCase { assertEquals("", Repository.stripWorkDir(relBase, relNonFile)); assertEquals("", Repository.stripWorkDir(absBase, absNonFile)); - assertEquals("", Repository.stripWorkDir(db.getWorkDir(), db.getWorkDir())); + assertEquals("", Repository.stripWorkDir(db.getWorkTree(), db.getWorkTree())); - File file = new File(new File(db.getWorkDir(), "subdir"), "File.java"); - assertEquals("subdir/File.java", Repository.stripWorkDir(db.getWorkDir(), file)); + File file = new File(new File(db.getWorkTree(), "subdir"), "File.java"); + assertEquals("subdir/File.java", Repository.stripWorkDir(db.getWorkTree(), file)); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java index 336bba22ce..472d6956e3 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java @@ -44,11 +44,15 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +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 { @@ -59,15 +63,14 @@ public class T0004_PackReader extends SampleDataRepositoryTestCase { public void test003_lookupCompressedObject() throws IOException { final PackFile pr; final ObjectId id; - final PackedObjectLoader or; + final ObjectLoader or; id = ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"); pr = new PackFile(TEST_IDX, TEST_PACK); - or = pr.get(new WindowCursor(), id); + 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(); } @@ -76,11 +79,9 @@ public class T0004_PackReader extends SampleDataRepositoryTestCase { final ObjectLoader or; id = ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"); - or = db.openObject(id); + or = db.open(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/UnpackedObjectTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/UnpackedObjectTest.java new file mode 100644 index 0000000000..25dfe4c239 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/UnpackedObjectTest.java @@ -0,0 +1,534 @@ +/* + * 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.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.zip.DeflaterOutputStream; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.LargeObjectException; +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.junit.TestRng; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectStream; +import org.eclipse.jgit.util.IO; + +public class UnpackedObjectTest extends LocalDiskRepositoryTestCase { + private TestRng rng; + + private FileRepository repo; + + private WindowCursor wc; + + protected void setUp() throws Exception { + super.setUp(); + rng = new TestRng(getName()); + repo = createBareRepository(); + wc = (WindowCursor) repo.newObjectReader(); + } + + protected void tearDown() throws Exception { + if (wc != null) + wc.release(); + super.tearDown(); + } + + public void testStandardFormat_SmallObject() throws Exception { + final int type = Constants.OBJ_BLOB; + byte[] data = rng.nextBytes(300); + byte[] gz = compressStandardFormat(type, data); + ObjectId id = ObjectId.zeroId(); + + ObjectLoader ol = UnpackedObject.open(new ByteArrayInputStream(gz), + path(id), id, wc); + assertNotNull("created loader", ol); + assertEquals(type, ol.getType()); + assertEquals(data.length, ol.getSize()); + assertFalse("is not large", ol.isLarge()); + assertTrue("same content", Arrays.equals(data, ol.getCachedBytes())); + + ObjectStream in = ol.openStream(); + assertNotNull("have stream", in); + assertEquals(type, in.getType()); + assertEquals(data.length, in.getSize()); + byte[] data2 = new byte[data.length]; + IO.readFully(in, data2, 0, data.length); + assertTrue("same content", Arrays.equals(data2, data)); + assertEquals("stream at EOF", -1, in.read()); + in.close(); + } + + public void testStandardFormat_LargeObject() throws Exception { + final int type = Constants.OBJ_BLOB; + byte[] data = rng.nextBytes(ObjectLoader.STREAM_THRESHOLD + 5); + ObjectId id = new ObjectInserter.Formatter().idFor(type, data); + write(id, compressStandardFormat(type, data)); + + ObjectLoader ol; + { + FileInputStream fs = new FileInputStream(path(id)); + try { + ol = UnpackedObject.open(fs, path(id), id, wc); + } finally { + fs.close(); + } + } + + assertNotNull("created loader", ol); + assertEquals(type, ol.getType()); + assertEquals(data.length, ol.getSize()); + assertTrue("is large", ol.isLarge()); + try { + ol.getCachedBytes(); + fail("Should have thrown LargeObjectException"); + } catch (LargeObjectException tooBig) { + assertEquals(id.name(), tooBig.getMessage()); + } + + ObjectStream in = ol.openStream(); + assertNotNull("have stream", in); + assertEquals(type, in.getType()); + assertEquals(data.length, in.getSize()); + byte[] data2 = new byte[data.length]; + IO.readFully(in, data2, 0, data.length); + assertTrue("same content", Arrays.equals(data2, data)); + assertEquals("stream at EOF", -1, in.read()); + in.close(); + } + + public void testStandardFormat_NegativeSize() throws Exception { + ObjectId id = ObjectId.zeroId(); + byte[] data = rng.nextBytes(300); + + try { + byte[] gz = compressStandardFormat("blob", "-1", data); + UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc); + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException coe) { + assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt, + id.name(), JGitText.get().corruptObjectNegativeSize), coe + .getMessage()); + } + } + + public void testStandardFormat_InvalidType() throws Exception { + ObjectId id = ObjectId.zeroId(); + byte[] data = rng.nextBytes(300); + + try { + byte[] gz = compressStandardFormat("not.a.type", "1", data); + UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc); + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException coe) { + assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt, + id.name(), JGitText.get().corruptObjectInvalidType), coe + .getMessage()); + } + } + + public void testStandardFormat_NoHeader() throws Exception { + ObjectId id = ObjectId.zeroId(); + byte[] data = {}; + + try { + byte[] gz = compressStandardFormat("", "", data); + UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc); + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException coe) { + assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt, + id.name(), JGitText.get().corruptObjectNoHeader), coe + .getMessage()); + } + } + + public void testStandardFormat_GarbageAfterSize() throws Exception { + ObjectId id = ObjectId.zeroId(); + byte[] data = rng.nextBytes(300); + + try { + byte[] gz = compressStandardFormat("blob", "1foo", data); + UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc); + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException coe) { + assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt, + id.name(), JGitText.get().corruptObjectGarbageAfterSize), + coe.getMessage()); + } + } + + public void testStandardFormat_SmallObject_CorruptZLibStream() + throws Exception { + ObjectId id = ObjectId.zeroId(); + byte[] data = rng.nextBytes(300); + + try { + byte[] gz = compressStandardFormat(Constants.OBJ_BLOB, data); + for (int i = 5; i < gz.length; i++) + gz[i] = 0; + UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc); + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException coe) { + assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt, + id.name(), JGitText.get().corruptObjectBadStream), coe + .getMessage()); + } + } + + public void testStandardFormat_SmallObject_TruncatedZLibStream() + throws Exception { + ObjectId id = ObjectId.zeroId(); + byte[] data = rng.nextBytes(300); + + try { + byte[] gz = compressStandardFormat(Constants.OBJ_BLOB, data); + byte[] tr = new byte[gz.length - 1]; + System.arraycopy(gz, 0, tr, 0, tr.length); + UnpackedObject.open(new ByteArrayInputStream(tr), path(id), id, wc); + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException coe) { + assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt, + id.name(), JGitText.get().corruptObjectBadStream), coe + .getMessage()); + } + } + + public void testStandardFormat_SmallObject_TrailingGarbage() + throws Exception { + ObjectId id = ObjectId.zeroId(); + byte[] data = rng.nextBytes(300); + + try { + byte[] gz = compressStandardFormat(Constants.OBJ_BLOB, data); + byte[] tr = new byte[gz.length + 1]; + System.arraycopy(gz, 0, tr, 0, gz.length); + UnpackedObject.open(new ByteArrayInputStream(tr), path(id), id, wc); + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException coe) { + assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt, + id.name(), JGitText.get().corruptObjectBadStream), coe + .getMessage()); + } + } + + public void testStandardFormat_LargeObject_CorruptZLibStream() + throws Exception { + final int type = Constants.OBJ_BLOB; + byte[] data = rng.nextBytes(ObjectLoader.STREAM_THRESHOLD + 5); + ObjectId id = new ObjectInserter.Formatter().idFor(type, data); + byte[] gz = compressStandardFormat(type, data); + gz[gz.length - 1] = 0; + gz[gz.length - 2] = 0; + + write(id, gz); + + ObjectLoader ol; + { + FileInputStream fs = new FileInputStream(path(id)); + try { + ol = UnpackedObject.open(fs, path(id), id, wc); + } finally { + fs.close(); + } + } + + try { + byte[] tmp = new byte[data.length]; + InputStream in = ol.openStream(); + try { + IO.readFully(in, tmp, 0, tmp.length); + } finally { + in.close(); + } + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException coe) { + assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt, + id.name(), JGitText.get().corruptObjectBadStream), coe + .getMessage()); + } + } + + public void testStandardFormat_LargeObject_TruncatedZLibStream() + throws Exception { + final int type = Constants.OBJ_BLOB; + byte[] data = rng.nextBytes(ObjectLoader.STREAM_THRESHOLD + 5); + ObjectId id = new ObjectInserter.Formatter().idFor(type, data); + byte[] gz = compressStandardFormat(type, data); + byte[] tr = new byte[gz.length - 1]; + System.arraycopy(gz, 0, tr, 0, tr.length); + + write(id, tr); + + ObjectLoader ol; + { + FileInputStream fs = new FileInputStream(path(id)); + try { + ol = UnpackedObject.open(fs, path(id), id, wc); + } finally { + fs.close(); + } + } + + byte[] tmp = new byte[data.length]; + InputStream in = ol.openStream(); + IO.readFully(in, tmp, 0, tmp.length); + try { + in.close(); + fail("close did not throw CorruptObjectException"); + } catch (CorruptObjectException coe) { + assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt, + id.name(), JGitText.get().corruptObjectBadStream), coe + .getMessage()); + } + } + + public void testStandardFormat_LargeObject_TrailingGarbage() + throws Exception { + final int type = Constants.OBJ_BLOB; + byte[] data = rng.nextBytes(ObjectLoader.STREAM_THRESHOLD + 5); + ObjectId id = new ObjectInserter.Formatter().idFor(type, data); + byte[] gz = compressStandardFormat(type, data); + byte[] tr = new byte[gz.length + 1]; + System.arraycopy(gz, 0, tr, 0, gz.length); + + write(id, tr); + + ObjectLoader ol; + { + FileInputStream fs = new FileInputStream(path(id)); + try { + ol = UnpackedObject.open(fs, path(id), id, wc); + } finally { + fs.close(); + } + } + + byte[] tmp = new byte[data.length]; + InputStream in = ol.openStream(); + IO.readFully(in, tmp, 0, tmp.length); + try { + in.close(); + fail("close did not throw CorruptObjectException"); + } catch (CorruptObjectException coe) { + assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt, + id.name(), JGitText.get().corruptObjectBadStream), coe + .getMessage()); + } + } + + public void testPackFormat_SmallObject() throws Exception { + final int type = Constants.OBJ_BLOB; + byte[] data = rng.nextBytes(300); + byte[] gz = compressPackFormat(type, data); + ObjectId id = ObjectId.zeroId(); + + ObjectLoader ol = UnpackedObject.open(new ByteArrayInputStream(gz), + path(id), id, wc); + assertNotNull("created loader", ol); + assertEquals(type, ol.getType()); + assertEquals(data.length, ol.getSize()); + assertFalse("is not large", ol.isLarge()); + assertTrue("same content", Arrays.equals(data, ol.getCachedBytes())); + + ObjectStream in = ol.openStream(); + assertNotNull("have stream", in); + assertEquals(type, in.getType()); + assertEquals(data.length, in.getSize()); + byte[] data2 = new byte[data.length]; + IO.readFully(in, data2, 0, data.length); + assertTrue("same content", Arrays.equals(data, ol.getCachedBytes())); + in.close(); + } + + public void testPackFormat_LargeObject() throws Exception { + final int type = Constants.OBJ_BLOB; + byte[] data = rng.nextBytes(ObjectLoader.STREAM_THRESHOLD + 5); + ObjectId id = new ObjectInserter.Formatter().idFor(type, data); + write(id, compressPackFormat(type, data)); + + ObjectLoader ol; + { + FileInputStream fs = new FileInputStream(path(id)); + try { + ol = UnpackedObject.open(fs, path(id), id, wc); + } finally { + fs.close(); + } + } + + assertNotNull("created loader", ol); + assertEquals(type, ol.getType()); + assertEquals(data.length, ol.getSize()); + assertTrue("is large", ol.isLarge()); + try { + ol.getCachedBytes(); + fail("Should have thrown LargeObjectException"); + } catch (LargeObjectException tooBig) { + assertEquals(id.name(), tooBig.getMessage()); + } + + ObjectStream in = ol.openStream(); + assertNotNull("have stream", in); + assertEquals(type, in.getType()); + assertEquals(data.length, in.getSize()); + byte[] data2 = new byte[data.length]; + IO.readFully(in, data2, 0, data.length); + assertTrue("same content", Arrays.equals(data2, data)); + assertEquals("stream at EOF", -1, in.read()); + in.close(); + } + + public void testPackFormat_DeltaNotAllowed() throws Exception { + ObjectId id = ObjectId.zeroId(); + byte[] data = rng.nextBytes(300); + + try { + byte[] gz = compressPackFormat(Constants.OBJ_OFS_DELTA, data); + UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc); + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException coe) { + assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt, + id.name(), JGitText.get().corruptObjectInvalidType), coe + .getMessage()); + } + + try { + byte[] gz = compressPackFormat(Constants.OBJ_REF_DELTA, data); + UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc); + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException coe) { + assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt, + id.name(), JGitText.get().corruptObjectInvalidType), coe + .getMessage()); + } + + try { + byte[] gz = compressPackFormat(Constants.OBJ_TYPE_5, data); + UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc); + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException coe) { + assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt, + id.name(), JGitText.get().corruptObjectInvalidType), coe + .getMessage()); + } + + try { + byte[] gz = compressPackFormat(Constants.OBJ_EXT, data); + UnpackedObject.open(new ByteArrayInputStream(gz), path(id), id, wc); + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException coe) { + assertEquals(MessageFormat.format(JGitText.get().objectIsCorrupt, + id.name(), JGitText.get().corruptObjectInvalidType), coe + .getMessage()); + } + } + + private byte[] compressStandardFormat(int type, byte[] data) + throws IOException { + String typeString = Constants.typeString(type); + String length = String.valueOf(data.length); + return compressStandardFormat(typeString, length, data); + } + + private byte[] compressStandardFormat(String type, String length, + byte[] data) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + DeflaterOutputStream d = new DeflaterOutputStream(out); + d.write(Constants.encodeASCII(type)); + d.write(' '); + d.write(Constants.encodeASCII(length)); + d.write(0); + d.write(data); + d.finish(); + return out.toByteArray(); + } + + private byte[] compressPackFormat(int type, byte[] data) throws IOException { + byte[] hdr = new byte[64]; + int rawLength = data.length; + int nextLength = rawLength >>> 4; + hdr[0] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (type << 4) | (rawLength & 0x0F)); + rawLength = nextLength; + int n = 1; + while (rawLength > 0) { + nextLength >>>= 7; + hdr[n++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (rawLength & 0x7F)); + rawLength = nextLength; + } + + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write(hdr, 0, n); + + DeflaterOutputStream d = new DeflaterOutputStream(out); + d.write(data); + d.finish(); + return out.toByteArray(); + } + + private File path(ObjectId id) { + return repo.getObjectDatabase().fileFor(id); + } + + private void write(ObjectId id, byte[] data) throws IOException { + File path = path(id); + path.getParentFile().mkdirs(); + FileOutputStream out = new FileOutputStream(path); + try { + out.write(data); + } finally { + out.close(); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheGetTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java index 8ff022ddc4..d8c682999c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheGetTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java @@ -41,7 +41,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.BufferedReader; import java.io.FileInputStream; @@ -51,6 +51,10 @@ 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; @@ -73,9 +77,9 @@ public class WindowCacheGetTest extends SampleDataRepositoryTestCase { final TestObject o = new TestObject(); o.id = ObjectId.fromString(parts[0]); o.setType(parts[1]); - o.rawSize = Integer.parseInt(parts[2]); + // parts[2] is the inflate size // parts[3] is the size-in-pack - o.offset = Long.parseLong(parts[4]); + // parts[4] is the offset in the pack toLoad.add(o); } } finally { @@ -122,12 +126,9 @@ public class WindowCacheGetTest extends SampleDataRepositoryTestCase { private void doCacheTests() throws IOException { for (final TestObject o : toLoad) { - final ObjectLoader or = db.openObject(o.id); + final ObjectLoader or = db.open(o.id, o.type); assertNotNull(or); - assertTrue(or instanceof PackedObjectLoader); assertEquals(o.type, or.getType()); - assertEquals(o.rawSize, or.getRawSize()); - assertEquals(o.offset, ((PackedObjectLoader) or).getObjectOffset()); } } @@ -136,10 +137,6 @@ public class WindowCacheGetTest extends SampleDataRepositoryTestCase { int type; - int rawSize; - - long offset; - void setType(final String typeStr) throws CorruptObjectException { final byte[] typeRaw = Constants.encode(typeStr + " "); final MutableInteger ptr = new MutableInteger(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheReconfigureTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheReconfigureTest.java index 9e093c85bd..e52b19d925 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheReconfigureTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheReconfigureTest.java @@ -41,7 +41,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; + +import org.eclipse.jgit.lib.RepositoryTestCase; public class WindowCacheReconfigureTest extends RepositoryTestCase { public void testConfigureCache_PackedGitLimit_0() { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/XInputStream.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/XInputStream.java index eef32b9276..9978c8e13c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/XInputStream.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/XInputStream.java @@ -41,7 +41,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.BufferedInputStream; import java.io.EOFException; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/pack/DeltaIndexTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/pack/DeltaIndexTest.java new file mode 100644 index 0000000000..868ef8825c --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/pack/DeltaIndexTest.java @@ -0,0 +1,228 @@ +/* + * 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.pack; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + +import junit.framework.TestCase; + +import org.eclipse.jgit.junit.TestRng; +import org.eclipse.jgit.lib.Constants; + +public class DeltaIndexTest extends TestCase { + private TestRng rng; + + private ByteArrayOutputStream actDeltaBuf; + + private ByteArrayOutputStream expDeltaBuf; + + private DeltaEncoder expDeltaEnc; + + private byte[] src; + + private byte[] dst; + + private ByteArrayOutputStream dstBuf; + + protected void setUp() throws Exception { + super.setUp(); + rng = new TestRng(getName()); + actDeltaBuf = new ByteArrayOutputStream(); + expDeltaBuf = new ByteArrayOutputStream(); + expDeltaEnc = new DeltaEncoder(expDeltaBuf, 0, 0); + dstBuf = new ByteArrayOutputStream(); + } + + public void testInsertWholeObject_Length12() throws IOException { + src = rng.nextBytes(12); + insert(src); + doTest(); + } + + public void testCopyWholeObject_Length128() throws IOException { + src = rng.nextBytes(128); + copy(0, 128); + doTest(); + } + + public void testCopyWholeObject_Length123() throws IOException { + src = rng.nextBytes(123); + copy(0, 123); + doTest(); + } + + public void testCopyZeros_Length128() throws IOException { + src = new byte[2048]; + copy(0, src.length); + doTest(); + + // The index should be smaller than expected due to the chain + // being truncated. Without truncation we would expect to have + // more than 3584 bytes used. + // + assertEquals(2636, new DeltaIndex(src).getIndexSize()); + } + + public void testShuffleSegments() throws IOException { + src = rng.nextBytes(128); + copy(64, 64); + copy(0, 64); + doTest(); + } + + public void testInsertHeadMiddle() throws IOException { + src = rng.nextBytes(1024); + insert("foo"); + copy(0, 512); + insert("yet more fooery"); + copy(0, 512); + doTest(); + } + + public void testInsertTail() throws IOException { + src = rng.nextBytes(1024); + copy(0, 512); + insert("bar"); + doTest(); + } + + public void testIndexSize() { + src = rng.nextBytes(1024); + DeltaIndex di = new DeltaIndex(src); + assertEquals(1860, di.getIndexSize()); + assertEquals("DeltaIndex[2 KiB]", di.toString()); + } + + public void testLimitObjectSize_Length12InsertFails() throws IOException { + src = rng.nextBytes(12); + dst = src; + + DeltaIndex di = new DeltaIndex(src); + assertFalse(di.encode(actDeltaBuf, dst, src.length)); + } + + public void testLimitObjectSize_Length130InsertFails() throws IOException { + src = rng.nextBytes(130); + dst = rng.nextBytes(130); + + DeltaIndex di = new DeltaIndex(src); + assertFalse(di.encode(actDeltaBuf, dst, src.length)); + } + + public void testLimitObjectSize_Length130CopyOk() throws IOException { + src = rng.nextBytes(130); + copy(0, 130); + dst = dstBuf.toByteArray(); + + DeltaIndex di = new DeltaIndex(src); + assertTrue(di.encode(actDeltaBuf, dst, dst.length)); + + byte[] actDelta = actDeltaBuf.toByteArray(); + byte[] expDelta = expDeltaBuf.toByteArray(); + + assertEquals(BinaryDelta.format(expDelta, false), // + BinaryDelta.format(actDelta, false)); + } + + public void testLimitObjectSize_Length130CopyFails() throws IOException { + src = rng.nextBytes(130); + copy(0, 130); + dst = dstBuf.toByteArray(); + + // The header requires 4 bytes for these objects, so a target length + // of 5 is bigger than the copy instruction and should cause an abort. + // + DeltaIndex di = new DeltaIndex(src); + assertFalse(di.encode(actDeltaBuf, dst, 5)); + assertEquals(4, actDeltaBuf.size()); + } + + public void testLimitObjectSize_InsertFrontFails() throws IOException { + src = rng.nextBytes(130); + insert("eight"); + copy(0, 130); + dst = dstBuf.toByteArray(); + + // The header requires 4 bytes for these objects, so a target length + // of 5 is bigger than the copy instruction and should cause an abort. + // + DeltaIndex di = new DeltaIndex(src); + assertFalse(di.encode(actDeltaBuf, dst, 5)); + assertEquals(4, actDeltaBuf.size()); + } + + private void copy(int offset, int len) throws IOException { + dstBuf.write(src, offset, len); + expDeltaEnc.copy(offset, len); + } + + private void insert(String text) throws IOException { + insert(Constants.encode(text)); + } + + private void insert(byte[] text) throws IOException { + dstBuf.write(text); + expDeltaEnc.insert(text); + } + + private void doTest() throws IOException { + dst = dstBuf.toByteArray(); + + DeltaIndex di = new DeltaIndex(src); + di.encode(actDeltaBuf, dst); + + byte[] actDelta = actDeltaBuf.toByteArray(); + byte[] expDelta = expDeltaBuf.toByteArray(); + + assertEquals(BinaryDelta.format(expDelta, false), // + BinaryDelta.format(actDelta, false)); + + assertTrue("delta is not empty", actDelta.length > 0); + assertEquals(src.length, BinaryDelta.getBaseSize(actDelta)); + assertEquals(dst.length, BinaryDelta.getResultSize(actDelta)); + assertTrue(Arrays.equals(dst, BinaryDelta.apply(src, actDelta))); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/pack/DeltaStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/pack/DeltaStreamTest.java new file mode 100644 index 0000000000..9b34ad5e09 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/pack/DeltaStreamTest.java @@ -0,0 +1,273 @@ +/* + * 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.pack; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +import junit.framework.TestCase; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.junit.TestRng; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.util.IO; + +public class DeltaStreamTest extends TestCase { + private TestRng rng; + + private ByteArrayOutputStream deltaBuf; + + private DeltaEncoder deltaEnc; + + private byte[] base; + + private byte[] data; + + private int dataPtr; + + private byte[] delta; + + protected void setUp() throws Exception { + super.setUp(); + rng = new TestRng(getName()); + deltaBuf = new ByteArrayOutputStream(); + } + + public void testCopy_SingleOp() throws IOException { + init((1 << 16) + 1, (1 << 8) + 1); + copy(0, data.length); + assertValidState(); + } + + public void testCopy_MaxSize() throws IOException { + int max = (0xff << 16) + (0xff << 8) + 0xff; + init(1 + max, max); + copy(1, max); + assertValidState(); + } + + public void testCopy_64k() throws IOException { + init(0x10000 + 2, 0x10000 + 1); + copy(1, 0x10000); + copy(0x10001, 1); + assertValidState(); + } + + public void testCopy_Gap() throws IOException { + init(256, 8); + copy(4, 4); + copy(128, 4); + assertValidState(); + } + + public void testCopy_OutOfOrder() throws IOException { + init((1 << 16) + 1, (1 << 16) + 1); + copy(1 << 8, 1 << 8); + copy(0, data.length - dataPtr); + assertValidState(); + } + + public void testInsert_SingleOp() throws IOException { + init((1 << 16) + 1, 2); + insert("hi"); + assertValidState(); + } + + public void testInsertAndCopy() throws IOException { + init(8, 512); + insert(new byte[127]); + insert(new byte[127]); + insert(new byte[127]); + insert(new byte[125]); + copy(2, 6); + assertValidState(); + } + + public void testSkip() throws IOException { + init(32, 15); + copy(2, 2); + insert("ab"); + insert("cd"); + copy(4, 4); + copy(0, 2); + insert("efg"); + assertValidState(); + + for (int p = 0; p < data.length; p++) { + byte[] act = new byte[data.length]; + System.arraycopy(data, 0, act, 0, p); + DeltaStream in = open(); + IO.skipFully(in, p); + assertEquals(data.length - p, in.read(act, p, data.length - p)); + assertEquals(-1, in.read()); + assertTrue("skipping " + p, Arrays.equals(data, act)); + } + + // Skip all the way to the end should still recognize EOF. + DeltaStream in = open(); + IO.skipFully(in, data.length); + assertEquals(-1, in.read()); + assertEquals(0, in.skip(1)); + + // Skip should not open the base as we move past it, but it + // will open when we need to start copying data from it. + final boolean[] opened = new boolean[1]; + in = new DeltaStream(new ByteArrayInputStream(delta)) { + @Override + protected long getBaseSize() throws IOException { + return base.length; + } + + @Override + protected InputStream openBase() throws IOException { + opened[0] = true; + return new ByteArrayInputStream(base); + } + }; + IO.skipFully(in, 7); + assertFalse("not yet open", opened[0]); + assertEquals(data[7], in.read()); + assertTrue("now open", opened[0]); + } + + public void testIncorrectBaseSize() throws IOException { + init(4, 4); + copy(0, 4); + assertValidState(); + + DeltaStream in = new DeltaStream(new ByteArrayInputStream(delta)) { + @Override + protected long getBaseSize() throws IOException { + return 128; + } + + @Override + protected InputStream openBase() throws IOException { + return new ByteArrayInputStream(base); + } + }; + try { + in.read(new byte[4]); + fail("did not throw an exception"); + } catch (CorruptObjectException e) { + assertEquals(JGitText.get().baseLengthIncorrect, e.getMessage()); + } + + in = new DeltaStream(new ByteArrayInputStream(delta)) { + @Override + protected long getBaseSize() throws IOException { + return 4; + } + + @Override + protected InputStream openBase() throws IOException { + return new ByteArrayInputStream(new byte[0]); + } + }; + try { + in.read(new byte[4]); + fail("did not throw an exception"); + } catch (CorruptObjectException e) { + assertEquals(JGitText.get().baseLengthIncorrect, e.getMessage()); + } + } + + private void init(int baseSize, int dataSize) throws IOException { + base = rng.nextBytes(baseSize); + data = new byte[dataSize]; + deltaEnc = new DeltaEncoder(deltaBuf, baseSize, dataSize); + } + + private void copy(int offset, int len) throws IOException { + System.arraycopy(base, offset, data, dataPtr, len); + deltaEnc.copy(offset, len); + assertEquals(deltaBuf.size(), deltaEnc.getSize()); + dataPtr += len; + } + + private void insert(String text) throws IOException { + insert(Constants.encode(text)); + } + + private void insert(byte[] text) throws IOException { + System.arraycopy(text, 0, data, dataPtr, text.length); + deltaEnc.insert(text); + assertEquals(deltaBuf.size(), deltaEnc.getSize()); + dataPtr += text.length; + } + + private void assertValidState() throws IOException { + assertEquals("test filled example result", data.length, dataPtr); + + delta = deltaBuf.toByteArray(); + assertEquals(base.length, BinaryDelta.getBaseSize(delta)); + assertEquals(data.length, BinaryDelta.getResultSize(delta)); + assertTrue(Arrays.equals(data, BinaryDelta.apply(base, delta))); + + byte[] act = new byte[data.length]; + DeltaStream in = open(); + assertEquals(data.length, in.getSize()); + assertEquals(data.length, in.read(act)); + assertEquals(-1, in.read()); + assertTrue(Arrays.equals(data, act)); + } + + private DeltaStream open() throws IOException { + return new DeltaStream(new ByteArrayInputStream(delta)) { + @Override + protected long getBaseSize() throws IOException { + return base.length; + } + + @Override + protected InputStream openBase() throws IOException { + return new ByteArrayInputStream(base); + } + }; + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java index 2d6aa28d5a..cc7056225b 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java @@ -148,12 +148,12 @@ public class BundleWriterTest extends SampleDataRepositoryTestCase { throws FileNotFoundException, IOException { final BundleWriter bw; - bw = new BundleWriter(db, NullProgressMonitor.INSTANCE); + bw = new BundleWriter(db); bw.include(name, ObjectId.fromString(anObjectToInclude)); if (assume != null) bw.assume(assume); final ByteArrayOutputStream out = new ByteArrayOutputStream(); - bw.writeBundle(out); + bw.writeBundle(NullProgressMonitor.INSTANCE, out); return out.toByteArray(); } 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 40c719f699..b331f9cf54 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; @@ -176,7 +176,7 @@ public class ReceivePackRefFilterTest extends LocalDiskRepositoryTestCase { // Verify the only storage of b is our packed delta above. // ObjectDirectory od = (ObjectDirectory) src.getObjectDatabase(); - assertTrue("has b", od.hasObject(b)); + assertTrue("has b", src.hasObject(b)); assertFalse("b not loose", od.fileFor(b).exists()); // Now use b but in a different commit than what is hidden. @@ -255,7 +255,7 @@ public class ReceivePackRefFilterTest extends LocalDiskRepositoryTestCase { } public void testUsingHiddenDeltaBaseFails() throws Exception { - final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64); + final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); packHeader(pack, 1); pack.write((Constants.OBJ_REF_DELTA) << 4 | 4); b.copyRawTo(pack); @@ -292,18 +292,18 @@ 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<Repository> s = new TestRepository<Repository>(src); RevCommit N = s.commit().parent(B).add("q", s.blob("b")).create(); // But don't include it in the pack. // - final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64); + final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); packHeader(pack, 2); - copy(pack, src.openObject(N)); - copy(pack,src.openObject(s.parseBody(N).getTree())); + copy(pack, src.open(N)); + copy(pack,src.open(s.parseBody(N).getTree())); digest(pack); - final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(256); + final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024); final PacketLineOut inPckLine = new PacketLineOut(inBuf); inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' ' + "refs/heads/s" + '\0' @@ -333,19 +333,19 @@ 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<Repository> s = new TestRepository<Repository>(src); RevBlob n = s.blob("n"); RevCommit N = s.commit().parent(B).add("q", n).create(); // But don't include it in the pack. // - final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64); + final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); packHeader(pack, 2); - copy(pack, src.openObject(N)); - copy(pack,src.openObject(s.parseBody(N).getTree())); + copy(pack, src.open(N)); + copy(pack,src.open(s.parseBody(N).getTree())); digest(pack); - final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(256); + final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024); final PacketLineOut inPckLine = new PacketLineOut(inBuf); inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' ' + "refs/heads/s" + '\0' @@ -373,18 +373,18 @@ public class ReceivePackRefFilterTest extends LocalDiskRepositoryTestCase { } public void testUsingUnknownTreeFails() throws Exception { - TestRepository s = new TestRepository(src); + TestRepository<Repository> s = new TestRepository<Repository>(src); RevCommit N = s.commit().parent(B).add("q", s.blob("a")).create(); RevTree t = s.parseBody(N).getTree(); // Don't include the tree in the pack. // - final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64); + final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); packHeader(pack, 1); - copy(pack, src.openObject(N)); + copy(pack, src.open(N)); digest(pack); - final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(256); + final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024); final PacketLineOut inPckLine = new PacketLineOut(inBuf); inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' ' + "refs/heads/s" + '\0' diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java index e3518251fe..a6bdd8886f 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java @@ -48,7 +48,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import org.eclipse.jgit.lib.RepositoryConfig; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.SampleDataRepositoryTestCase; public class TransportTest extends SampleDataRepositoryTestCase { @@ -59,7 +59,7 @@ public class TransportTest extends SampleDataRepositoryTestCase { @Override public void setUp() throws Exception { super.setUp(); - final RepositoryConfig config = db.getConfig(); + final Config config = db.getConfig(); remoteConfig = new RemoteConfig(config, "test"); remoteConfig.addURI(new URIish("http://everyones.loves.git/u/2")); transport = null; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java index e96445a30a..12c11482ae 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java @@ -51,7 +51,7 @@ import junit.framework.TestCase; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; -import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.ObjectReader; public class AbstractTreeIteratorTest extends TestCase { @@ -73,7 +73,7 @@ public class AbstractTreeIteratorTest extends TestCase { } @Override - public AbstractTreeIterator createSubtreeIterator(Repository repo) + public AbstractTreeIterator createSubtreeIterator(ObjectReader reader) throws IncorrectObjectTypeException, IOException { return null; } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/EmptyTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/EmptyTreeIteratorTest.java index 111264b1c9..1ea2dc6250 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/EmptyTreeIteratorTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/EmptyTreeIteratorTest.java @@ -44,6 +44,7 @@ package org.eclipse.jgit.treewalk; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.RepositoryTestCase; public class EmptyTreeIteratorTest extends RepositoryTestCase { @@ -55,7 +56,8 @@ public class EmptyTreeIteratorTest extends RepositoryTestCase { public void testCreateSubtreeIterator() throws Exception { final EmptyTreeIterator etp = new EmptyTreeIterator(); - final AbstractTreeIterator sub = etp.createSubtreeIterator(db); + final ObjectReader reader = db.newObjectReader(); + final AbstractTreeIterator sub = etp.createSubtreeIterator(reader); assertNotNull(sub); assertTrue(sub.first()); assertTrue(sub.eof()); @@ -106,7 +108,8 @@ public class EmptyTreeIteratorTest extends RepositoryTestCase { called[0] = true; } }; - parent.createSubtreeIterator(db).stopWalk(); + final ObjectReader reader = db.newObjectReader(); + parent.createSubtreeIterator(reader).stopWalk(); assertTrue(called[0]); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java index eb08e495b9..f939c90d81 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java @@ -49,6 +49,7 @@ import java.security.MessageDigest; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.RepositoryTestCase; import org.eclipse.jgit.util.RawParseUtils; @@ -124,7 +125,8 @@ public class FileTreeIteratorTest extends RepositoryTestCase { assertFalse(top.eof()); assertEquals(FileMode.TREE.getBits(), top.mode); - final AbstractTreeIterator sub = top.createSubtreeIterator(db); + final ObjectReader reader = db.newObjectReader(); + final AbstractTreeIterator sub = top.createSubtreeIterator(reader); assertTrue(sub instanceof FileTreeIterator); final FileTreeIterator subfti = (FileTreeIterator) sub; assertTrue(sub.first()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/NameConflictTreeWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/NameConflictTreeWalkTest.java index 35298b803f..675331baf4 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/NameConflictTreeWalkTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/NameConflictTreeWalkTest.java @@ -66,8 +66,8 @@ public class NameConflictTreeWalkTest extends RepositoryTestCase { private static final FileMode EXECUTABLE_FILE = FileMode.EXECUTABLE_FILE; public void testNoDF_NoGap() throws Exception { - final DirCache tree0 = DirCache.read(db); - final DirCache tree1 = DirCache.read(db); + final DirCache tree0 = db.readDirCache(); + final DirCache tree1 = db.readDirCache(); { final DirCacheBuilder b0 = tree0.builder(); final DirCacheBuilder b1 = tree1.builder(); @@ -97,8 +97,8 @@ public class NameConflictTreeWalkTest extends RepositoryTestCase { } public void testDF_NoGap() throws Exception { - final DirCache tree0 = DirCache.read(db); - final DirCache tree1 = DirCache.read(db); + final DirCache tree0 = db.readDirCache(); + final DirCache tree1 = db.readDirCache(); { final DirCacheBuilder b0 = tree0.builder(); final DirCacheBuilder b1 = tree1.builder(); @@ -128,8 +128,8 @@ public class NameConflictTreeWalkTest extends RepositoryTestCase { } public void testDF_GapByOne() throws Exception { - final DirCache tree0 = DirCache.read(db); - final DirCache tree1 = DirCache.read(db); + final DirCache tree0 = db.readDirCache(); + final DirCache tree1 = db.readDirCache(); { final DirCacheBuilder b0 = tree0.builder(); final DirCacheBuilder b1 = tree1.builder(); @@ -160,8 +160,8 @@ public class NameConflictTreeWalkTest extends RepositoryTestCase { } public void testDF_SkipsSeenSubtree() throws Exception { - final DirCache tree0 = DirCache.read(db); - final DirCache tree1 = DirCache.read(db); + final DirCache tree0 = db.readDirCache(); + final DirCache tree1 = db.readDirCache(); { final DirCacheBuilder b0 = tree0.builder(); final DirCacheBuilder b1 = tree1.builder(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java index d136b8f297..274df5bec0 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java @@ -86,7 +86,7 @@ public class PostOrderTreeWalkTest extends RepositoryTestCase { } public void testNoPostOrder() throws Exception { - final DirCache tree = DirCache.read(db); + final DirCache tree = db.readDirCache(); { final DirCacheBuilder b = tree.builder(); @@ -115,7 +115,7 @@ public class PostOrderTreeWalkTest extends RepositoryTestCase { } public void testWithPostOrder_EnterSubtree() throws Exception { - final DirCache tree = DirCache.read(db); + final DirCache tree = db.readDirCache(); { final DirCacheBuilder b = tree.builder(); @@ -150,7 +150,7 @@ public class PostOrderTreeWalkTest extends RepositoryTestCase { } public void testWithPostOrder_NoEnterSubtree() throws Exception { - final DirCache tree = DirCache.read(db); + final DirCache tree = db.readDirCache(); { final DirCacheBuilder b = tree.builder(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTestCase.java index 1aaefc415f..302eada999 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTestCase.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTestCase.java @@ -43,6 +43,8 @@ package org.eclipse.jgit.treewalk.filter; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; + import java.io.IOException; import java.util.LinkedList; import java.util.List; @@ -52,17 +54,17 @@ import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectWriter; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.RepositoryTestCase; import org.eclipse.jgit.treewalk.TreeWalk; public class PathSuffixFilterTestCase extends RepositoryTestCase { public void testNonRecursiveFiltering() throws IOException { - final ObjectWriter ow = new ObjectWriter(db); - final ObjectId aSth = ow.writeBlob("a.sth".getBytes()); - final ObjectId aTxt = ow.writeBlob("a.txt".getBytes()); - final DirCache dc = DirCache.read(db); + final ObjectInserter odi = db.newObjectInserter(); + final ObjectId aSth = odi.insert(OBJ_BLOB, "a.sth".getBytes()); + final ObjectId aTxt = odi.insert(OBJ_BLOB, "a.txt".getBytes()); + final DirCache dc = db.readDirCache(); final DirCacheBuilder builder = dc.builder(); final DirCacheEntry aSthEntry = new DirCacheEntry("a.sth"); aSthEntry.setFileMode(FileMode.REGULAR_FILE); @@ -73,7 +75,8 @@ public class PathSuffixFilterTestCase extends RepositoryTestCase { builder.add(aSthEntry); builder.add(aTxtEntry); builder.finish(); - final ObjectId treeId = dc.writeTree(ow); + final ObjectId treeId = dc.writeTree(odi); + odi.flush(); final TreeWalk tw = new TreeWalk(db); @@ -92,12 +95,12 @@ public class PathSuffixFilterTestCase extends RepositoryTestCase { } public void testRecursiveFiltering() throws IOException { - final ObjectWriter ow = new ObjectWriter(db); - final ObjectId aSth = ow.writeBlob("a.sth".getBytes()); - final ObjectId aTxt = ow.writeBlob("a.txt".getBytes()); - final ObjectId bSth = ow.writeBlob("b.sth".getBytes()); - final ObjectId bTxt = ow.writeBlob("b.txt".getBytes()); - final DirCache dc = DirCache.read(db); + final ObjectInserter odi = db.newObjectInserter(); + final ObjectId aSth = odi.insert(OBJ_BLOB, "a.sth".getBytes()); + final ObjectId aTxt = odi.insert(OBJ_BLOB, "a.txt".getBytes()); + final ObjectId bSth = odi.insert(OBJ_BLOB, "b.sth".getBytes()); + final ObjectId bTxt = odi.insert(OBJ_BLOB, "b.txt".getBytes()); + final DirCache dc = db.readDirCache(); final DirCacheBuilder builder = dc.builder(); final DirCacheEntry aSthEntry = new DirCacheEntry("a.sth"); aSthEntry.setFileMode(FileMode.REGULAR_FILE); @@ -116,7 +119,8 @@ public class PathSuffixFilterTestCase extends RepositoryTestCase { builder.add(bSthEntry); builder.add(bTxtEntry); builder.finish(); - final ObjectId treeId = dc.writeTree(ow); + final ObjectId treeId = dc.writeTree(odi); + odi.flush(); final TreeWalk tw = new TreeWalk(db); diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index 51d440e58e..a8f39d93bf 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -9,6 +9,7 @@ Export-Package: org.eclipse.jgit;version="0.9.0", org.eclipse.jgit.api;version="0.9.0", org.eclipse.jgit.diff;version="0.9.0", org.eclipse.jgit.dircache;version="0.9.0", + org.eclipse.jgit.events;version="0.9.0", org.eclipse.jgit.errors;version="0.9.0", org.eclipse.jgit.fnmatch;version="0.9.0", org.eclipse.jgit.ignore;version="0.9.0", @@ -19,6 +20,8 @@ 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.storage.pack;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/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties index ca1832e571..88c6c8e3a4 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties @@ -127,7 +127,7 @@ duplicateAdvertisementsOf=duplicate advertisements of {0} duplicateRef=Duplicate ref: {0} duplicateRemoteRefUpdateIsIllegal=Duplicate remote ref update is illegal. Affected remote name: {0} duplicateStagesNotAllowed=Duplicate stages not allowed -eitherGIT_DIRorGIT_WORK_TREEmustBePassed=Either GIT_DIR or GIT_WORK_TREE must be passed to Repository constructor +eitherGitDirOrWorkTreeRequired=One of setGitDir or setWorkTree must be called. emptyPathNotPermitted=Empty path not permitted. encryptionError=Encryption error: {0} endOfFileInEscape=End of file in escape @@ -224,6 +224,7 @@ mergeStrategyAlreadyExistsAsDefault=Merge strategy "{0}" already exists as a def mergeStrategyDoesNotSupportHeads=merge strategy {0} does not support {1} heads to be merged into HEAD mergeUsingStrategyResultedInDescription=Merge using strategy {0} resulted in: {1}. {2} missingAccesskey=Missing accesskey. +missingDeltaBase=delta base missingForwardImageInGITBinaryPatch=Missing forward-image in GIT binary patch missingObject=Missing {0} {1} missingPrerequisiteCommits=missing prerequisite commits: @@ -304,6 +305,7 @@ renamesAlreadyFound=Renames have already been found. renamesFindingByContent=Finding renames by content similarity renamesFindingExact=Finding exact renames repositoryAlreadyExists=Repository already exists: {0} +repositoryConfigFileInvalid=Repository config file {0} invalid {1} repositoryIsRequired=Repository is required. repositoryNotFound=repository not found: {0} repositoryState_applyMailbox=Apply mailbox diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java index 71e5e4a899..9df9887af4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java @@ -187,7 +187,7 @@ public class JGitText extends TranslationBundle { /***/ public String duplicateRef; /***/ public String duplicateRemoteRefUpdateIsIllegal; /***/ public String duplicateStagesNotAllowed; - /***/ public String eitherGIT_DIRorGIT_WORK_TREEmustBePassed; + /***/ public String eitherGitDirOrWorkTreeRequired; /***/ public String emptyPathNotPermitted; /***/ public String encryptionError; /***/ public String endOfFileInEscape; @@ -284,6 +284,7 @@ public class JGitText extends TranslationBundle { /***/ public String mergeStrategyDoesNotSupportHeads; /***/ public String mergeUsingStrategyResultedInDescription; /***/ public String missingAccesskey; + /***/ public String missingDeltaBase; /***/ public String missingForwardImageInGITBinaryPatch; /***/ public String missingObject; /***/ public String missingPrerequisiteCommits; @@ -363,6 +364,7 @@ public class JGitText extends TranslationBundle { /***/ public String renamesFindingByContent; /***/ public String renamesFindingExact; /***/ public String repositoryAlreadyExists; + /***/ public String repositoryConfigFileInvalid; /***/ public String repositoryIsRequired; /***/ public String repositoryNotFound; /***/ public String repositoryState_applyMailbox; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java index 157b85f9c9..e41ab580bb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java @@ -127,7 +127,7 @@ public class AddCommand extends GitCommand<DirCache> { addAll = true; try { - dc = DirCache.lock(repo); + dc = repo.lockDirCache(); ObjectWriter ow = new ObjectWriter(repo); DirCacheIterator c; @@ -147,7 +147,7 @@ public class AddCommand extends GitCommand<DirCache> { while (tw.next()) { String path = tw.getPathString(); - final File file = new File(repo.getWorkDir(), path); + final File file = new File(repo.getWorkTree(), path); WorkingTreeIterator f = tw.getTree(1, WorkingTreeIterator.class); if (tw.getTree(0, DirCacheIterator.class) == null && f != null && f.isEntryIgnored()) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java index eef952e7cd..17b7113470 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -54,13 +54,13 @@ import org.eclipse.jgit.errors.UnmergedPathException; import org.eclipse.jgit.lib.Commit; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectWriter; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; -import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; +import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; @@ -139,54 +139,66 @@ public class CommitCommand extends GitCommand<RevCommit> { parents.add(0, headId); // lock the index - DirCache index = DirCache.lock(repo); + DirCache index = repo.lockDirCache(); try { - ObjectWriter repoWriter = new ObjectWriter(repo); - - // Write the index as tree to the object database. This may fail - // for example when the index contains unmerged pathes - // (unresolved conflicts) - ObjectId indexTreeId = index.writeTree(repoWriter); + ObjectInserter odi = repo.newObjectInserter(); + try { + // Write the index as tree to the object database. This may + // fail for example when the index contains unmerged paths + // (unresolved conflicts) + ObjectId indexTreeId = index.writeTree(odi); - // Create a Commit object, populate it and write it - Commit commit = new Commit(repo); - commit.setCommitter(committer); - commit.setAuthor(author); - commit.setMessage(message); + // Create a Commit object, populate it and write it + Commit commit = new Commit(repo); + commit.setCommitter(committer); + commit.setAuthor(author); + commit.setMessage(message); - commit.setParentIds(parents.toArray(new ObjectId[]{})); - commit.setTreeId(indexTreeId); - ObjectId commitId = repoWriter.writeCommit(commit); + commit.setParentIds(parents.toArray(new ObjectId[] {})); + commit.setTreeId(indexTreeId); + ObjectId commitId = odi.insert(Constants.OBJ_COMMIT, odi + .format(commit)); + odi.flush(); - RevCommit revCommit = new RevWalk(repo).parseCommit(commitId); - RefUpdate ru = repo.updateRef(Constants.HEAD); - ru.setNewObjectId(commitId); - ru.setRefLogMessage("commit : " + revCommit.getShortMessage(), - false); + RevWalk revWalk = new RevWalk(repo); + try { + RevCommit revCommit = revWalk.parseCommit(commitId); + RefUpdate ru = repo.updateRef(Constants.HEAD); + ru.setNewObjectId(commitId); + ru.setRefLogMessage("commit : " + + revCommit.getShortMessage(), false); - ru.setExpectedOldObjectId(headId); - Result rc = ru.update(); - switch (rc) { - case NEW: - case FAST_FORWARD: - setCallable(false); - if (state == RepositoryState.MERGING_RESOLVED) { - // Commit was successful. Now delete the files - // used for merge commits - new File(repo.getDirectory(), Constants.MERGE_HEAD) - .delete(); - new File(repo.getDirectory(), Constants.MERGE_MSG) - .delete(); + ru.setExpectedOldObjectId(headId); + Result rc = ru.update(); + switch (rc) { + case NEW: + case FAST_FORWARD: { + setCallable(false); + File meta = repo.getDirectory(); + if (state == RepositoryState.MERGING_RESOLVED + && meta != null) { + // Commit was successful. Now delete the files + // used for merge commits + new File(meta, Constants.MERGE_HEAD).delete(); + new File(meta, Constants.MERGE_MSG).delete(); + } + return revCommit; + } + case REJECTED: + case LOCK_FAILURE: + throw new ConcurrentRefUpdateException(JGitText + .get().couldNotLockHEAD, ru.getRef(), rc); + default: + throw new JGitInternalException(MessageFormat + .format(JGitText.get().updatingRefFailed, + Constants.HEAD, + commitId.toString(), rc)); + } + } finally { + revWalk.release(); } - return revCommit; - case REJECTED: - case LOCK_FAILURE: - throw new ConcurrentRefUpdateException( - JGitText.get().couldNotLockHEAD, ru.getRef(), rc); - default: - throw new JGitInternalException(MessageFormat.format( - JGitText.get().updatingRefFailed - , Constants.HEAD, commitId.toString(), rc)); + } finally { + odi.release(); } } finally { index.unlock(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java index 00a0309152..972aa618ad 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java @@ -119,37 +119,41 @@ public class MergeCommand extends GitCommand<MergeResult> { // Check for FAST_FORWARD, ALREADY_UP_TO_DATE RevWalk revWalk = new RevWalk(repo); - RevCommit headCommit = revWalk.lookupCommit(head.getObjectId()); - - Ref ref = commits.get(0); - - refLogMessage.append(ref.getName()); - - // handle annotated tags - ObjectId objectId = ref.getPeeledObjectId(); - if (objectId == null) - objectId = ref.getObjectId(); - - RevCommit srcCommit = revWalk.lookupCommit(objectId); - if (revWalk.isMergedInto(srcCommit, headCommit)) { - setCallable(false); - return new MergeResult(headCommit, - MergeStatus.ALREADY_UP_TO_DATE, mergeStrategy); - } else if (revWalk.isMergedInto(headCommit, srcCommit)) { - // FAST_FORWARD detected: skip doing a real merge but only - // update HEAD - refLogMessage.append(": " + MergeStatus.FAST_FORWARD); - checkoutNewHead(revWalk, headCommit, srcCommit); - updateHead(refLogMessage, srcCommit, head.getObjectId()); - setCallable(false); - return new MergeResult(srcCommit, MergeStatus.FAST_FORWARD, - mergeStrategy); - } else { - return new MergeResult( - headCommit, - MergeResult.MergeStatus.NOT_SUPPORTED, - mergeStrategy, - JGitText.get().onlyAlreadyUpToDateAndFastForwardMergesAreAvailable); + try { + RevCommit headCommit = revWalk.lookupCommit(head.getObjectId()); + + Ref ref = commits.get(0); + + refLogMessage.append(ref.getName()); + + // handle annotated tags + ObjectId objectId = ref.getPeeledObjectId(); + if (objectId == null) + objectId = ref.getObjectId(); + + RevCommit srcCommit = revWalk.lookupCommit(objectId); + if (revWalk.isMergedInto(srcCommit, headCommit)) { + setCallable(false); + return new MergeResult(headCommit, + MergeStatus.ALREADY_UP_TO_DATE, mergeStrategy); + } else if (revWalk.isMergedInto(headCommit, srcCommit)) { + // FAST_FORWARD detected: skip doing a real merge but only + // update HEAD + refLogMessage.append(": " + MergeStatus.FAST_FORWARD); + checkoutNewHead(revWalk, headCommit, srcCommit); + updateHead(refLogMessage, srcCommit, head.getObjectId()); + setCallable(false); + return new MergeResult(srcCommit, MergeStatus.FAST_FORWARD, + mergeStrategy); + } else { + return new MergeResult( + headCommit, + MergeResult.MergeStatus.NOT_SUPPORTED, + mergeStrategy, + JGitText.get().onlyAlreadyUpToDateAndFastForwardMergesAreAvailable); + } + } finally { + revWalk.release(); } } catch (IOException e) { throw new JGitInternalException( @@ -163,7 +167,7 @@ public class MergeCommand extends GitCommand<MergeResult> { RevCommit newHeadCommit) throws IOException, CheckoutConflictException { GitIndex index = repo.getIndex(); - File workDir = repo.getWorkDir(); + File workDir = repo.getWorkTree(); if (workDir != null) { WorkDirCheckout workDirCheckout = new WorkDirCheckout(repo, workDir, headCommit.asCommit(revWalk).getTree(), index, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java index cdcc5e63e4..835cf6f865 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java @@ -50,20 +50,24 @@ import static org.eclipse.jgit.lib.FileMode.GITLINK; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.util.List; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.LargeObjectException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.CoreConfig; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.patch.FileHeader; import org.eclipse.jgit.patch.HunkHeader; import org.eclipse.jgit.patch.FileHeader.PatchType; +import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.QuotedString; import org.eclipse.jgit.util.io.DisabledOutputStream; @@ -83,6 +87,8 @@ public class DiffFormatter { private RawText.Factory rawTextFactory = RawText.FACTORY; + private long bigFileThreshold = 50 * 1024 * 1024; + /** * Create a new formatter with a default level of context. * @@ -110,6 +116,9 @@ public class DiffFormatter { */ public void setRepository(Repository repository) { db = repository; + + CoreConfig cfg = db.getConfig().get(CoreConfig.KEY); + bigFileThreshold = cfg.getStreamFileThreshold(); } /** @@ -158,6 +167,19 @@ public class DiffFormatter { } /** + * Set the maximum file size that should be considered for diff output. + * <p> + * Text files that are larger than this size will not have a difference + * generated during output. + * + * @param bigFileThreshold + * the limit, in bytes. + */ + public void setBigFileThreshold(long bigFileThreshold) { + this.bigFileThreshold = bigFileThreshold; + } + + /** * Flush the underlying output stream of this formatter. * * @throws IOException @@ -318,9 +340,32 @@ public class DiffFormatter { if (db == null) throw new IllegalStateException(JGitText.get().repositoryIsRequired); + if (id.isComplete()) { - ObjectLoader ldr = db.openObject(id.toObjectId()); - return ldr.getCachedBytes(); + ObjectLoader ldr = db.open(id.toObjectId()); + if (!ldr.isLarge()) + return ldr.getCachedBytes(); + + long sz = ldr.getSize(); + if (sz < bigFileThreshold && sz < Integer.MAX_VALUE) { + byte[] buf; + try { + buf = new byte[(int) sz]; + } catch (OutOfMemoryError noMemory) { + LargeObjectException e; + + e = new LargeObjectException(id.toObjectId()); + e.initCause(noMemory); + throw e; + } + InputStream in = ldr.openStream(); + try { + IO.readFully(in, buf, 0, buf.length); + } finally { + in.close(); + } + return buf; + } } return new byte[] {}; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java index cf5615a1cb..a3203e349f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java @@ -57,6 +57,7 @@ import org.eclipse.jgit.diff.DiffEntry.ChangeType; import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; @@ -294,14 +295,19 @@ public class RenameDetector { return; if (getRenameLimit() == 0 || cnt <= getRenameLimit()) { - SimilarityRenameDetector d; - - d = new SimilarityRenameDetector(repo, deleted, added); - d.setRenameScore(getRenameScore()); - d.compute(pm); - deleted = d.getLeftOverSources(); - added = d.getLeftOverDestinations(); - entries.addAll(d.getMatches()); + ObjectReader reader = repo.newObjectReader(); + try { + SimilarityRenameDetector d; + + d = new SimilarityRenameDetector(reader, deleted, added); + d.setRenameScore(getRenameScore()); + d.compute(pm); + deleted = d.getLeftOverSources(); + added = d.getLeftOverDestinations(); + entries.addAll(d.getMatches()); + } finally { + reader.release(); + } } else { overRenameLimit = true; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java index d5a31d6044..22b74f461c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java @@ -43,9 +43,14 @@ package org.eclipse.jgit.diff; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; import java.util.Arrays; +import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectStream; /** * Index structure of lines/blocks in one file. @@ -107,10 +112,20 @@ class SimilarityIndex { fileSize = size; } - void hash(ObjectLoader obj) { - byte[] raw = obj.getCachedBytes(); - setFileSize(raw.length); - hash(raw, 0, raw.length); + void hash(ObjectLoader obj) throws MissingObjectException, IOException { + if (obj.isLarge()) { + ObjectStream in = obj.openStream(); + try { + setFileSize(in.getSize()); + hash(in, fileSize); + } finally { + in.close(); + } + } else { + byte[] raw = obj.getCachedBytes(); + setFileSize(raw.length); + hash(raw, 0, raw.length); + } } void hash(byte[] raw, int ptr, final int end) { @@ -129,6 +144,35 @@ class SimilarityIndex { } } + void hash(InputStream in, long remaining) throws IOException { + byte[] buf = new byte[4096]; + int ptr = 0; + int cnt = 0; + + while (0 < remaining) { + int hash = 5381; + + // Hash one line, or one block, whichever occurs first. + int n = 0; + do { + if (ptr == cnt) { + ptr = 0; + cnt = in.read(buf, 0, buf.length); + if (cnt <= 0) + throw new EOFException(); + } + + n++; + int c = buf[ptr++] & 0xff; + if (c == '\n') + break; + hash = (hash << 5) ^ c; + } while (n < 64 && n < remaining); + add(hash, n); + remaining -= n; + } + } + /** * Sort the internal table so it can be used for efficient scoring. * <p> diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java index e2115f0acc..d05fc2a313 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java @@ -50,11 +50,12 @@ import java.util.List; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.diff.DiffEntry.ChangeType; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; -import org.eclipse.jgit.lib.Repository; class SimilarityRenameDetector { /** @@ -71,7 +72,7 @@ class SimilarityRenameDetector { private static final int SCORE_SHIFT = 2 * BITS_PER_INDEX; - private final Repository repo; + private ObjectReader reader; /** * All sources to consider for copies or renames. @@ -111,9 +112,9 @@ class SimilarityRenameDetector { private List<DiffEntry> out; - SimilarityRenameDetector(Repository repo, List<DiffEntry> srcs, + SimilarityRenameDetector(ObjectReader reader, List<DiffEntry> srcs, List<DiffEntry> dsts) { - this.repo = repo; + this.reader = reader; this.srcs = srcs; this.dsts = dsts; } @@ -336,13 +337,13 @@ class SimilarityRenameDetector { private SimilarityIndex hash(ObjectId objectId) throws IOException { SimilarityIndex r = new SimilarityIndex(); - r.hash(repo.openObject(objectId)); + r.hash(reader.open(objectId)); r.sort(); return r; } private long size(ObjectId objectId) throws IOException { - return repo.openObject(objectId).getSize(); + return reader.getObjectSize(objectId, Constants.OBJ_BLOB); } private static int score(long value) { 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 42fea48520..cc10fad2b4 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,9 @@ 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.ObjectWriter; -import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.ObjectInserter; +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; @@ -158,28 +157,6 @@ public class DirCache { } /** - * Create a new in-core index representation and read an index from disk. - * <p> - * The new index will be read before it is returned to the caller. Read - * failures are reported as exceptions and therefore prevent the method from - * returning a partially populated index. - * - * @param db - * repository the caller wants to read the default index of. - * @return a cache representing the contents of the specified index file (if - * it exists) or an empty cache if the file does not exist. - * @throws IOException - * the index file is present but could not be read. - * @throws CorruptObjectException - * the index file is using a format or extension that this - * library does not support. - */ - public static DirCache read(final Repository db) - throws CorruptObjectException, IOException { - return read(new File(db.getDirectory(), "index")); - } - - /** * Create a new in-core index representation, lock it, and read from disk. * <p> * The new index will be locked and then read before it is returned to the @@ -220,29 +197,6 @@ public class DirCache { return c; } - /** - * Create a new in-core index representation, lock it, and read from disk. - * <p> - * The new index will be locked and then read before it is returned to the - * caller. Read failures are reported as exceptions and therefore prevent - * the method from returning a partially populated index. - * - * @param db - * repository the caller wants to read the default index of. - * @return a cache representing the contents of the specified index file (if - * it exists) or an empty cache if the file does not exist. - * @throws IOException - * the index file is present but could not be read, or the lock - * could not be obtained. - * @throws CorruptObjectException - * the index file is using a format or extension that this - * library does not support. - */ - public static DirCache lock(final Repository db) - throws CorruptObjectException, IOException { - return lock(new File(db.getDirectory(), "index")); - } - /** Location of the current version of the index file. */ private final File liveFile; @@ -768,7 +722,9 @@ public class DirCache { * Write all index trees to the object store, returning the root tree. * * @param ow - * the writer to use when serializing to the store. + * the writer to use when serializing to the store. The caller is + * responsible for flushing the inserter before trying to use the + * returned tree identity. * @return identity for the root tree. * @throws UnmergedPathException * one or more paths contain higher-order stages (stage > 0), @@ -779,7 +735,7 @@ public class DirCache { * @throws IOException * an unexpected error occurred writing to the object store. */ - public ObjectId writeTree(final ObjectWriter ow) + public ObjectId writeTree(final ObjectInserter ow) throws UnmergedPathException, IOException { return getCacheTree(true).writeTree(sortedEntries, 0, 0, ow); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java index 181192d141..1eb95c4be0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java @@ -50,7 +50,7 @@ import java.io.IOException; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.treewalk.AbstractTreeIterator; /** @@ -106,7 +106,7 @@ public class DirCacheBuildIterator extends DirCacheIterator { } @Override - public AbstractTreeIterator createSubtreeIterator(final Repository repo) + public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader) throws IncorrectObjectTypeException, IOException { if (currentSubtree == null) throw new IncorrectObjectTypeException(getEntryObjectId(), diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java index e6b6197819..5665002dc7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java @@ -50,8 +50,7 @@ import java.util.Arrays; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.lib.AnyObjectId; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.WindowCursor; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.TreeWalk; @@ -149,11 +148,12 @@ public class DirCacheBuilder extends BaseDirCacheEditor { * as necessary. * @param stage * stage of the entries when adding them. - * @param db - * repository the tree(s) will be read from during recursive + * @param reader + * reader the tree(s) will be read from during recursive * traversal. This must be the same repository that the resulting * DirCache would be written out to (or used in) otherwise the * caller is simply asking for deferred MissingObjectExceptions. + * Caller is responsible for releasing this reader when done. * @param tree * the tree to recursively add. This tree's contents will appear * under <code>pathPrefix</code>. The ObjectId must be that of a @@ -163,16 +163,11 @@ public class DirCacheBuilder extends BaseDirCacheEditor { * a tree cannot be read to iterate through its entries. */ public void addTree(final byte[] pathPrefix, final int stage, - final Repository db, final AnyObjectId tree) throws IOException { - final TreeWalk tw = new TreeWalk(db); + final ObjectReader reader, final AnyObjectId tree) throws IOException { + final TreeWalk tw = new TreeWalk(reader); tw.reset(); - final WindowCursor curs = new WindowCursor(); - try { - tw.addTree(new CanonicalTreeParser(pathPrefix, db, tree - .toObjectId(), curs)); - } finally { - curs.release(); - } + tw.addTree(new CanonicalTreeParser(pathPrefix, reader, tree + .toObjectId())); tw.setRecursive(true); if (tw.next()) { final DirCacheEntry newEntry = toEntry(stage, tw); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java index 9c47187821..b4e2d2c2dd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java @@ -50,7 +50,7 @@ import java.util.Arrays; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; -import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.EmptyTreeIterator; @@ -125,7 +125,7 @@ public class DirCacheIterator extends AbstractTreeIterator { } @Override - public AbstractTreeIterator createSubtreeIterator(final Repository repo) + public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader) throws IncorrectObjectTypeException, IOException { if (currentSubtree == null) throw new IncorrectObjectTypeException(getEntryObjectId(), diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java index 144b1a6cfc..e04b797ab3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java @@ -57,7 +57,7 @@ import org.eclipse.jgit.errors.UnmergedPathException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectWriter; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.util.MutableInteger; import org.eclipse.jgit.util.RawParseUtils; @@ -311,7 +311,7 @@ public class DirCacheTree { * an unexpected error occurred writing to the object store. */ ObjectId writeTree(final DirCacheEntry[] cache, int cIdx, - final int pathOffset, final ObjectWriter ow) + final int pathOffset, final ObjectInserter ow) throws UnmergedPathException, IOException { if (id == null) { final int endIdx = cIdx + entrySpan; @@ -346,13 +346,13 @@ public class DirCacheTree { entryIdx++; } - id = ow.writeCanonicalTree(out.toByteArray()); + id = ow.insert(Constants.OBJ_TREE, out.toByteArray()); } return id; } private int computeSize(final DirCacheEntry[] cache, int cIdx, - final int pathOffset, final ObjectWriter ow) + final int pathOffset, final ObjectInserter ow) throws UnmergedPathException, IOException { final int endIdx = cIdx + entrySpan; int childIdx = 0; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/LargeObjectException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/LargeObjectException.java new file mode 100644 index 0000000000..d897c51de1 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/LargeObjectException.java @@ -0,0 +1,67 @@ +/* + * 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.errors; + +import org.eclipse.jgit.lib.ObjectId; + +/** An object is too big to load into memory as a single byte array. */ +public class LargeObjectException extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** Create a large object exception, where the object isn't known. */ + public LargeObjectException() { + // Do nothing. + } + + /** + * Create a large object exception, naming the object that is too big. + * + * @param id + * identity of the object that is too big to be loaded as a byte + * array in this JVM. + */ + public LargeObjectException(ObjectId id) { + super(id.name()); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoWorkTreeException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoWorkTreeException.java new file mode 100644 index 0000000000..f2980efe69 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoWorkTreeException.java @@ -0,0 +1,59 @@ +/* + * 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.errors; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.lib.Repository; + +/** + * Indicates a {@link Repository} has no working directory, and is thus bare. + */ +public class NoWorkTreeException extends IllegalStateException { + private static final long serialVersionUID = 1L; + + /** Creates an exception indicating there is no work tree for a repository. */ + public NoWorkTreeException() { + super(JGitText.get().bareRepositoryNoWorkdirAndIndex); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/StoredObjectRepresentationNotAvailableException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/StoredObjectRepresentationNotAvailableException.java new file mode 100644 index 0000000000..e9e3f4d65e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/StoredObjectRepresentationNotAvailableException.java @@ -0,0 +1,61 @@ +/* + * 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.errors; + +import org.eclipse.jgit.storage.pack.ObjectToPack; + +/** A previously selected representation is no longer available. */ +public class StoredObjectRepresentationNotAvailableException extends Exception { + private static final long serialVersionUID = 1L; + + /** + * Construct an error for an object. + * + * @param otp + * the object whose current representation is no longer present. + */ + public StoredObjectRepresentationNotAvailableException(ObjectToPack otp) { + // Do nothing. + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedEvent.java new file mode 100644 index 0000000000..79598eacb3 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedEvent.java @@ -0,0 +1,57 @@ +/* + * 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.events; + +/** Describes a change to one or more keys in the configuration. */ +public class ConfigChangedEvent extends RepositoryEvent<ConfigChangedListener> { + @Override + public Class<ConfigChangedListener> getListenerType() { + return ConfigChangedListener.class; + } + + @Override + public void dispatch(ConfigChangedListener listener) { + listener.onConfigChanged(this); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedListener.java index e43c33ad7d..322cf7f6d6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedListener.java @@ -41,20 +41,15 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; - -/** - * A default {@link RepositoryListener} that does nothing except invoke an - * optional general method for any repository change. - */ -public class RepositoryAdapter implements RepositoryListener { - - public void indexChanged(final IndexChangedEvent e) { - // Empty - } - - public void refsChanged(final RefsChangedEvent e) { - // Empty - } +package org.eclipse.jgit.events; +/** Receives {@link ConfigChangedEvent}s. */ +public interface ConfigChangedListener extends RepositoryListener { + /** + * Invoked when any change is made to the configuration. + * + * @param event + * information about the changes. + */ + void onConfigChanged(ConfigChangedEvent event); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java new file mode 100644 index 0000000000..a54288ee9e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java @@ -0,0 +1,57 @@ +/* + * 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.events; + +/** Describes a change to one or more paths in the index file. */ +public class IndexChangedEvent extends RepositoryEvent<IndexChangedListener> { + @Override + public Class<IndexChangedListener> getListenerType() { + return IndexChangedListener.class; + } + + @Override + public void dispatch(IndexChangedListener listener) { + listener.onIndexChanged(this); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedListener.java index 495049ce74..d41ef74ee9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedListener.java @@ -41,30 +41,15 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; - -/** - * This class passes information about changed refs to a - * {@link RepositoryListener} - * - * Currently only a reference to the repository is passed. - */ -public class RepositoryChangedEvent { - private final Repository repository; - - RepositoryChangedEvent(final Repository repository) { - this.repository = repository; - } +package org.eclipse.jgit.events; +/** Receives {@link IndexChangedEvent}s. */ +public interface IndexChangedListener extends RepositoryListener { /** - * @return the repository that was changed + * Invoked when any change is made to the index. + * + * @param event + * information about the changes. */ - public Repository getRepository() { - return repository; - } - - @Override - public String toString() { - return "RepositoryChangedEvent[" + repository + "]"; - } + void onIndexChanged(IndexChangedEvent event); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerHandle.java index c866db531e..ef90b2205c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexChangedEvent.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerHandle.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> + * 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 @@ -41,27 +41,31 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.events; -/** - * This class passes information about a changed Git index to a - * {@link RepositoryListener} - * - * Currently only a reference to the repository is passed. - */ -public class IndexChangedEvent extends RepositoryChangedEvent { - /** - * Create an event describing index changes in a repository. - * - * @param repository - * the repository whose index (DirCache) recently changed. - */ - public IndexChangedEvent(final Repository repository) { - super(repository); +/** Tracks a previously registered {@link RepositoryListener}. */ +public class ListenerHandle { + private final ListenerList parent; + + final Class<? extends RepositoryListener> type; + + final RepositoryListener listener; + + ListenerHandle(ListenerList parent, + Class<? extends RepositoryListener> type, + RepositoryListener listener) { + this.parent = parent; + this.type = type; + this.listener = listener; + } + + /** Remove the listener and stop receiving events. */ + public void remove() { + parent.remove(this); } @Override public String toString() { - return "IndexChangedEvent[" + getRepository() + "]"; + return type.getSimpleName() + "[" + listener + "]"; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java new file mode 100644 index 0000000000..6ac4b0f8ba --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java @@ -0,0 +1,142 @@ +/* + * 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.events; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; + +/** Manages a thread-safe list of {@link RepositoryListener}s. */ +public class ListenerList { + private final ConcurrentMap<Class<? extends RepositoryListener>, CopyOnWriteArrayList<ListenerHandle>> lists = new ConcurrentHashMap<Class<? extends RepositoryListener>, CopyOnWriteArrayList<ListenerHandle>>(); + + /** + * Register an IndexChangedListener. + * + * @param listener + * the listener implementation. + * @return handle to later remove the listener. + */ + public ListenerHandle addIndexChangedListener(IndexChangedListener listener) { + return addListener(IndexChangedListener.class, listener); + } + + /** + * Register a RefsChangedListener. + * + * @param listener + * the listener implementation. + * @return handle to later remove the listener. + */ + public ListenerHandle addRefsChangedListener(RefsChangedListener listener) { + return addListener(RefsChangedListener.class, listener); + } + + /** + * Register a ConfigChangedListener. + * + * @param listener + * the listener implementation. + * @return handle to later remove the listener. + */ + public ListenerHandle addConfigChangedListener( + ConfigChangedListener listener) { + return addListener(ConfigChangedListener.class, listener); + } + + /** + * Add a listener to the list. + * + * @param <T> + * the type of listener being registered. + * @param type + * type of listener being registered. + * @param listener + * the listener instance. + * @return a handle to later remove the registration, if desired. + */ + public <T extends RepositoryListener> ListenerHandle addListener( + Class<T> type, T listener) { + ListenerHandle handle = new ListenerHandle(this, type, listener); + add(handle); + return handle; + } + + /** + * Dispatch an event to all interested listeners. + * <p> + * Listeners are selected by the type of listener the event delivers to. + * + * @param event + * the event to deliver. + */ + @SuppressWarnings("unchecked") + public void dispatch(RepositoryEvent event) { + List<ListenerHandle> list = lists.get(event.getListenerType()); + if (list != null) { + for (ListenerHandle handle : list) + event.dispatch(handle.listener); + } + } + + private void add(ListenerHandle handle) { + List<ListenerHandle> list = lists.get(handle.type); + if (list == null) { + CopyOnWriteArrayList<ListenerHandle> newList; + + newList = new CopyOnWriteArrayList<ListenerHandle>(); + list = lists.putIfAbsent(handle.type, newList); + if (list == null) + list = newList; + } + list.add(handle); + } + + void remove(ListenerHandle handle) { + List<ListenerHandle> list = lists.get(handle.type); + if (list != null) + list.remove(handle); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedEvent.java new file mode 100644 index 0000000000..36af3f8b77 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedEvent.java @@ -0,0 +1,57 @@ +/* + * 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.events; + +/** Describes a change to one or more references of a repository. */ +public class RefsChangedEvent extends RepositoryEvent<RefsChangedListener> { + @Override + public Class<RefsChangedListener> getListenerType() { + return RefsChangedListener.class; + } + + @Override + public void dispatch(RefsChangedListener listener) { + listener.onRefsChanged(this); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefsChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedListener.java index 705c6138e3..9c0f4ed588 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefsChangedEvent.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedListener.java @@ -41,27 +41,15 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.events; -/** - * This class passes information about a changed Git index to a - * {@link RepositoryListener} - * - * Currently only a reference to the repository is passed. - */ -public class RefsChangedEvent extends RepositoryChangedEvent { +/** Receives {@link RefsChangedEvent}s. */ +public interface RefsChangedListener extends RepositoryListener { /** - * Create an event describing reference changes in a repository. + * Invoked when any reference changes. * - * @param repository - * the repository whose references recently changed. + * @param event + * information about the changes. */ - public RefsChangedEvent(final Repository repository) { - super(repository); - } - - @Override - public String toString() { - return "RefsChangedEvent[" + getRepository() + "]"; - } + void onRefsChanged(RefsChangedEvent event); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java new file mode 100644 index 0000000000..ba1c81d5d8 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2010, Google Inc. + * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> + * 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.events; + +import org.eclipse.jgit.lib.Repository; + +/** + * Describes a modification made to a repository. + * + * @param <T> + * type of listener this event dispatches to. + */ +public abstract class RepositoryEvent<T extends RepositoryListener> { + private Repository repository; + + /** + * Set the repository this event occurred on. + * <p> + * This method should only be invoked once on each event object, and is + * automatically set by {@link Repository#fireEvent(RepositoryEvent)}. + * + * @param r + * the repository. + */ + public void setRepository(Repository r) { + if (repository == null) + repository = r; + } + + /** @return the repository that was changed. */ + public Repository getRepository() { + return repository; + } + + /** @return type of listener this event dispatches to. */ + public abstract Class<T> getListenerType(); + + /** + * Dispatch this event to the given listener. + * + * @param listener + * listener that wants this event. + */ + public abstract void dispatch(T listener); + + @Override + public String toString() { + String type = getClass().getSimpleName(); + if (repository == null) + return type; + return type + "[" + repository + "]"; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryListener.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryListener.java index 0473093e20..4f951e5f87 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryListener.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryListener.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> + * 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 @@ -41,29 +41,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.events; -/** - * A RepositoryListener gets notification about changes in refs or repository. - * <p> - * It currently does <em>not</em> get notification about which items are - * changed. - */ +/** A listener can register for event delivery. */ public interface RepositoryListener { - /** - * Invoked when a ref changes - * - * @param e - * information about the changes. - */ - void refsChanged(RefsChangedEvent e); - - /** - * Invoked when the index changes - * - * @param e - * information about the changes. - */ - void indexChanged(IndexChangedEvent e); - + // Empty marker interface; see extensions for actual methods. } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java deleted file mode 100644 index 40f110684e..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2010, Constantine Plotnikov <constantine.plotnikov@gmail.com> - * 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.IOException; -import java.util.Collection; - -/** - * An ObjectDatabase of another {@link Repository}. - * <p> - * This {@code ObjectDatabase} wraps around another {@code Repository}'s object - * database, providing its contents to the caller, and closing the Repository - * when this database is closed. The primary user of this class is - * {@link ObjectDirectory}, when the {@code info/alternates} file points at the - * {@code objects/} directory of another repository. - */ -public final class AlternateRepositoryDatabase extends ObjectDatabase { - private final Repository repository; - - private final ObjectDatabase odb; - - /** - * @param alt - * the alternate repository to wrap and export. - */ - public AlternateRepositoryDatabase(final Repository alt) { - repository = alt; - odb = repository.getObjectDatabase(); - } - - /** @return the alternate repository objects are borrowed from. */ - public Repository getRepository() { - return repository; - } - - @Override - public void closeSelf() { - repository.close(); - } - - @Override - public void create() throws IOException { - repository.create(); - } - - @Override - public boolean exists() { - return odb.exists(); - } - - @Override - protected boolean hasObject1(final AnyObjectId objectId) { - return odb.hasObject1(objectId); - } - - @Override - protected boolean tryAgain1() { - return odb.tryAgain1(); - } - - @Override - protected boolean hasObject2(final String objectName) { - return odb.hasObject2(objectName); - } - - @Override - protected ObjectLoader openObject1(final WindowCursor curs, - final AnyObjectId objectId) throws IOException { - return odb.openObject1(curs, objectId); - } - - @Override - protected ObjectLoader openObject2(final WindowCursor curs, - final String objectName, final AnyObjectId objectId) - throws IOException { - return odb.openObject2(curs, objectName, objectId); - } - - @Override - void openObjectInAllPacks1(final Collection<PackedObjectLoader> out, - final WindowCursor curs, final AnyObjectId objectId) - throws IOException { - odb.openObjectInAllPacks1(out, curs, objectId); - } - - @Override - protected ObjectDatabase[] loadAlternates() throws IOException { - return odb.getAlternates(); - } - - @Override - protected void closeAlternates(final ObjectDatabase[] alt) { - // Do nothing; these belong to odb to close, not us. - } - - @Override - public ObjectDatabase newCachedDatabase() { - return odb.newCachedDatabase(); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java index 7d08f3d4c9..ecaa82b75a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java @@ -113,7 +113,7 @@ public abstract class AnyObjectId implements Comparable { * @return < 0 if this id comes before other; 0 if this id is equal to * other; > 0 if this id comes after other. */ - public int compareTo(final ObjectId other) { + public int compareTo(final AnyObjectId other) { if (this == other) return 0; @@ -139,7 +139,7 @@ public abstract class AnyObjectId implements Comparable { } public int compareTo(final Object other) { - return compareTo(((ObjectId) other)); + return compareTo(((AnyObjectId) other)); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java new file mode 100644 index 0000000000..92edb0325c --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java @@ -0,0 +1,616 @@ +package org.eclipse.jgit.lib; + +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BARE; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WORKTREE; +import static org.eclipse.jgit.lib.Constants.DOT_GIT; +import static org.eclipse.jgit.lib.Constants.GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY; +import static org.eclipse.jgit.lib.Constants.GIT_CEILING_DIRECTORIES_KEY; +import static org.eclipse.jgit.lib.Constants.GIT_DIR_KEY; +import static org.eclipse.jgit.lib.Constants.GIT_INDEX_KEY; +import static org.eclipse.jgit.lib.Constants.GIT_OBJECT_DIRECTORY_KEY; +import static org.eclipse.jgit.lib.Constants.GIT_WORK_TREE_KEY; + +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Collection; +import java.util.LinkedList; +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; + +/** + * Base builder to customize repository construction. + * <p> + * Repository implementations may subclass this builder in order to add custom + * repository detection methods. + * + * @param <B> + * type of the repository builder. + * @param <R> + * type of the repository that is constructed. + * @see RepositoryBuilder + * @see FileRepositoryBuilder + */ +public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Repository> { + private FS fs; + + private File gitDir; + + private File objectDirectory; + + private List<File> alternateObjectDirectories; + + private File indexFile; + + private File workTree; + + /** Directories limiting the search for a Git repository. */ + private List<File> ceilingDirectories; + + /** True only if the caller wants to force bare behavior. */ + private boolean bare; + + /** Configuration file of target repository, lazily loaded if required. */ + private Config config; + + /** + * Set the file system abstraction needed by this repository. + * + * @param fs + * the abstraction. + * @return {@code this} (for chaining calls). + */ + public B setFS(FS fs) { + this.fs = fs; + return self(); + } + + /** @return the file system abstraction, or null if not set. */ + public FS getFS() { + return fs; + } + + /** + * Set the Git directory storing the repository metadata. + * <p> + * The meta directory stores the objects, references, and meta files like + * {@code MERGE_HEAD}, or the index file. If {@code null} the path is + * assumed to be {@code workTree/.git}. + * + * @param gitDir + * {@code GIT_DIR}, the repository meta directory. + * @return {@code this} (for chaining calls). + */ + public B setGitDir(File gitDir) { + this.gitDir = gitDir; + this.config = null; + return self(); + } + + /** @return the meta data directory; null if not set. */ + public File getGitDir() { + return gitDir; + } + + /** + * Set the directory storing the repository's objects. + * + * @param objectDirectory + * {@code GIT_OBJECT_DIRECTORY}, the directory where the + * repository's object files are stored. + * @return {@code this} (for chaining calls). + */ + public B setObjectDirectory(File objectDirectory) { + this.objectDirectory = objectDirectory; + return self(); + } + + /** @return the object directory; null if not set. */ + public File getObjectDirectory() { + return objectDirectory; + } + + /** + * Add an alternate object directory to the search list. + * <p> + * This setting handles one alternate directory at a time, and is provided + * to support {@code GIT_ALTERNATE_OBJECT_DIRECTORIES}. + * + * @param other + * another objects directory to search after the standard one. + * @return {@code this} (for chaining calls). + */ + public B addAlternateObjectDirectory(File other) { + if (other != null) { + if (alternateObjectDirectories == null) + alternateObjectDirectories = new LinkedList<File>(); + alternateObjectDirectories.add(other); + } + return self(); + } + + /** + * Add alternate object directories to the search list. + * <p> + * This setting handles several alternate directories at once, and is + * provided to support {@code GIT_ALTERNATE_OBJECT_DIRECTORIES}. + * + * @param inList + * other object directories to search after the standard one. The + * collection's contents is copied to an internal list. + * @return {@code this} (for chaining calls). + */ + public B addAlternateObjectDirectories(Collection<File> inList) { + if (inList != null) { + for (File path : inList) + addAlternateObjectDirectory(path); + } + return self(); + } + + /** + * Add alternate object directories to the search list. + * <p> + * This setting handles several alternate directories at once, and is + * provided to support {@code GIT_ALTERNATE_OBJECT_DIRECTORIES}. + * + * @param inList + * other object directories to search after the standard one. The + * array's contents is copied to an internal list. + * @return {@code this} (for chaining calls). + */ + public B addAlternateObjectDirectories(File[] inList) { + if (inList != null) { + for (File path : inList) + addAlternateObjectDirectory(path); + } + return self(); + } + + /** @return ordered array of alternate directories; null if non were set. */ + public File[] getAlternateObjectDirectories() { + final List<File> alts = alternateObjectDirectories; + if (alts == null) + return null; + return alts.toArray(new File[alts.size()]); + } + + /** + * Force the repository to be treated as bare (have no working directory). + * <p> + * If bare the working directory aspects of the repository won't be + * configured, and will not be accessible. + * + * @return {@code this} (for chaining calls). + */ + public B setBare() { + setIndexFile(null); + setWorkTree(null); + bare = true; + return self(); + } + + /** @return true if this repository was forced bare by {@link #setBare()}. */ + public boolean isBare() { + return bare; + } + + /** + * Set the top level directory of the working files. + * + * @param workTree + * {@code GIT_WORK_TREE}, the working directory of the checkout. + * @return {@code this} (for chaining calls). + */ + public B setWorkTree(File workTree) { + this.workTree = workTree; + return self(); + } + + /** @return the work tree directory, or null if not set. */ + public File getWorkTree() { + return workTree; + } + + /** + * Set the local index file that is caching checked out file status. + * <p> + * The location of the index file tracking the status information for each + * checked out file in {@code workTree}. This may be null to assume the + * default {@code gitDiir/index}. + * + * @param indexFile + * {@code GIT_INDEX_FILE}, the index file location. + * @return {@code this} (for chaining calls). + */ + public B setIndexFile(File indexFile) { + this.indexFile = indexFile; + return self(); + } + + /** @return the index file location, or null if not set. */ + public File getIndexFile() { + return indexFile; + } + + /** + * Read standard Git environment variables and configure from those. + * <p> + * This method tries to read the standard Git environment variables, such as + * {@code GIT_DIR} and {@code GIT_WORK_TREE} to configure this builder + * instance. If an environment variable is set, it overrides the value + * already set in this builder. + * + * @return {@code this} (for chaining calls). + */ + public B readEnvironment() { + return readEnvironment(SystemReader.getInstance()); + } + + /** + * Read standard Git environment variables and configure from those. + * <p> + * This method tries to read the standard Git environment variables, such as + * {@code GIT_DIR} and {@code GIT_WORK_TREE} to configure this builder + * instance. If a property is already set in the builder, the environment + * variable is not used. + * + * @param sr + * the SystemReader abstraction to access the environment. + * @return {@code this} (for chaining calls). + */ + public B readEnvironment(SystemReader sr) { + if (getGitDir() == null) { + String val = sr.getenv(GIT_DIR_KEY); + if (val != null) + setGitDir(new File(val)); + } + + if (getObjectDirectory() == null) { + String val = sr.getenv(GIT_OBJECT_DIRECTORY_KEY); + if (val != null) + setObjectDirectory(new File(val)); + } + + if (getAlternateObjectDirectories() == null) { + String val = sr.getenv(GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY); + if (val != null) { + for (String path : val.split(File.pathSeparator)) + addAlternateObjectDirectory(new File(path)); + } + } + + if (getWorkTree() == null) { + String val = sr.getenv(GIT_WORK_TREE_KEY); + if (val != null) + setWorkTree(new File(val)); + } + + if (getIndexFile() == null) { + String val = sr.getenv(GIT_INDEX_KEY); + if (val != null) + setIndexFile(new File(val)); + } + + if (ceilingDirectories == null) { + String val = sr.getenv(GIT_CEILING_DIRECTORIES_KEY); + if (val != null) { + for (String path : val.split(File.pathSeparator)) + addCeilingDirectory(new File(path)); + } + } + + return self(); + } + + /** + * Add a ceiling directory to the search limit list. + * <p> + * This setting handles one ceiling directory at a time, and is provided to + * support {@code GIT_CEILING_DIRECTORIES}. + * + * @param root + * a path to stop searching at; its parent will not be searched. + * @return {@code this} (for chaining calls). + */ + public B addCeilingDirectory(File root) { + if (root != null) { + if (ceilingDirectories == null) + ceilingDirectories = new LinkedList<File>(); + ceilingDirectories.add(root); + } + return self(); + } + + /** + * Add ceiling directories to the search list. + * <p> + * This setting handles several ceiling directories at once, and is provided + * to support {@code GIT_CEILING_DIRECTORIES}. + * + * @param inList + * directory paths to stop searching at. The collection's + * contents is copied to an internal list. + * @return {@code this} (for chaining calls). + */ + public B addCeilingDirectories(Collection<File> inList) { + if (inList != null) { + for (File path : inList) + addCeilingDirectory(path); + } + return self(); + } + + /** + * Add ceiling directories to the search list. + * <p> + * This setting handles several ceiling directories at once, and is provided + * to support {@code GIT_CEILING_DIRECTORIES}. + * + * @param inList + * directory paths to stop searching at. The array's contents is + * copied to an internal list. + * @return {@code this} (for chaining calls). + */ + public B addCeilingDirectories(File[] inList) { + if (inList != null) { + for (File path : inList) + addCeilingDirectory(path); + } + return self(); + } + + /** + * Configure {@code GIT_DIR} by searching up the file system. + * <p> + * Starts from the current working directory of the JVM and scans up through + * the directory tree until a Git repository is found. Success can be + * determined by checking for {@code getGitDir() != null}. + * <p> + * The search can be limited to specific spaces of the local filesystem by + * {@link #addCeilingDirectory(File)}, or inheriting the list through a + * prior call to {@link #readEnvironment()}. + * + * @return {@code this} (for chaining calls). + */ + public B findGitDir() { + if (getGitDir() == null) + findGitDir(new File("").getAbsoluteFile()); + return self(); + } + + /** + * Configure {@code GIT_DIR} by searching up the file system. + * <p> + * Starts from the supplied directory path and scans up through the parent + * directory tree until a Git repository is found. Success can be determined + * by checking for {@code getGitDir() != null}. + * <p> + * The search can be limited to specific spaces of the local filesystem by + * {@link #addCeilingDirectory(File)}, or inheriting the list through a + * prior call to {@link #readEnvironment()}. + * + * @param current + * directory to begin searching in. + * @return {@code this} (for chaining calls). + */ + public B findGitDir(File current) { + if (getGitDir() == null) { + FS tryFS = safeFS(); + while (current != null) { + File dir = new File(current, DOT_GIT); + if (FileKey.isGitRepository(dir, tryFS)) { + setGitDir(dir); + break; + } + + current = current.getParentFile(); + if (current != null && ceilingDirectories.contains(current)) + break; + } + } + return self(); + } + + /** + * Guess and populate all parameters not already defined. + * <p> + * If an option was not set, the setup method will try to default the option + * based on other options. If insufficient information is available, an + * exception is thrown to the caller. + * + * @return {@code this} + * @throws IllegalArgumentException + * insufficient parameters were set, or some parameters are + * incompatible with one another. + * @throws IOException + * the repository could not be accessed to configure the rest of + * the builder's parameters. + */ + public B setup() throws IllegalArgumentException, IOException { + requireGitDirOrWorkTree(); + setupGitDir(); + setupWorkTree(); + setupInternals(); + return self(); + } + + /** + * Create a repository matching the configuration in this builder. + * <p> + * 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. + */ + @SuppressWarnings("unchecked") + public R build() throws IOException { + return (R) new FileRepository(setup()); + } + + /** Require either {@code gitDir} or {@code workTree} to be set. */ + protected void requireGitDirOrWorkTree() { + if (getGitDir() == null && getWorkTree() == null) + throw new IllegalArgumentException( + JGitText.get().eitherGitDirOrWorkTreeRequired); + } + + /** + * Perform standard gitDir initialization. + * + * @throws IOException + * the repository could not be accessed + */ + protected void setupGitDir() throws IOException { + // No gitDir? Try to assume its under the workTree. + // + if (getGitDir() == null && getWorkTree() != null) + setGitDir(new File(getWorkTree(), DOT_GIT)); + } + + /** + * Perform standard work-tree initialization. + * <p> + * This is a method typically invoked inside of {@link #setup()}, near the + * end after the repository has been identified and its configuration is + * available for inspection. + * + * @throws IOException + * the repository configuration could not be read. + */ + protected void setupWorkTree() throws IOException { + if (getFS() == null) + setFS(FS.DETECTED); + + // If we aren't bare, we should have a work tree. + // + if (!isBare() && getWorkTree() == null) + setWorkTree(guessWorkTreeOrFail()); + + if (!isBare()) { + // If after guessing we're still not bare, we must have + // a metadata directory to hold the repository. Assume + // its at the work tree. + // + if (getGitDir() == null) + setGitDir(getWorkTree().getParentFile()); + if (getIndexFile() == null) + setIndexFile(new File(getGitDir(), "index")); + } + } + + /** + * Configure the internal implementation details of the repository. + * + * @throws IOException + * the repository could not be accessed + */ + protected void setupInternals() throws IOException { + if (getObjectDirectory() == null && getGitDir() != null) + setObjectDirectory(safeFS().resolve(getGitDir(), "objects")); + } + + /** + * Get the cached repository configuration, loading if not yet available. + * + * @return the configuration of the repository. + * @throws IOException + * the configuration is not available, or is badly formed. + */ + protected Config getConfig() throws IOException { + if (config == null) + config = loadConfig(); + return config; + } + + /** + * Parse and load the repository specific configuration. + * <p> + * The default implementation reads {@code gitDir/config}, or returns an + * empty configuration if gitDir was not set. + * + * @return the repository's configuration. + * @throws IOException + * the configuration is not available. + */ + protected Config loadConfig() throws IOException { + if (getGitDir() != null) { + // We only want the repository's configuration file, and not + // the user file, as these parameters must be unique to this + // repository and not inherited from other files. + // + File path = safeFS().resolve(getGitDir(), "config"); + FileBasedConfig cfg = new FileBasedConfig(path); + try { + cfg.load(); + } catch (ConfigInvalidException err) { + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().repositoryConfigFileInvalid, path + .getAbsolutePath(), err.getMessage())); + } + return cfg; + } else { + return new Config(); + } + } + + private File guessWorkTreeOrFail() throws IOException { + final Config cfg = getConfig(); + + // If set, core.worktree wins. + // + String path = cfg.getString(CONFIG_CORE_SECTION, null, + CONFIG_KEY_WORKTREE); + if (path != null) + return safeFS().resolve(getGitDir(), path); + + // If core.bare is set, honor its value. Assume workTree is + // the parent directory of the repository. + // + if (cfg.getString(CONFIG_CORE_SECTION, null, CONFIG_KEY_BARE) != null) { + if (cfg.getBoolean(CONFIG_CORE_SECTION, CONFIG_KEY_BARE, true)) { + setBare(); + return null; + } + return getGitDir().getParentFile(); + } + + if (getGitDir().getName().equals(DOT_GIT)) { + // No value for the "bare" flag, but gitDir is named ".git", + // use the parent of the directory + // + return getGitDir().getParentFile(); + } + + // We have to assume we are bare. + // + setBare(); + return null; + } + + /** @return the configured FS, or {@link FS#DETECTED}. */ + protected FS safeFS() { + return getFS() != null ? getFS() : FS.DETECTED; + } + + /** @return {@code this} */ + @SuppressWarnings("unchecked") + protected final B self() { + return (B) this; + } +}
\ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java index b05942b02c..b56966ff42 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java @@ -91,10 +91,8 @@ public class BlobBasedConfig extends Config { public BlobBasedConfig(Config base, final Repository r, final ObjectId objectId) throws IOException, ConfigInvalidException { super(base); - final ObjectLoader loader = r.openBlob(objectId); - if (loader == null) - throw new IOException(MessageFormat.format(JGitText.get().blobNotFound, objectId)); - fromText(RawParseUtils.decode(loader.getBytes())); + ObjectLoader loader = r.open(objectId, Constants.OBJ_BLOB); + fromText(RawParseUtils.decode(loader.getCachedBytes())); } /** @@ -122,10 +120,7 @@ public class BlobBasedConfig extends Config { if (tree == null) throw new FileNotFoundException(MessageFormat.format(JGitText.get().entryNotFoundByPath, path)); final ObjectId blobId = tree.getObjectId(0); - final ObjectLoader loader = tree.getRepository().openBlob(blobId); - if (loader == null) - throw new IOException(MessageFormat.format(JGitText.get().blobNotFoundForPath - , blobId, path)); - fromText(RawParseUtils.decode(loader.getBytes())); + ObjectLoader loader = r.open(blobId,Constants.OBJ_BLOB); + fromText(RawParseUtils.decode(loader.getCachedBytes())); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java deleted file mode 100644 index 3dcea1636f..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2010, Constantine Plotnikov <constantine.plotnikov@gmail.com> - * 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.IOException; -import java.util.Collection; - -/** - * {@link ObjectDatabase} wrapper providing temporary lookup caching. - * <p> - * The base class for {@code ObjectDatabase}s that wrap other database instances - * and optimize querying for objects by caching some database dependent - * information. Instances of this class (or any of its subclasses) can be - * returned from the method {@link ObjectDatabase#newCachedDatabase()}. This - * class can be used in scenarios where the database does not change, or when - * changes in the database while some operation is in progress is an acceptable - * risk. - * <p> - * The default implementation delegates all requests to the wrapped database. - * The instance might be indirectly invalidated if the wrapped instance is - * closed. Closing the delegating instance does not implies closing the wrapped - * instance. For alternative databases, cached instances are used as well. - */ -public class CachedObjectDatabase extends ObjectDatabase { - /** - * The wrapped database instance - */ - protected final ObjectDatabase wrapped; - - /** - * Create the delegating database instance - * - * @param wrapped - * the wrapped object database - */ - public CachedObjectDatabase(ObjectDatabase wrapped) { - this.wrapped = wrapped; - } - - @Override - protected boolean hasObject1(AnyObjectId objectId) { - return wrapped.hasObject1(objectId); - } - - @Override - protected ObjectLoader openObject1(WindowCursor curs, AnyObjectId objectId) - throws IOException { - return wrapped.openObject1(curs, objectId); - } - - @Override - protected boolean hasObject2(String objectName) { - return wrapped.hasObject2(objectName); - } - - @Override - protected ObjectDatabase[] loadAlternates() throws IOException { - ObjectDatabase[] loaded = wrapped.getAlternates(); - ObjectDatabase[] result = new ObjectDatabase[loaded.length]; - for (int i = 0; i < loaded.length; i++) { - result[i] = loaded[i].newCachedDatabase(); - } - return result; - } - - @Override - protected ObjectLoader openObject2(WindowCursor curs, String objectName, - AnyObjectId objectId) throws IOException { - return wrapped.openObject2(curs, objectName, objectId); - } - - @Override - void openObjectInAllPacks1(Collection<PackedObjectLoader> out, - WindowCursor curs, AnyObjectId objectId) throws IOException { - wrapped.openObjectInAllPacks1(out, curs, objectId); - } - - @Override - protected boolean tryAgain1() { - return wrapped.tryAgain1(); - } - - @Override - public ObjectDatabase newCachedDatabase() { - // Note that "this" is not returned since subclasses might actually do something, - // on closeSelf() (for example closing database connections or open repositories). - // The situation might become even more tricky if we will consider alternates. - return wrapped.newCachedDatabase(); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Commit.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Commit.java index 66dd89120c..eeffb08e95 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Commit.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Commit.java @@ -339,7 +339,14 @@ public class Commit implements Treeish { public void commit() throws IOException { if (getCommitId() != null) throw new IllegalStateException(MessageFormat.format(JGitText.get().commitAlreadyExists, getCommitId())); - setCommitId(new ObjectWriter(objdb).writeCommit(this)); + ObjectInserter odi = objdb.newObjectInserter(); + try { + ObjectId id = odi.insert(Constants.OBJ_COMMIT, odi.format(this)); + odi.flush(); + setCommitId(id); + } finally { + odi.release(); + } } public String toString() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java index 9080ec1398..5ad7910be8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java @@ -47,6 +47,7 @@ package org.eclipse.jgit.lib; import static java.util.zip.Deflater.DEFAULT_COMPRESSION; +import static org.eclipse.jgit.lib.ObjectLoader.STREAM_THRESHOLD; import org.eclipse.jgit.lib.Config.SectionParser; @@ -67,14 +68,21 @@ public class CoreConfig { private final boolean logAllRefUpdates; + private final int streamFileThreshold; + private CoreConfig(final Config rc) { compression = rc.getInt("core", "compression", DEFAULT_COMPRESSION); packIndexVersion = rc.getInt("pack", "indexversion", 2); logAllRefUpdates = rc.getBoolean("core", "logallrefupdates", true); + + long maxMem = Runtime.getRuntime().maxMemory(); + long sft = rc.getLong("core", null, "streamfilethreshold", STREAM_THRESHOLD); + sft = Math.min(sft, maxMem / 4); // don't use more than 1/4 of the heap + sft = Math.min(sft, Integer.MAX_VALUE); // cannot exceed array length + streamFileThreshold = (int) sft; } /** - * @see ObjectWriter * @return The compression level to use when storing loose objects */ public int getCompression() { @@ -95,4 +103,9 @@ public class CoreConfig { public boolean isLogAllRefUpdates() { return logAllRefUpdates; } + + /** @return the size threshold beyond which objects must be streamed. */ + public int getStreamFileThreshold() { + return streamFileThreshold; + } } 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 <marek.zawirski@gmail.com> - * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com> - * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.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/FileTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java index ed1b51d8d5..7990956f6b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java @@ -100,7 +100,7 @@ public class FileTreeEntry extends TreeEntry { * @throws IOException */ public ObjectLoader openReader() throws IOException { - return getRepository().openBlob(getId()); + return getRepository().open(getId(), Constants.OBJ_BLOB); } public void accept(final TreeVisitor tv, final int flags) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java index df4906374f..bf293d190b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java @@ -71,6 +71,7 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.NotSupportedException; +import org.eclipse.jgit.events.IndexChangedEvent; import org.eclipse.jgit.util.RawParseUtils; /** @@ -155,7 +156,7 @@ public class GitIndex { public void rereadIfNecessary() throws IOException { if (cacheFile.exists() && cacheFile.lastModified() != lastCacheTime) { read(); - db.fireIndexChanged(); + db.fireEvent(new IndexChangedEvent()); } } @@ -329,7 +330,7 @@ public class GitIndex { changed = false; statDirty = false; lastCacheTime = cacheFile.lastModified(); - db.fireIndexChanged(); + db.fireEvent(new IndexChangedEvent()); } finally { if (!lock.delete()) throw new IOException( @@ -374,7 +375,7 @@ public class GitIndex { // to change this for testing. if (filemode != null) return filemode.booleanValue(); - RepositoryConfig config = db.getConfig(); + Config config = db.getConfig(); filemode = Boolean.valueOf(config.getBoolean("core", null, "filemode", true)); return filemode.booleanValue(); } @@ -459,7 +460,7 @@ public class GitIndex { uid = -1; gid = -1; try { - size = (int) db.openBlob(f.getId()).getSize(); + size = (int) db.open(f.getId(), Constants.OBJ_BLOB).getSize(); } catch (IOException e) { e.printStackTrace(); size = -1; @@ -899,17 +900,16 @@ public class GitIndex { * @throws IOException */ public void checkoutEntry(File wd, Entry e) throws IOException { - ObjectLoader ol = db.openBlob(e.sha1); - byte[] bytes = ol.getBytes(); + ObjectLoader ol = db.open(e.sha1, Constants.OBJ_BLOB); File file = new File(wd, e.getName()); file.delete(); file.getParentFile().mkdirs(); - FileChannel channel = new FileOutputStream(file).getChannel(); - ByteBuffer buffer = ByteBuffer.wrap(bytes); - int j = channel.write(buffer); - if (j != bytes.length) - throw new IOException(MessageFormat.format(JGitText.get().couldNotWriteFile, file)); - channel.close(); + FileOutputStream dst = new FileOutputStream(file); + try { + ol.copyTo(dst); + } finally { + dst.close(); + } if (config_filemode() && File_hasExecute()) { if (FileMode.EXECUTABLE_FILE.equals(e.mode)) { if (!File_canExecute(file)) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java index 030b94214d..db0f942b07 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java @@ -95,7 +95,7 @@ public class IndexDiff { * @throws IOException */ public boolean diff() throws IOException { - final File root = index.getRepository().getWorkDir(); + final File root = index.getRepository().getWorkTree(); new IndexTreeWalker(index, tree, root, new AbstractIndexTreeVisitor() { public void visitEntry(TreeEntry treeEntry, Entry indexEntry, File file) { if (treeEntry == null) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java index 7eac79fb78..15d118c0e9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java @@ -44,31 +44,20 @@ package org.eclipse.jgit.lib; import java.io.IOException; -import java.util.Collection; -import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; /** * Abstraction of arbitrary object storage. * <p> * An object database stores one or more Git objects, indexed by their unique - * {@link ObjectId}. Optionally an object database can reference one or more - * alternates; other ObjectDatabase instances that are searched in addition to - * the current database. - * <p> - * Databases are usually divided into two halves: a half that is considered to - * be fast to search, and a half that is considered to be slow to search. When - * alternates are present the fast half is fully searched (recursively through - * all alternates) before the slow half is considered. + * {@link ObjectId}. */ public abstract class ObjectDatabase { - /** Constant indicating no alternate databases exist. */ - protected static final ObjectDatabase[] NO_ALTERNATES = {}; - - private final AtomicReference<ObjectDatabase[]> alternates; - /** Initialize a new database instance for access. */ protected ObjectDatabase() { - alternates = new AtomicReference<ObjectDatabase[]>(); + // Protected to force extension. } /** @@ -92,292 +81,101 @@ public abstract class ObjectDatabase { } /** - * Close any resources held by this database and its active alternates. - */ - public final void close() { - closeSelf(); - closeAlternates(); - } - - /** - * Close any resources held by this database only; ignoring alternates. + * Create a new {@code ObjectInserter} to insert new objects. * <p> - * To fully close this database and its referenced alternates, the caller - * should instead invoke {@link #close()}. + * The returned inserter is not itself thread-safe, but multiple concurrent + * inserter instances created from the same {@code ObjectDatabase} must be + * thread-safe. + * + * @return writer the caller can use to create objects in this database. */ - public void closeSelf() { - // Assume no action is required. - } - - /** Fully close all loaded alternates and clear the alternate list. */ - public final void closeAlternates() { - ObjectDatabase[] alt = alternates.get(); - if (alt != null) { - alternates.set(null); - closeAlternates(alt); - } - } + public abstract ObjectInserter newInserter(); /** - * Does the requested object exist in this database? + * Create a new {@code ObjectReader} to read existing objects. * <p> - * Alternates (if present) are searched automatically. + * The returned reader is not itself thread-safe, but multiple concurrent + * reader instances created from the same {@code ObjectDatabase} must be + * thread-safe. * - * @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. + * @return reader the caller can use to load objects from this database. */ - public final boolean hasObject(final AnyObjectId objectId) { - return hasObjectImpl1(objectId) || hasObjectImpl2(objectId.name()); - } - - private final boolean hasObjectImpl1(final AnyObjectId objectId) { - if (hasObject1(objectId)) { - return true; - } - for (final ObjectDatabase alt : getAlternates()) { - if (alt.hasObjectImpl1(objectId)) { - return true; - } - } - return tryAgain1() && hasObject1(objectId); - } - - private final boolean hasObjectImpl2(final String objectId) { - if (hasObject2(objectId)) { - return true; - } - for (final ObjectDatabase alt : getAlternates()) { - if (alt.hasObjectImpl2(objectId)) { - return true; - } - } - return false; - } + public abstract ObjectReader newReader(); /** - * Fast half of {@link #hasObject(AnyObjectId)}. - * - * @param objectId - * identity of the object to test for existence of. - * @return true if the specified object is stored in this database. + * Close any resources held by this database. */ - protected abstract boolean hasObject1(AnyObjectId objectId); + public abstract void close(); /** - * Slow half of {@link #hasObject(AnyObjectId)}. + * Does the requested object exist in this database? + * <p> + * This is a one-shot call interface which may be faster than allocating a + * {@link #newReader()} to perform the lookup. * - * @param objectName + * @param objectId * identity of the object to test for existence of. * @return true if the specified object is stored in this database. - */ - protected boolean hasObject2(String objectName) { - // Assume the search took place during hasObject1. - return false; + * @throws IOException + * the object store cannot be accessed. + */ + public boolean has(final AnyObjectId objectId) throws IOException { + final ObjectReader or = newReader(); + try { + return or.has(objectId); + } finally { + or.release(); + } } /** * Open an object from this database. * <p> - * Alternates (if present) are searched automatically. + * This is a one-shot call interface which may be faster than allocating a + * {@link #newReader()} to perform the lookup. * - * @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. + * @return a {@link ObjectLoader} for accessing the object. + * @throws MissingObjectException + * the object does not exist. * @throws IOException + * the object store cannot be accessed. */ - public final 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; - } - - private ObjectLoader openObjectImpl1(final WindowCursor curs, - final AnyObjectId objectId) throws IOException { - ObjectLoader ldr; - - ldr = openObject1(curs, objectId); - if (ldr != null) { - return ldr; - } - for (final ObjectDatabase alt : getAlternates()) { - ldr = alt.openObjectImpl1(curs, objectId); - if (ldr != null) { - return ldr; - } - } - if (tryAgain1()) { - ldr = openObject1(curs, objectId); - if (ldr != null) { - return ldr; - } - } - return null; - } - - private ObjectLoader openObjectImpl2(final WindowCursor curs, - final String objectName, final AnyObjectId objectId) + public ObjectLoader open(final AnyObjectId objectId) throws IOException { - ObjectLoader ldr; - - ldr = openObject2(curs, objectName, objectId); - if (ldr != null) { - return ldr; - } - for (final ObjectDatabase alt : getAlternates()) { - ldr = alt.openObjectImpl2(curs, objectName, objectId); - if (ldr != null) { - return ldr; - } - } - return null; + return open(objectId, ObjectReader.OBJ_ANY); } /** - * Fast half of {@link #openObject(WindowCursor, AnyObjectId)}. - * - * @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 - */ - protected abstract ObjectLoader openObject1(WindowCursor curs, - AnyObjectId objectId) throws IOException; - - /** - * Slow half of {@link #openObject(WindowCursor, AnyObjectId)}. - * - * @param curs - * temporary working space associated with the calling thread. - * @param objectName - * name of the object to open. - * @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 - */ - protected ObjectLoader openObject2(WindowCursor curs, String objectName, - AnyObjectId objectId) throws IOException { - // Assume the search took place during openObject1. - return null; - } - - /** - * Open the object from all packs containing it. + * Open an object from this database. * <p> - * If any alternates are present, their packs are also considered. - * - * @param out - * result collection of loaders for this object, filled with - * loaders from all packs containing specified object - * @param curs - * temporary working space associated with the calling thread. - * @param objectId - * id of object to search for - * @throws IOException - */ - final void openObjectInAllPacks(final Collection<PackedObjectLoader> out, - final WindowCursor curs, final AnyObjectId objectId) - throws IOException { - openObjectInAllPacks1(out, curs, objectId); - for (final ObjectDatabase alt : getAlternates()) { - alt.openObjectInAllPacks1(out, curs, objectId); - } - } - - /** - * Open the object from all packs containing it. + * This is a one-shot call interface which may be faster than allocating a + * {@link #newReader()} to perform the lookup. * - * @param out - * result collection of loaders for this object, filled with - * loaders from all packs containing specified object - * @param curs - * temporary working space associated with the calling thread. * @param objectId - * id of object to search for - * @throws IOException - */ - void openObjectInAllPacks1(Collection<PackedObjectLoader> out, - WindowCursor curs, AnyObjectId objectId) throws IOException { - // Assume no pack support - } - - /** - * @return true if the fast-half search should be tried again. - */ - protected boolean tryAgain1() { - return false; - } - - /** - * Get the alternate databases known to this database. - * - * @return the alternate list. Never null, but may be an empty array. - */ - public final ObjectDatabase[] getAlternates() { - ObjectDatabase[] r = alternates.get(); - if (r == null) { - synchronized (alternates) { - r = alternates.get(); - if (r == null) { - try { - r = loadAlternates(); - } catch (IOException e) { - r = NO_ALTERNATES; - } - alternates.set(r); - } - } - } - return r; - } - - /** - * Load the list of alternate databases into memory. - * <p> - * This method is invoked by {@link #getAlternates()} if the alternate list - * has not yet been populated, or if {@link #closeAlternates()} has been - * called on this instance and the alternate list is needed again. - * <p> - * If the alternate array is empty, implementors should consider using the - * constant {@link #NO_ALTERNATES}. - * - * @return the alternate list for this database. + * identity of the object to open. + * @param typeHint + * hint about the type of object being requested; + * {@link ObjectReader#OBJ_ANY} if the object type is not known, + * or does not matter to the caller. + * @return a {@link ObjectLoader} for accessing the object. + * @throws MissingObjectException + * the object does not exist. + * @throws IncorrectObjectTypeException + * typeHint was not OBJ_ANY, and the object's actual type does + * not match typeHint. * @throws IOException - * the alternate list could not be accessed. The empty alternate - * array {@link #NO_ALTERNATES} will be assumed by the caller. - */ - protected ObjectDatabase[] loadAlternates() throws IOException { - return NO_ALTERNATES; - } - - /** - * Close the list of alternates returned by {@link #loadAlternates()}. - * - * @param alt - * the alternate list, from {@link #loadAlternates()}. - */ - protected void closeAlternates(ObjectDatabase[] alt) { - for (final ObjectDatabase d : alt) { - d.close(); + * the object store cannot be accessed. + */ + public ObjectLoader open(AnyObjectId objectId, int typeHint) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + final ObjectReader or = newReader(); + try { + return or.open(objectId, typeHint); + } finally { + or.release(); } } @@ -387,9 +185,8 @@ public abstract class ObjectDatabase { * done after instance creation might fail to be noticed. * * @return new cached database instance - * @see CachedObjectDatabase */ public ObjectDatabase newCachedDatabase() { - return new CachedObjectDatabase(this); + return this; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java new file mode 100644 index 0000000000..fd99d39e78 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com> + * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> + * 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.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.text.MessageFormat; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.ObjectWritingException; + +/** + * Inserts objects into an existing {@code ObjectDatabase}. + * <p> + * An inserter is not thread-safe. Individual threads should each obtain their + * own unique inserter instance, or must arrange for locking at a higher level + * to ensure the inserter is in use by no more than one thread at a time. + * <p> + * Objects written by an inserter may not be immediately visible for reading + * after the insert method completes. Callers must invoke either + * {@link #release()} or {@link #flush()} prior to updating references or + * otherwise making the returned ObjectIds visible to other code. + */ +public abstract class ObjectInserter { + private static final byte[] htree = Constants.encodeASCII("tree"); + + private static final byte[] hparent = Constants.encodeASCII("parent"); + + private static final byte[] hauthor = Constants.encodeASCII("author"); + + private static final byte[] hcommitter = Constants.encodeASCII("committer"); + + private static final byte[] hencoding = Constants.encodeASCII("encoding"); + + /** An inserter that can be used for formatting and id generation only. */ + public static class Formatter extends ObjectInserter { + @Override + public ObjectId insert(int objectType, long length, InputStream in) + throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void flush() throws IOException { + // Do nothing. + } + + @Override + public void release() { + // Do nothing. + } + } + + /** Digest to compute the name of an object. */ + private final MessageDigest digest; + + /** Temporary working buffer for streaming data through. */ + private byte[] tempBuffer; + + /** Create a new inserter for a database. */ + protected ObjectInserter() { + digest = Constants.newMessageDigest(); + } + + /** @return a temporary byte array for use by the caller. */ + protected byte[] buffer() { + if (tempBuffer == null) + tempBuffer = new byte[8192]; + return tempBuffer; + } + + /** @return digest to help compute an ObjectId */ + protected MessageDigest digest() { + digest.reset(); + return digest; + } + + /** + * Compute the name of an object, without inserting it. + * + * @param type + * type code of the object to store. + * @param data + * complete content of the object. + * @return the name of the object. + */ + public ObjectId idFor(int type, byte[] data) { + return idFor(type, data, 0, data.length); + } + + /** + * Compute the name of an object, without inserting it. + * + * @param type + * type code of the object to store. + * @param data + * complete content of the object. + * @param off + * first position within {@code data}. + * @param len + * number of bytes to copy from {@code data}. + * @return the name of the object. + */ + public ObjectId idFor(int type, byte[] data, int off, int len) { + MessageDigest md = digest(); + md.update(Constants.encodedTypeString(type)); + md.update((byte) ' '); + md.update(Constants.encodeASCII(len)); + md.update((byte) 0); + md.update(data, off, len); + return ObjectId.fromRaw(md.digest()); + } + + /** + * Compute the name of an object, without inserting it. + * + * @param objectType + * type code of the object to store. + * @param length + * number of bytes to scan from {@code in}. + * @param in + * stream providing the object content. The caller is responsible + * for closing the stream. + * @return the name of the object. + * @throws IOException + * the source stream could not be read. + */ + public ObjectId idFor(int objectType, long length, InputStream in) + throws IOException { + MessageDigest md = digest(); + md.update(Constants.encodedTypeString(objectType)); + md.update((byte) ' '); + md.update(Constants.encodeASCII(length)); + md.update((byte) 0); + byte[] buf = buffer(); + while (length > 0) { + int n = in.read(buf, 0, (int) Math.min(length, buf.length)); + if (n < 0) + throw new EOFException("Unexpected end of input"); + md.update(buf, 0, n); + length -= n; + } + return ObjectId.fromRaw(md.digest()); + } + + /** + * Insert a single object into the store, returning its unique name. + * + * @param type + * type code of the object to store. + * @param data + * complete content of the object. + * @return the name of the object. + * @throws IOException + * the object could not be stored. + */ + public ObjectId insert(final int type, final byte[] data) + throws IOException { + return insert(type, data, 0, data.length); + } + + /** + * Insert a single object into the store, returning its unique name. + * + * @param type + * type code of the object to store. + * @param data + * complete content of the object. + * @param off + * first position within {@code data}. + * @param len + * number of bytes to copy from {@code data}. + * @return the name of the object. + * @throws IOException + * the object could not be stored. + */ + public ObjectId insert(int type, byte[] data, int off, int len) + throws IOException { + return insert(type, len, new ByteArrayInputStream(data, off, len)); + } + + /** + * Insert a single object into the store, returning its unique name. + * + * @param objectType + * type code of the object to store. + * @param length + * number of bytes to copy from {@code in}. + * @param in + * stream providing the object content. The caller is responsible + * for closing the stream. + * @return the name of the object. + * @throws IOException + * the object could not be stored, or the source stream could + * not be read. + */ + public abstract ObjectId insert(int objectType, long length, InputStream in) + throws IOException; + + /** + * Make all inserted objects visible. + * <p> + * The flush may take some period of time to make the objects available to + * other threads. + * + * @throws IOException + * the flush could not be completed; objects inserted thus far + * are in an indeterminate state. + */ + public abstract void flush() throws IOException; + + /** + * Release any resources used by this inserter. + * <p> + * An inserter that has been released can be used again, but may need to be + * released after the subsequent usage. + */ + public abstract void release(); + + /** + * Format a Tree in canonical format. + * + * @param tree + * the tree object to format + * @return canonical encoding of the tree object. + * @throws IOException + * the tree cannot be loaded, or its not in a writable state. + */ + public final byte[] format(Tree tree) throws IOException { + ByteArrayOutputStream o = new ByteArrayOutputStream(); + for (TreeEntry e : tree.members()) { + ObjectId id = e.getId(); + if (id == null) + throw new ObjectWritingException(MessageFormat.format(JGitText + .get().objectAtPathDoesNotHaveId, e.getFullName())); + + e.getMode().copyTo(o); + o.write(' '); + o.write(e.getNameUTF8()); + o.write(0); + id.copyRawTo(o); + } + return o.toByteArray(); + } + + /** + * Format a Commit in canonical format. + * + * @param commit + * the commit object to format + * @return canonical encoding of the commit object. + * @throws UnsupportedEncodingException + * the commit's chosen encoding isn't supported on this JVM. + */ + public final byte[] format(Commit commit) + throws UnsupportedEncodingException { + String encoding = commit.getEncoding(); + if (encoding == null) + encoding = Constants.CHARACTER_ENCODING; + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + OutputStreamWriter w = new OutputStreamWriter(os, encoding); + try { + os.write(htree); + os.write(' '); + commit.getTreeId().copyTo(os); + os.write('\n'); + + ObjectId[] ps = commit.getParentIds(); + for (int i = 0; i < ps.length; ++i) { + os.write(hparent); + os.write(' '); + ps[i].copyTo(os); + os.write('\n'); + } + + os.write(hauthor); + os.write(' '); + w.write(commit.getAuthor().toExternalString()); + w.flush(); + os.write('\n'); + + os.write(hcommitter); + os.write(' '); + w.write(commit.getCommitter().toExternalString()); + w.flush(); + os.write('\n'); + + if (!encoding.equals(Constants.CHARACTER_ENCODING)) { + os.write(hencoding); + os.write(' '); + os.write(Constants.encodeASCII(encoding)); + os.write('\n'); + } + + os.write('\n'); + w.write(commit.getMessage()); + w.flush(); + } catch (IOException err) { + // This should never occur, the only way to get it above is + // for the ByteArrayOutputStream to throw, but it doesn't. + // + throw new RuntimeException(err); + } + return os.toByteArray(); + } + + /** + * Format a Tag in canonical format. + * + * @param tag + * the tag object to format + * @return canonical encoding of the tag object. + */ + public final byte[] format(Tag tag) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + OutputStreamWriter w = new OutputStreamWriter(os, Constants.CHARSET); + try { + w.write("object "); + tag.getObjId().copyTo(w); + w.write('\n'); + + w.write("type "); + w.write(tag.getType()); + w.write("\n"); + + w.write("tag "); + w.write(tag.getTag()); + w.write("\n"); + + w.write("tagger "); + w.write(tag.getAuthor().toExternalString()); + w.write('\n'); + + w.write('\n'); + w.write(tag.getMessage()); + w.close(); + } catch (IOException err) { + // This should never occur, the only way to get it above is + // for the ByteArrayOutputStream to throw, but it doesn't. + // + throw new RuntimeException(err); + } + return os.toByteArray(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java index 4839a1c28b..e19bfc4fba 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java @@ -47,6 +47,12 @@ package org.eclipse.jgit.lib; +import java.io.EOFException; +import java.io.IOException; +import java.io.OutputStream; + +import org.eclipse.jgit.errors.LargeObjectException; +import org.eclipse.jgit.errors.MissingObjectException; /** * Base class for a set of loaders for different representations of Git objects. @@ -54,6 +60,14 @@ package org.eclipse.jgit.lib; */ public abstract class ObjectLoader { /** + * Default setting for the large object threshold. + * <p> + * Objects larger than this size must be accessed as a stream through the + * loader's {@link #openStream()} method. + */ + public static final int STREAM_THRESHOLD = 1024 * 1024; + + /** * @return Git in pack object type, see {@link Constants}. */ public abstract int getType(); @@ -64,14 +78,32 @@ public abstract class ObjectLoader { public abstract long getSize(); /** + * @return true if this object is too large to obtain as a byte array. + * Objects over a certain threshold should be accessed only by their + * {@link #openStream()} to prevent overflowing the JVM heap. + */ + public boolean isLarge() { + try { + getCachedBytes(); + return false; + } catch (LargeObjectException tooBig) { + return true; + } + } + + /** * Obtain a copy of the bytes of this object. * <p> * Unlike {@link #getCachedBytes()} this method returns an array that might * be modified by the caller. * * @return the bytes of this object. + * @throws LargeObjectException + * if the object won't fit into a byte array, because + * {@link #isLarge()} returns true. Callers should use + * {@link #openStream()} instead to access the contents. */ - public final byte[] getBytes() { + public final byte[] getBytes() throws LargeObjectException { final byte[] data = getCachedBytes(); final byte[] copy = new byte[data.length]; System.arraycopy(data, 0, copy, 0, data.length); @@ -87,19 +119,120 @@ public abstract class ObjectLoader { * Changes (if made) will affect the cache but not the repository itself. * * @return the cached bytes of this object. Do not modify it. + * @throws LargeObjectException + * if the object won't fit into a byte array, because + * {@link #isLarge()} returns true. Callers should use + * {@link #openStream()} instead to access the contents. + */ + public abstract byte[] getCachedBytes() throws LargeObjectException; + + /** + * Obtain an input stream to read this object's data. + * + * @return a stream of this object's data. Caller must close the stream when + * through with it. The returned stream is buffered with a + * reasonable buffer size. + * @throws MissingObjectException + * the object no longer exists. + * @throws IOException + * the object store cannot be accessed. */ - public abstract byte[] getCachedBytes(); + public abstract ObjectStream openStream() throws MissingObjectException, + IOException; /** - * @return raw object type from object header, as stored in storage (pack, - * loose file). This may be different from {@link #getType()} result - * for packs (see {@link Constants}). + * Copy this object to the output stream. + * <p> + * For some object store implementations, this method may be more efficient + * than reading from {@link #openStream()} into a temporary byte array, then + * writing to the destination stream. + * <p> + * The default implementation of this method is to copy with a temporary + * byte array for large objects, or to pass through the cached byte array + * for small objects. + * + * @param out + * stream to receive the complete copy of this object's data. + * Caller is responsible for flushing or closing this stream + * after this method returns. + * @throws MissingObjectException + * the object no longer exists. + * @throws IOException + * the object store cannot be accessed, or the stream cannot be + * written to. */ - public abstract int getRawType(); + public void copyTo(OutputStream out) throws MissingObjectException, + IOException { + if (isLarge()) { + ObjectStream in = openStream(); + try { + byte[] tmp = new byte[1024]; + long copied = 0; + for (;;) { + int n = in.read(tmp); + if (n < 0) + break; + out.write(tmp, 0, n); + copied += n; + } + if (copied != getSize()) + throw new EOFException(); + } finally { + in.close(); + } + } else { + out.write(getCachedBytes()); + } + } /** - * @return raw size of object from object header (pack, loose file). - * Interpretation of this value depends on {@link #getRawType()}. + * Simple loader around the cached byte array. + * <p> + * ObjectReader implementations can use this stream type when the object's + * content is small enough to be accessed as a single byte array. */ - public abstract long getRawSize(); + public static class SmallObject extends ObjectLoader { + private final int type; + + private final byte[] data; + + /** + * Construct a small object loader. + * + * @param type + * type of the object. + * @param data + * the object's data array. This array will be returned as-is + * for the {@link #getCachedBytes()} method. + */ + public SmallObject(int type, byte[] data) { + this.type = type; + this.data = data; + } + + @Override + public int getType() { + return type; + } + + @Override + public long getSize() { + return getCachedBytes().length; + } + + @Override + public boolean isLarge() { + return false; + } + + @Override + public byte[] getCachedBytes() { + return data; + } + + @Override + public ObjectStream openStream() { + return new ObjectStream.SmallStream(this); + } + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java new file mode 100644 index 0000000000..ae70638672 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java @@ -0,0 +1,185 @@ +/* + * 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; + +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.storage.pack.ObjectReuseAsIs; + +/** + * Reads an {@link ObjectDatabase} for a single thread. + * <p> + * Readers that can support efficient reuse of pack encoded objects should also + * implement the companion interface {@link ObjectReuseAsIs}. + */ +public abstract class ObjectReader { + /** Type hint indicating the caller doesn't know the type. */ + protected static final int OBJ_ANY = -1; + + /** + * Construct a new reader from the same data. + * <p> + * Applications can use this method to build a new reader from the same data + * source, but for an different thread. + * + * @return a brand new reader, using the same data source. + */ + public abstract ObjectReader newReader(); + + /** + * Does the requested object exist in this database? + * + * @param objectId + * identity of the object to test for existence of. + * @return true if the specified object is stored in this database. + * @throws IOException + * the object store cannot be accessed. + */ + public boolean has(AnyObjectId objectId) throws IOException { + return has(objectId, OBJ_ANY); + } + + /** + * Does the requested object exist in this database? + * + * @param objectId + * identity of the object to test for existence of. + * @param typeHint + * hint about the type of object being requested; + * {@link #OBJ_ANY} if the object type is not known, or does not + * matter to the caller. + * @return true if the specified object is stored in this database. + * @throws IncorrectObjectTypeException + * typeHint was not OBJ_ANY, and the object's actual type does + * not match typeHint. + * @throws IOException + * the object store cannot be accessed. + */ + public boolean has(AnyObjectId objectId, int typeHint) throws IOException { + try { + open(objectId, typeHint); + return true; + } catch (MissingObjectException notFound) { + return false; + } + } + + /** + * Open an object from this database. + * + * @param objectId + * identity of the object to open. + * @return a {@link ObjectLoader} for accessing the object. + * @throws MissingObjectException + * the object does not exist. + * @throws IOException + * the object store cannot be accessed. + */ + public ObjectLoader open(AnyObjectId objectId) + throws MissingObjectException, IOException { + return open(objectId, OBJ_ANY); + } + + /** + * Open an object from this database. + * + * @param objectId + * identity of the object to open. + * @param typeHint + * hint about the type of object being requested; + * {@link #OBJ_ANY} if the object type is not known, or does not + * matter to the caller. + * @return a {@link ObjectLoader} for accessing the object. + * @throws MissingObjectException + * the object does not exist. + * @throws IncorrectObjectTypeException + * typeHint was not OBJ_ANY, and the object's actual type does + * not match typeHint. + * @throws IOException + * the object store cannot be accessed. + */ + public abstract ObjectLoader open(AnyObjectId objectId, int typeHint) + throws MissingObjectException, IncorrectObjectTypeException, + IOException; + + /** + * Get only the size of an object. + * <p> + * The default implementation of this method opens an ObjectLoader. + * Databases are encouraged to override this if a faster access method is + * available to them. + * + * @param objectId + * identity of the object to open. + * @param typeHint + * hint about the type of object being requested; + * {@link #OBJ_ANY} if the object type is not known, or does not + * matter to the caller. + * @return size of object in bytes. + * @throws MissingObjectException + * the object does not exist. + * @throws IncorrectObjectTypeException + * typeHint was not OBJ_ANY, and the object's actual type does + * not match typeHint. + * @throws IOException + * the object store cannot be accessed. + */ + public long getObjectSize(AnyObjectId objectId, int typeHint) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + return open(objectId, typeHint).getSize(); + } + + /** + * Release any resources used by this reader. + * <p> + * A reader that has been released can be used again, but may need to be + * released after the subsequent usage. + */ + public void release() { + // Do nothing. + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectStream.java new file mode 100644 index 0000000000..86d66439d0 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectStream.java @@ -0,0 +1,220 @@ +/* + * 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; +import java.io.InputStream; + +/** Stream of data coming from an object loaded by {@link ObjectLoader}. */ +public abstract class ObjectStream extends InputStream { + /** @return Git object type, see {@link Constants}. */ + public abstract int getType(); + + /** @return total size of object in bytes */ + public abstract long getSize(); + + /** + * Simple stream around the cached byte array created by a loader. + * <p> + * ObjectLoader implementations can use this stream type when the object's + * content is small enough to be accessed as a single byte array, but the + * application has still requested it in stream format. + */ + public static class SmallStream extends ObjectStream { + private final int type; + + private final byte[] data; + + private int ptr; + + private int mark; + + /** + * Create the stream from an existing loader's cached bytes. + * + * @param loader + * the loader. + */ + public SmallStream(ObjectLoader loader) { + this.type = loader.getType(); + this.data = loader.getCachedBytes(); + } + + @Override + public int getType() { + return type; + } + + @Override + public long getSize() { + return data.length; + } + + @Override + public int available() { + return data.length - ptr; + } + + @Override + public long skip(long n) { + int s = (int) Math.min(available(), Math.max(0, n)); + ptr += s; + return s; + } + + @Override + public int read() { + if (ptr == data.length) + return -1; + return data[ptr++] & 0xff; + } + + @Override + public int read(byte[] b, int off, int len) { + if (ptr == data.length) + return -1; + int n = Math.min(available(), len); + System.arraycopy(data, ptr, b, off, n); + ptr += n; + return n; + } + + @Override + public boolean markSupported() { + return true; + } + + @Override + public void mark(int readlimit) { + mark = ptr; + } + + @Override + public void reset() { + ptr = mark; + } + } + + /** + * Simple filter stream around another stream. + * <p> + * ObjectLoader implementations can use this stream type when the object's + * content is available from a standard InputStream. + */ + public static class Filter extends ObjectStream { + private final int type; + + private final long size; + + private final InputStream in; + + /** + * Create a filter stream for an object. + * + * @param type + * the type of the object. + * @param size + * total size of the object, in bytes. + * @param in + * stream the object's raw data is available from. This + * stream should be buffered with some reasonable amount of + * buffering. + */ + public Filter(int type, long size, InputStream in) { + this.type = type; + this.size = size; + this.in = in; + } + + @Override + public int getType() { + return type; + } + + @Override + public long getSize() { + return size; + } + + @Override + public int available() throws IOException { + return in.available(); + } + + @Override + public long skip(long n) throws IOException { + return in.skip(n); + } + + @Override + public int read() throws IOException { + return in.read(); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return in.read(b, off, len); + } + + @Override + public boolean markSupported() { + return in.markSupported(); + } + + @Override + public void mark(int readlimit) { + in.mark(readlimit); + } + + @Override + public void reset() throws IOException { + in.reset(); + } + + @Override + public void close() throws IOException { + in.close(); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java index 20147ed6ce..ce91efb8e4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java @@ -1,6 +1,7 @@ /* * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> + * 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 @@ -44,61 +45,49 @@ package org.eclipse.jgit.lib; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; +import static org.eclipse.jgit.lib.Constants.OBJ_TAG; +import static org.eclipse.jgit.lib.Constants.OBJ_TREE; + import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStreamWriter; -import java.security.MessageDigest; -import java.text.MessageFormat; -import java.util.zip.Deflater; -import java.util.zip.DeflaterOutputStream; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.ObjectWritingException; /** * A class for writing loose objects. + * + * @deprecated Use {@link Repository#newObjectInserter()}. */ public class ObjectWriter { - private static final byte[] htree = Constants.encodeASCII("tree"); - - private static final byte[] hparent = Constants.encodeASCII("parent"); - - private static final byte[] hauthor = Constants.encodeASCII("author"); - - private static final byte[] hcommitter = Constants.encodeASCII("committer"); - - private static final byte[] hencoding = Constants.encodeASCII("encoding"); - - private final Repository r; - - private final byte[] buf; - - private final MessageDigest md; + private final ObjectInserter inserter; /** * Construct an Object writer for the specified repository + * * @param d */ public ObjectWriter(final Repository d) { - r = d; - buf = new byte[8192]; - md = Constants.newMessageDigest(); + inserter = d.newObjectInserter(); } /** * Write a blob with the specified data * - * @param b bytes of the blob + * @param b + * bytes of the blob * @return SHA-1 of the blob * @throws IOException */ public ObjectId writeBlob(final byte[] b) throws IOException { - return writeBlob(b.length, new ByteArrayInputStream(b)); + try { + ObjectId id = inserter.insert(OBJ_BLOB, b); + inserter.flush(); + return id; + } finally { + inserter.release(); + } } /** @@ -130,174 +119,101 @@ public class ObjectWriter { */ public ObjectId writeBlob(final long len, final InputStream is) throws IOException { - return writeObject(Constants.OBJ_BLOB, len, is, true); + try { + ObjectId id = inserter.insert(OBJ_BLOB, len, is); + inserter.flush(); + return id; + } finally { + inserter.release(); + } } /** * Write a Tree to the object database. * - * @param t + * @param tree * Tree * @return SHA-1 of the tree * @throws IOException */ - public ObjectId writeTree(final Tree t) throws IOException { - final ByteArrayOutputStream o = new ByteArrayOutputStream(); - final TreeEntry[] items = t.members(); - for (int k = 0; k < items.length; k++) { - final TreeEntry e = items[k]; - final ObjectId id = e.getId(); - - if (id == null) - throw new ObjectWritingException(MessageFormat.format( - JGitText.get().objectAtPathDoesNotHaveId, e.getFullName())); - - e.getMode().copyTo(o); - o.write(' '); - o.write(e.getNameUTF8()); - o.write(0); - id.copyRawTo(o); + public ObjectId writeTree(Tree tree) throws IOException { + try { + ObjectId id = inserter.insert(OBJ_TREE, inserter.format(tree)); + inserter.flush(); + return id; + } finally { + inserter.release(); } - return writeCanonicalTree(o.toByteArray()); } /** * Write a canonical tree to the object database. * - * @param b + * @param treeData * the canonical encoding of the tree object. * @return SHA-1 of the tree * @throws IOException */ - public ObjectId writeCanonicalTree(final byte[] b) throws IOException { - return writeTree(b.length, new ByteArrayInputStream(b)); - } - - private ObjectId writeTree(final long len, final InputStream is) - throws IOException { - return writeObject(Constants.OBJ_TREE, len, is, true); + public ObjectId writeCanonicalTree(byte[] treeData) throws IOException { + try { + ObjectId id = inserter.insert(OBJ_TREE, treeData); + inserter.flush(); + return id; + } finally { + inserter.release(); + } } /** * Write a Commit to the object database * - * @param c + * @param commit * Commit to store * @return SHA-1 of the commit * @throws IOException */ - public ObjectId writeCommit(final Commit c) throws IOException { - final ByteArrayOutputStream os = new ByteArrayOutputStream(); - String encoding = c.getEncoding(); - if (encoding == null) - encoding = Constants.CHARACTER_ENCODING; - final OutputStreamWriter w = new OutputStreamWriter(os, encoding); - - os.write(htree); - os.write(' '); - c.getTreeId().copyTo(os); - os.write('\n'); - - ObjectId[] ps = c.getParentIds(); - for (int i=0; i<ps.length; ++i) { - os.write(hparent); - os.write(' '); - ps[i].copyTo(os); - os.write('\n'); - } - - os.write(hauthor); - os.write(' '); - w.write(c.getAuthor().toExternalString()); - w.flush(); - os.write('\n'); - - os.write(hcommitter); - os.write(' '); - w.write(c.getCommitter().toExternalString()); - w.flush(); - os.write('\n'); - - if (!encoding.equals(Constants.CHARACTER_ENCODING)) { - os.write(hencoding); - os.write(' '); - os.write(Constants.encodeASCII(encoding)); - os.write('\n'); + public ObjectId writeCommit(Commit commit) throws IOException { + try { + ObjectId id = inserter.insert(OBJ_COMMIT, inserter.format(commit)); + inserter.flush(); + return id; + } finally { + inserter.release(); } - - os.write('\n'); - w.write(c.getMessage()); - w.flush(); - - return writeCommit(os.toByteArray()); - } - - private ObjectId writeTag(final byte[] b) throws IOException { - return writeTag(b.length, new ByteArrayInputStream(b)); } /** * Write an annotated Tag to the object database * - * @param c + * @param tag * Tag * @return SHA-1 of the tag * @throws IOException */ - public ObjectId writeTag(final Tag c) throws IOException { - final ByteArrayOutputStream os = new ByteArrayOutputStream(); - final OutputStreamWriter w = new OutputStreamWriter(os, - Constants.CHARSET); - - w.write("object "); - c.getObjId().copyTo(w); - w.write('\n'); - - w.write("type "); - w.write(c.getType()); - w.write("\n"); - - w.write("tag "); - w.write(c.getTag()); - w.write("\n"); - - w.write("tagger "); - w.write(c.getAuthor().toExternalString()); - w.write('\n'); - - w.write('\n'); - w.write(c.getMessage()); - w.close(); - - return writeTag(os.toByteArray()); - } - - private ObjectId writeCommit(final byte[] b) throws IOException { - return writeCommit(b.length, new ByteArrayInputStream(b)); - } - - private ObjectId writeCommit(final long len, final InputStream is) - throws IOException { - return writeObject(Constants.OBJ_COMMIT, len, is, true); - } - - private ObjectId writeTag(final long len, final InputStream is) - throws IOException { - return writeObject(Constants.OBJ_TAG, len, is, true); + public ObjectId writeTag(Tag tag) throws IOException { + try { + ObjectId id = inserter.insert(OBJ_TAG, inserter.format(tag)); + inserter.flush(); + return id; + } finally { + inserter.release(); + } } /** * Compute the SHA-1 of a blob without creating an object. This is for * figuring out if we already have a blob or not. * - * @param len number of bytes to consume - * @param is stream for read blob data from + * @param len + * number of bytes to consume + * @param is + * stream for read blob data from * @return SHA-1 of a looked for blob * @throws IOException */ - public ObjectId computeBlobSha1(final long len, final InputStream is) + public ObjectId computeBlobSha1(long len, InputStream is) throws IOException { - return writeObject(Constants.OBJ_BLOB, len, is, false); + return computeObjectSha1(OBJ_BLOB, len, is); } /** @@ -313,119 +229,12 @@ public class ObjectWriter { * @return SHA-1 of data combined with type information * @throws IOException */ - public ObjectId computeObjectSha1(final int type, final long len, final InputStream is) + public ObjectId computeObjectSha1(int type, long len, InputStream is) throws IOException { - return writeObject(type, len, is, false); - } - - ObjectId writeObject(final int type, long len, final InputStream is, - boolean store) throws IOException { - final File t; - final DeflaterOutputStream deflateStream; - final FileOutputStream fileStream; - ObjectId id = null; - Deflater def = null; - - if (store) { - t = File.createTempFile("noz", null, r.getObjectsDirectory()); - fileStream = new FileOutputStream(t); - } else { - t = null; - fileStream = null; - } - - md.reset(); - if (store) { - def = new Deflater(r.getConfig().getCore().getCompression()); - deflateStream = new DeflaterOutputStream(fileStream, def); - } else - deflateStream = null; - try { - byte[] header; - int n; - - header = Constants.encodedTypeString(type); - md.update(header); - if (deflateStream != null) - deflateStream.write(header); - - md.update((byte) ' '); - if (deflateStream != null) - deflateStream.write((byte) ' '); - - header = Constants.encodeASCII(len); - md.update(header); - if (deflateStream != null) - deflateStream.write(header); - - md.update((byte) 0); - if (deflateStream != null) - deflateStream.write((byte) 0); - - while (len > 0 - && (n = is.read(buf, 0, (int) Math.min(len, buf.length))) > 0) { - md.update(buf, 0, n); - if (deflateStream != null) - deflateStream.write(buf, 0, n); - len -= n; - } - - if (len != 0) - throw new IOException("Input did not match supplied length. " - + len + " bytes are missing."); - - if (deflateStream != null ) { - deflateStream.close(); - if (t != null) - t.setReadOnly(); - } - - id = ObjectId.fromRaw(md.digest()); + return inserter.idFor(type, len, is); } finally { - if (id == null && deflateStream != null) { - try { - deflateStream.close(); - } finally { - t.delete(); - } - } - if (def != null) { - def.end(); - } + inserter.release(); } - - if (t == null) - return id; - - if (r.hasObject(id)) { - // Object is already in the repository so remove - // the temporary file. - // - t.delete(); - } else { - final File o = r.toFile(id); - if (!t.renameTo(o)) { - // Maybe the directory doesn't exist yet as the object - // directories are always lazily created. Note that we - // try the rename first as the directory likely does exist. - // - o.getParentFile().mkdir(); - if (!t.renameTo(o)) { - if (!r.hasObject(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. - // - t.delete(); - throw new ObjectWritingException("Unable to" - + " create new object: " + o); - } - } - } - } - - return id; } } 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 829832e6a5..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java +++ /dev/null @@ -1,526 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com> - * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -import java.io.EOFException; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -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.CheckedOutputStream; -import java.util.zip.DataFormatException; - -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.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<PackIndex.MutableEntry> { - /** Sorts PackFiles to be most recently created to least recently created. */ - public static Comparator<PackFile> SORT = new Comparator<PackFile>() { - 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; - - /** - * Construct a reader for an existing, pre-indexed packfile. - * - * @param idxFile - * path of the <code>.idx</code> file listing the contents. - * @param packFile - * path of the <code>.pack</code> 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 { - 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. - * <p> - * For performance reasons only the index file is searched; the main pack - * content is ignored entirely. - * </p> - * - * @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 { - return idx().hasObject(id); - } - - /** - * 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 ? 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. - * <p> - * Iterator returns objects in SHA-1 lexicographical order. - * </p> - * - * @return iterator over entries of associated pack index - * - * @see PackIndex#iterator() - */ - public Iterator<PackIndex.MutableEntry> iterator() { - try { - return idx().iterator(); - } catch (IOException e) { - return Collections.<PackIndex.MutableEntry> 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 copyRawData(final PackedObjectLoader loader, - final OutputStream out, final byte buf[], final WindowCursor curs) - throws IOException { - final long objectOffset = loader.objectOffset; - final long dataOffset = objectOffset + loader.headerSize; - final long sz = findEndOffset(objectOffset) - dataOffset; - final PackIndex idx = idx(); - - if (idx.hasCRC32Support()) { - final CRC32 crc = new CRC32(); - int headerCnt = loader.headerSize; - while (headerCnt > 0) { - final int toRead = Math.min(headerCnt, buf.length); - readFully(objectOffset, buf, 0, toRead, curs); - crc.update(buf, 0, toRead); - headerCnt -= toRead; - } - final CheckedOutputStream crcOut = new CheckedOutputStream(out, crc); - copyToStream(dataOffset, buf, sz, crcOut, curs); - final long computed = crc.getValue(); - - final ObjectId id = findObjectForOffset(objectOffset); - final long expected = idx.findCRC32(id); - if (computed != expected) - throw new CorruptObjectException(MessageFormat.format( - JGitText.get().objectAtHasBadZlibStream, objectOffset, getPackFile())); - } else { - try { - curs.inflateVerify(this, dataOffset); - } catch (DataFormatException dfe) { - final CorruptObjectException coe; - coe = new CorruptObjectException(MessageFormat.format( - JGitText.get().objectAtHasBadZlibStream, objectOffset, getPackFile())); - coe.initCause(dfe); - throw coe; - } - copyToStream(dataOffset, buf, sz, out, curs); - } - } - - boolean supportsFastCopyRawData() throws IOException { - return idx().hasCRC32Support(); - } - - 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 void copyToStream(long position, final byte[] buf, long cnt, - final OutputStream out, final WindowCursor curs) - throws IOException, EOFException { - while (cnt > 0) { - final int toRead = (int) Math.min(cnt, buf.length); - readFully(position, buf, 0, toRead, curs); - position += toRead; - cnt -= toRead; - out.write(buf, 0, toRead); - } - } - - synchronized void beginCopyRawData() throws IOException { - if (++activeCopyRawData == 1 && activeWindows == 0) - doOpen(); - } - - synchronized void endCopyRawData() { - 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; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java deleted file mode 100644 index 48f41a5586..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java +++ /dev/null @@ -1,1040 +0,0 @@ -/* - * Copyright (C) 2008-2010, Google Inc. - * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> - * 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.security.MessageDigest; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.zip.Deflater; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.IncorrectObjectTypeException; -import org.eclipse.jgit.errors.MissingObjectException; -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.transport.PackedObjectInfo; -import org.eclipse.jgit.util.NB; - -/** - * <p> - * PackWriter class is responsible for generating pack files from specified set - * of objects from repository. This implementation produce pack files in format - * version 2. - * </p> - * <p> - * Source of objects may be specified in two ways: - * <ul> - * <li>(usually) by providing sets of interesting and uninteresting objects in - * repository - all interesting objects and their ancestors except uninteresting - * objects and their ancestors will be included in pack, or</li> - * <li>by providing iterator of {@link RevObject} specifying exact list and - * order of objects in pack</li> - * </ul> - * Typical usage consists of creating instance intended for some pack, - * configuring options, preparing the list of objects by calling - * {@link #preparePack(Iterator)} or - * {@link #preparePack(Collection, Collection)}, and finally - * producing the stream with {@link #writePack(OutputStream)}. - * </p> - * <p> - * Class provide set of configurable options and {@link ProgressMonitor} - * support, as operations may take a long time for big repositories. Deltas - * searching algorithm is <b>NOT IMPLEMENTED</b> yet - this implementation - * relies only on deltas and objects reuse. - * </p> - * <p> - * This class is not thread safe, it is intended to be used in one thread, with - * one instance per created pack. Subsequent calls to writePack result in - * undefined behavior. - * </p> - */ -public class PackWriter { - /** - * Title of {@link ProgressMonitor} task used during counting objects to - * pack. - * - * @see #preparePack(Collection, Collection) - */ - public static final String COUNTING_OBJECTS_PROGRESS = JGitText.get().countingObjects; - - /** - * Title of {@link ProgressMonitor} task used during searching for objects - * reuse or delta reuse. - * - * @see #writePack(OutputStream) - */ - public static final String SEARCHING_REUSE_PROGRESS = JGitText.get().compressingObjects; - - /** - * Title of {@link ProgressMonitor} task used during writing out pack - * (objects) - * - * @see #writePack(OutputStream) - */ - public static final String WRITING_OBJECTS_PROGRESS = JGitText.get().writingObjects; - - /** - * Default value of deltas reuse option. - * - * @see #setReuseDeltas(boolean) - */ - public static final boolean DEFAULT_REUSE_DELTAS = true; - - /** - * Default value of objects reuse option. - * - * @see #setReuseObjects(boolean) - */ - public static final boolean DEFAULT_REUSE_OBJECTS = true; - - /** - * Default value of delta base as offset option. - * - * @see #setDeltaBaseAsOffset(boolean) - */ - public static final boolean DEFAULT_DELTA_BASE_AS_OFFSET = false; - - /** - * Default value of maximum delta chain depth. - * - * @see #setMaxDeltaDepth(int) - */ - public static final int DEFAULT_MAX_DELTA_DEPTH = 50; - - private static final int PACK_VERSION_GENERATED = 2; - - @SuppressWarnings("unchecked") - private final List<ObjectToPack> objectsLists[] = new List[Constants.OBJ_TAG + 1]; - { - objectsLists[0] = Collections.<ObjectToPack> emptyList(); - objectsLists[Constants.OBJ_COMMIT] = new ArrayList<ObjectToPack>(); - objectsLists[Constants.OBJ_TREE] = new ArrayList<ObjectToPack>(); - objectsLists[Constants.OBJ_BLOB] = new ArrayList<ObjectToPack>(); - objectsLists[Constants.OBJ_TAG] = new ArrayList<ObjectToPack>(); - } - - private final ObjectIdSubclassMap<ObjectToPack> objectsMap = new ObjectIdSubclassMap<ObjectToPack>(); - - // edge objects for thin packs - private final ObjectIdSubclassMap<ObjectId> edgeObjects = new ObjectIdSubclassMap<ObjectId>(); - - private final Repository db; - - private PackOutputStream out; - - private final Deflater deflater; - - private ProgressMonitor initMonitor; - - private ProgressMonitor writeMonitor; - - private final byte[] buf = new byte[16384]; // 16 KB - - private final WindowCursor windowCursor = new WindowCursor(); - - private List<ObjectToPack> sortedByName; - - private byte packcsum[]; - - private boolean reuseDeltas = DEFAULT_REUSE_DELTAS; - - private boolean reuseObjects = DEFAULT_REUSE_OBJECTS; - - private boolean deltaBaseAsOffset = DEFAULT_DELTA_BASE_AS_OFFSET; - - private int maxDeltaDepth = DEFAULT_MAX_DELTA_DEPTH; - - private int outputVersion; - - private boolean thin; - - private boolean ignoreMissingUninteresting = true; - - /** - * Create writer for specified repository. - * <p> - * Objects for packing are specified in {@link #preparePack(Iterator)} or - * {@link #preparePack(Collection, Collection)}. - * - * @param repo - * repository where objects are stored. - * @param monitor - * operations progress monitor, used within - * {@link #preparePack(Iterator)}, - * {@link #preparePack(Collection, Collection)} - * , or {@link #writePack(OutputStream)}. - */ - public PackWriter(final Repository repo, final ProgressMonitor monitor) { - this(repo, monitor, monitor); - } - - /** - * Create writer for specified repository. - * <p> - * Objects for packing are specified in {@link #preparePack(Iterator)} or - * {@link #preparePack(Collection, Collection)}. - * - * @param repo - * repository where objects are stored. - * @param imonitor - * operations progress monitor, used within - * {@link #preparePack(Iterator)}, - * {@link #preparePack(Collection, Collection)} - * @param wmonitor - * operations progress monitor, used within - * {@link #writePack(OutputStream)}. - */ - public PackWriter(final Repository repo, final ProgressMonitor imonitor, - final ProgressMonitor wmonitor) { - this.db = repo; - initMonitor = imonitor == null ? NullProgressMonitor.INSTANCE : imonitor; - writeMonitor = wmonitor == null ? NullProgressMonitor.INSTANCE : wmonitor; - this.deflater = new Deflater(db.getConfig().getCore().getCompression()); - outputVersion = repo.getConfig().getCore().getPackIndexVersion(); - } - - /** - * Check whether object is configured to reuse deltas existing in - * repository. - * <p> - * Default setting: {@value #DEFAULT_REUSE_DELTAS} - * </p> - * - * @return true if object is configured to reuse deltas; false otherwise. - */ - public boolean isReuseDeltas() { - return reuseDeltas; - } - - /** - * Set reuse deltas configuration option for this writer. When enabled, - * writer will search for delta representation of object in repository and - * use it if possible. Normally, only deltas with base to another object - * existing in set of objects to pack will be used. Exception is however - * thin-pack (see - * {@link #preparePack(Collection, Collection)} and - * {@link #preparePack(Iterator)}) where base object must exist on other - * side machine. - * <p> - * When raw delta data is directly copied from a pack file, checksum is - * computed to verify data. - * </p> - * <p> - * Default setting: {@value #DEFAULT_REUSE_DELTAS} - * </p> - * - * @param reuseDeltas - * boolean indicating whether or not try to reuse deltas. - */ - public void setReuseDeltas(boolean reuseDeltas) { - this.reuseDeltas = reuseDeltas; - } - - /** - * Checks whether object is configured to reuse existing objects - * representation in repository. - * <p> - * Default setting: {@value #DEFAULT_REUSE_OBJECTS} - * </p> - * - * @return true if writer is configured to reuse objects representation from - * pack; false otherwise. - */ - public boolean isReuseObjects() { - return reuseObjects; - } - - /** - * Set reuse objects configuration option for this writer. If enabled, - * writer searches for representation in a pack file. If possible, - * compressed data is directly copied from such a pack file. Data checksum - * is verified. - * <p> - * Default setting: {@value #DEFAULT_REUSE_OBJECTS} - * </p> - * - * @param reuseObjects - * boolean indicating whether or not writer should reuse existing - * objects representation. - */ - public void setReuseObjects(boolean reuseObjects) { - this.reuseObjects = reuseObjects; - } - - /** - * Check whether writer can store delta base as an offset (new style - * reducing pack size) or should store it as an object id (legacy style, - * compatible with old readers). - * <p> - * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET} - * </p> - * - * @return true if delta base is stored as an offset; false if it is stored - * as an object id. - */ - public boolean isDeltaBaseAsOffset() { - return deltaBaseAsOffset; - } - - /** - * Set writer delta base format. Delta base can be written as an offset in a - * pack file (new approach reducing file size) or as an object id (legacy - * approach, compatible with old readers). - * <p> - * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET} - * </p> - * - * @param deltaBaseAsOffset - * boolean indicating whether delta base can be stored as an - * offset. - */ - public void setDeltaBaseAsOffset(boolean deltaBaseAsOffset) { - this.deltaBaseAsOffset = deltaBaseAsOffset; - } - - /** - * Get maximum depth of delta chain set up for this writer. Generated chains - * are not longer than this value. - * <p> - * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH} - * </p> - * - * @return maximum delta chain depth. - */ - public int getMaxDeltaDepth() { - return maxDeltaDepth; - } - - /** - * Set up maximum depth of delta chain for this writer. Generated chains are - * not longer than this value. Too low value causes low compression level, - * while too big makes unpacking (reading) longer. - * <p> - * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH} - * </p> - * - * @param maxDeltaDepth - * maximum delta chain depth. - */ - public void setMaxDeltaDepth(int maxDeltaDepth) { - this.maxDeltaDepth = maxDeltaDepth; - } - - /** @return true if this writer is producing a thin pack. */ - public boolean isThin() { - return thin; - } - - /** - * @param packthin - * a boolean indicating whether writer may pack objects with - * delta base object not within set of objects to pack, but - * belonging to party repository (uninteresting/boundary) as - * determined by set; this kind of pack is used only for - * transport; true - to produce thin pack, false - otherwise. - */ - public void setThin(final boolean packthin) { - thin = packthin; - } - - /** - * @return true to ignore objects that are uninteresting and also not found - * on local disk; false to throw a {@link MissingObjectException} - * out of {@link #preparePack(Collection, Collection)} if an - * uninteresting object is not in the source repository. By default, - * true, permitting gracefully ignoring of uninteresting objects. - */ - public boolean isIgnoreMissingUninteresting() { - return ignoreMissingUninteresting; - } - - /** - * @param ignore - * true if writer should ignore non existing uninteresting - * objects during construction set of objects to pack; false - * otherwise - non existing uninteresting objects may cause - * {@link MissingObjectException} - */ - public void setIgnoreMissingUninteresting(final boolean ignore) { - ignoreMissingUninteresting = ignore; - } - - /** - * Set the pack index file format version this instance will create. - * - * @param version - * the version to write. The special version 0 designates the - * oldest (most compatible) format available for the objects. - * @see PackIndexWriter - */ - public void setIndexVersion(final int version) { - outputVersion = version; - } - - /** - * Returns objects number in a pack file that was created by this writer. - * - * @return number of objects in pack. - */ - public int getObjectsNumber() { - return objectsMap.size(); - } - - /** - * Prepare the list of objects to be written to the pack stream. - * <p> - * Iterator <b>exactly</b> determines which objects are included in a pack - * and order they appear in pack (except that objects order by type is not - * needed at input). This order should conform general rules of ordering - * objects in git - by recency and path (type and delta-base first is - * internally secured) and responsibility for guaranteeing this order is on - * a caller side. Iterator must return each id of object to write exactly - * once. - * </p> - * <p> - * When iterator returns object that has {@link RevFlag#UNINTERESTING} flag, - * this object won't be included in an output pack. Instead, it is recorded - * as edge-object (known to remote repository) for thin-pack. In such a case - * writer may pack objects with delta base object not within set of objects - * to pack, but belonging to party repository - those marked with - * {@link RevFlag#UNINTERESTING} flag. This type of pack is used only for - * transport. - * </p> - * - * @param objectsSource - * iterator of object to store in a pack; order of objects within - * each type is important, ordering by type is not needed; - * allowed types for objects are {@link Constants#OBJ_COMMIT}, - * {@link Constants#OBJ_TREE}, {@link Constants#OBJ_BLOB} and - * {@link Constants#OBJ_TAG}; objects returned by iterator may - * be later reused by caller as object id and type are internally - * copied in each iteration; if object returned by iterator has - * {@link RevFlag#UNINTERESTING} flag set, it won't be included - * in a pack, but is considered as edge-object for thin-pack. - * @throws IOException - * when some I/O problem occur during reading objects. - */ - public void preparePack(final Iterator<RevObject> objectsSource) - throws IOException { - while (objectsSource.hasNext()) { - addObject(objectsSource.next()); - } - } - - /** - * Prepare the list of objects to be written to the pack stream. - * <p> - * Basing on these 2 sets, another set of objects to put in a pack file is - * created: this set consists of all objects reachable (ancestors) from - * interesting objects, except uninteresting objects and their ancestors. - * This method uses class {@link ObjectWalk} extensively to find out that - * appropriate set of output objects and their optimal order in output pack. - * Order is consistent with general git in-pack rules: sort by object type, - * recency, path and delta-base first. - * </p> - * - * @param interestingObjects - * collection of objects to be marked as interesting (start - * points of graph traversal). - * @param uninterestingObjects - * collection of objects to be marked as uninteresting (end - * points of graph traversal). - * @throws IOException - * when some I/O problem occur during reading objects. - */ - public void preparePack( - final Collection<? extends ObjectId> interestingObjects, - final Collection<? extends ObjectId> uninterestingObjects) - throws IOException { - ObjectWalk walker = setUpWalker(interestingObjects, - uninterestingObjects); - findObjectsToPack(walker); - } - - /** - * Determine if the pack file will contain the requested object. - * - * @param id - * the object to test the existence of. - * @return true if the object will appear in the output pack file. - */ - public boolean willInclude(final AnyObjectId id) { - return objectsMap.get(id) != null; - } - - /** - * Computes SHA-1 of lexicographically sorted objects ids written in this - * pack, as used to name a pack file in repository. - * - * @return ObjectId representing SHA-1 name of a pack that was created. - */ - public ObjectId computeName() { - final MessageDigest md = Constants.newMessageDigest(); - for (ObjectToPack otp : sortByName()) { - otp.copyRawTo(buf, 0); - md.update(buf, 0, Constants.OBJECT_ID_LENGTH); - } - return ObjectId.fromRaw(md.digest()); - } - - /** - * Create an index file to match the pack file just written. - * <p> - * This method can only be invoked after {@link #preparePack(Iterator)} or - * {@link #preparePack(Collection, Collection)} has been - * invoked and completed successfully. Writing a corresponding index is an - * optional feature that not all pack users may require. - * - * @param indexStream - * output for the index data. Caller is responsible for closing - * this stream. - * @throws IOException - * the index data could not be written to the supplied stream. - */ - public void writeIndex(final OutputStream indexStream) throws IOException { - final List<ObjectToPack> list = sortByName(); - final PackIndexWriter iw; - if (outputVersion <= 0) - iw = PackIndexWriter.createOldestPossible(indexStream, list); - else - iw = PackIndexWriter.createVersion(indexStream, outputVersion); - iw.write(list, packcsum); - } - - private List<ObjectToPack> sortByName() { - if (sortedByName == null) { - sortedByName = new ArrayList<ObjectToPack>(objectsMap.size()); - for (List<ObjectToPack> list : objectsLists) { - for (ObjectToPack otp : list) - sortedByName.add(otp); - } - Collections.sort(sortedByName); - } - return sortedByName; - } - - /** - * Write the prepared pack to the supplied stream. - * <p> - * At first, this method collects and sorts objects to pack, then deltas - * search is performed if set up accordingly, finally pack stream is - * written. {@link ProgressMonitor} tasks {@value #SEARCHING_REUSE_PROGRESS} - * (only if reuseDeltas or reuseObjects is enabled) and - * {@value #WRITING_OBJECTS_PROGRESS} are updated during packing. - * </p> - * <p> - * All reused objects data checksum (Adler32/CRC32) is computed and - * validated against existing checksum. - * </p> - * - * @param packStream - * output stream of pack data. The stream should be buffered by - * the caller. The caller is responsible for closing the stream. - * @throws IOException - * an error occurred reading a local object's data to include in - * the pack, or writing compressed object data to the output - * stream. - */ - public void writePack(OutputStream packStream) throws IOException { - if (reuseDeltas || reuseObjects) - searchForReuse(); - - out = new PackOutputStream(packStream); - - writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber()); - writeHeader(); - writeObjects(); - writeChecksum(); - - windowCursor.release(); - writeMonitor.endTask(); - } - - private void searchForReuse() throws IOException { - initMonitor.beginTask(SEARCHING_REUSE_PROGRESS, getObjectsNumber()); - final Collection<PackedObjectLoader> reuseLoaders = new ArrayList<PackedObjectLoader>(); - for (List<ObjectToPack> list : objectsLists) { - for (ObjectToPack otp : list) { - if (initMonitor.isCancelled()) - throw new IOException( - JGitText.get().packingCancelledDuringObjectsWriting); - reuseLoaders.clear(); - searchForReuse(reuseLoaders, otp); - initMonitor.update(1); - } - } - - initMonitor.endTask(); - } - - private void searchForReuse( - final Collection<PackedObjectLoader> reuseLoaders, - final ObjectToPack otp) throws IOException { - db.openObjectInAllPacks(otp, reuseLoaders, windowCursor); - if (reuseDeltas) { - selectDeltaReuseForObject(otp, reuseLoaders); - } - // delta reuse is preferred over object reuse - if (reuseObjects && !otp.isCopyable()) { - selectObjectReuseForObject(otp, reuseLoaders); - } - } - - private void selectDeltaReuseForObject(final ObjectToPack otp, - final Collection<PackedObjectLoader> loaders) throws IOException { - PackedObjectLoader bestLoader = null; - ObjectId bestBase = null; - - for (PackedObjectLoader loader : loaders) { - ObjectId idBase = loader.getDeltaBase(); - if (idBase == null) - continue; - ObjectToPack otpBase = objectsMap.get(idBase); - - // only if base is in set of objects to write or thin-pack's edge - if ((otpBase != null || (thin && edgeObjects.get(idBase) != null)) - // select smallest possible delta if > 1 available - && isBetterDeltaReuseLoader(bestLoader, loader)) { - bestLoader = loader; - bestBase = (otpBase != null ? otpBase : idBase); - } - } - - if (bestLoader != null) { - otp.setCopyFromPack(bestLoader); - otp.setDeltaBase(bestBase); - } - } - - private static boolean isBetterDeltaReuseLoader( - PackedObjectLoader currentLoader, PackedObjectLoader loader) - throws IOException { - if (currentLoader == null) - return true; - if (loader.getRawSize() < currentLoader.getRawSize()) - return true; - return (loader.getRawSize() == currentLoader.getRawSize() - && loader.supportsFastCopyRawData() && !currentLoader - .supportsFastCopyRawData()); - } - - private void selectObjectReuseForObject(final ObjectToPack otp, - final Collection<PackedObjectLoader> loaders) { - for (final PackedObjectLoader loader : loaders) { - if (loader instanceof WholePackedObjectLoader) { - otp.setCopyFromPack(loader); - return; - } - } - } - - private void writeHeader() throws IOException { - System.arraycopy(Constants.PACK_SIGNATURE, 0, buf, 0, 4); - NB.encodeInt32(buf, 4, PACK_VERSION_GENERATED); - NB.encodeInt32(buf, 8, getObjectsNumber()); - out.write(buf, 0, 12); - } - - private void writeObjects() throws IOException { - for (List<ObjectToPack> list : objectsLists) { - for (ObjectToPack otp : list) { - if (writeMonitor.isCancelled()) - throw new IOException( - JGitText.get().packingCancelledDuringObjectsWriting); - if (!otp.isWritten()) - writeObject(otp); - } - } - } - - private void writeObject(final ObjectToPack otp) throws IOException { - otp.markWantWrite(); - if (otp.isDeltaRepresentation()) { - ObjectToPack deltaBase = otp.getDeltaBase(); - assert deltaBase != null || thin; - if (deltaBase != null && !deltaBase.isWritten()) { - if (deltaBase.wantWrite()) { - otp.clearDeltaBase(); // cycle detected - otp.clearSourcePack(); - } else { - writeObject(deltaBase); - } - } - } - - assert !otp.isWritten(); - - out.resetCRC32(); - otp.setOffset(out.length()); - - final PackedObjectLoader reuse = open(otp); - if (reuse != null) { - try { - if (otp.isDeltaRepresentation()) - writeDeltaObjectHeader(otp, reuse); - else - writeObjectHeader(otp.getType(), reuse.getSize()); - reuse.copyRawData(out, buf, windowCursor); - } finally { - reuse.endCopyRawData(); - } - } else if (otp.isDeltaRepresentation()) { - throw new IOException(JGitText.get().creatingDeltasIsNotImplemented); - } else { - writeWholeObjectDeflate(otp); - } - otp.setCRC(out.getCRC32()); - - writeMonitor.update(1); - } - - private PackedObjectLoader open(final ObjectToPack otp) throws IOException { - while (otp.isCopyable()) { - try { - PackedObjectLoader reuse = otp.getCopyLoader(windowCursor); - reuse.beginCopyRawData(); - return reuse; - } catch (IOException err) { - // The pack we found the object in originally is gone, or - // it has been overwritten with a different layout. - // - otp.clearDeltaBase(); - otp.clearSourcePack(); - searchForReuse(new ArrayList<PackedObjectLoader>(), otp); - continue; - } - } - return null; - } - - private void writeWholeObjectDeflate(final ObjectToPack otp) - throws IOException { - final ObjectLoader loader = db.openObject(windowCursor, otp); - final byte[] data = loader.getCachedBytes(); - writeObjectHeader(otp.getType(), data.length); - deflater.reset(); - deflater.setInput(data, 0, data.length); - deflater.finish(); - do { - final int n = deflater.deflate(buf, 0, buf.length); - if (n > 0) - out.write(buf, 0, n); - } while (!deflater.finished()); - } - - private void writeDeltaObjectHeader(final ObjectToPack otp, - final PackedObjectLoader reuse) throws IOException { - if (deltaBaseAsOffset && otp.getDeltaBase() != null) { - writeObjectHeader(Constants.OBJ_OFS_DELTA, reuse.getRawSize()); - - final ObjectToPack deltaBase = otp.getDeltaBase(); - long offsetDiff = otp.getOffset() - deltaBase.getOffset(); - int pos = buf.length - 1; - buf[pos] = (byte) (offsetDiff & 0x7F); - while ((offsetDiff >>= 7) > 0) { - buf[--pos] = (byte) (0x80 | (--offsetDiff & 0x7F)); - } - - out.write(buf, pos, buf.length - pos); - } else { - writeObjectHeader(Constants.OBJ_REF_DELTA, reuse.getRawSize()); - otp.getDeltaBaseId().copyRawTo(buf, 0); - out.write(buf, 0, Constants.OBJECT_ID_LENGTH); - } - } - - private void writeObjectHeader(final int objectType, long dataLength) - throws IOException { - long nextLength = dataLength >>> 4; - int size = 0; - buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) - | (objectType << 4) | (dataLength & 0x0F)); - dataLength = nextLength; - while (dataLength > 0) { - nextLength >>>= 7; - buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (dataLength & 0x7F)); - dataLength = nextLength; - } - out.write(buf, 0, size); - } - - private void writeChecksum() throws IOException { - packcsum = out.getDigest(); - out.write(packcsum); - } - - private ObjectWalk setUpWalker( - final Collection<? extends ObjectId> interestingObjects, - final Collection<? extends ObjectId> uninterestingObjects) - throws MissingObjectException, IOException, - IncorrectObjectTypeException { - final ObjectWalk walker = new ObjectWalk(db); - walker.setRetainBody(false); - walker.sort(RevSort.TOPO); - walker.sort(RevSort.COMMIT_TIME_DESC, true); - if (thin) - walker.sort(RevSort.BOUNDARY, true); - - for (ObjectId id : interestingObjects) { - RevObject o = walker.parseAny(id); - walker.markStart(o); - } - if (uninterestingObjects != null) { - for (ObjectId id : uninterestingObjects) { - final RevObject o; - try { - o = walker.parseAny(id); - } catch (MissingObjectException x) { - if (ignoreMissingUninteresting) - continue; - throw x; - } - walker.markUninteresting(o); - } - } - return walker; - } - - private void findObjectsToPack(final ObjectWalk walker) - throws MissingObjectException, IncorrectObjectTypeException, - IOException { - initMonitor.beginTask(COUNTING_OBJECTS_PROGRESS, - ProgressMonitor.UNKNOWN); - RevObject o; - - while ((o = walker.next()) != null) { - addObject(o); - initMonitor.update(1); - } - while ((o = walker.nextObject()) != null) { - addObject(o); - initMonitor.update(1); - } - initMonitor.endTask(); - } - - /** - * Include one object to the output file. - * <p> - * Objects are written in the order they are added. If the same object is - * added twice, it may be written twice, creating a larger than necessary - * file. - * - * @param object - * the object to add. - * @throws IncorrectObjectTypeException - * the object is an unsupported type. - */ - public void addObject(final RevObject object) - throws IncorrectObjectTypeException { - if (object.has(RevFlag.UNINTERESTING)) { - edgeObjects.add(object); - thin = true; - return; - } - - final ObjectToPack otp = new ObjectToPack(object, object.getType()); - try { - objectsLists[object.getType()].add(otp); - } catch (ArrayIndexOutOfBoundsException x) { - throw new IncorrectObjectTypeException(object, - JGitText.get().incorrectObjectType_COMMITnorTREEnorBLOBnorTAG); - } catch (UnsupportedOperationException x) { - // index pointing to "dummy" empty list - throw new IncorrectObjectTypeException(object, - JGitText.get().incorrectObjectType_COMMITnorTREEnorBLOBnorTAG); - } - objectsMap.add(otp); - } - - /** - * Class holding information about object that is going to be packed by - * {@link PackWriter}. Information include object representation in a - * pack-file and object status. - * - */ - static class ObjectToPack extends PackedObjectInfo { - /** Other object being packed that this will delta against. */ - private ObjectId deltaBase; - - /** Pack to reuse compressed data from, otherwise null. */ - private PackFile copyFromPack; - - /** Offset of the object's header in {@link #copyFromPack}. */ - private long copyOffset; - - /** - * Bit field, from bit 0 to bit 31: - * <ul> - * <li>1 bit: wantWrite</li> - * <li>3 bits: type</li> - * <li>28 bits: deltaDepth</li> - * </ul> - */ - private int flags; - - /** - * Construct object for specified object id. <br/> By default object is - * marked as not written and non-delta packed (as a whole object). - * - * @param src - * object id of object for packing - * @param type - * real type code of the object, not its in-pack type. - */ - ObjectToPack(AnyObjectId src, final int type) { - super(src); - flags |= type << 1; - } - - /** - * @return delta base object id if object is going to be packed in delta - * representation; null otherwise - if going to be packed as a - * whole object. - */ - ObjectId getDeltaBaseId() { - return deltaBase; - } - - /** - * @return delta base object to pack if object is going to be packed in - * delta representation and delta is specified as object to - * pack; null otherwise - if going to be packed as a whole - * object or delta base is specified only as id. - */ - ObjectToPack getDeltaBase() { - if (deltaBase instanceof ObjectToPack) - return (ObjectToPack) deltaBase; - return null; - } - - /** - * Set delta base for the object. Delta base set by this method is used - * by {@link PackWriter} to write object - determines its representation - * in a created pack. - * - * @param deltaBase - * delta base object or null if object should be packed as a - * whole object. - * - */ - void setDeltaBase(ObjectId deltaBase) { - this.deltaBase = deltaBase; - } - - void clearDeltaBase() { - this.deltaBase = null; - } - - /** - * @return true if object is going to be written as delta; false - * otherwise. - */ - boolean isDeltaRepresentation() { - return deltaBase != null; - } - - /** - * Check if object is already written in a pack. This information is - * used to achieve delta-base precedence in a pack file. - * - * @return true if object is already written; false otherwise. - */ - boolean isWritten() { - return getOffset() != 0; - } - - boolean isCopyable() { - return copyFromPack != null; - } - - PackedObjectLoader getCopyLoader(WindowCursor curs) throws IOException { - return copyFromPack.resolveBase(curs, copyOffset); - } - - void setCopyFromPack(PackedObjectLoader loader) { - this.copyFromPack = loader.pack; - this.copyOffset = loader.objectOffset; - } - - void clearSourcePack() { - copyFromPack = null; - } - - int getType() { - return (flags>>1) & 0x7; - } - - int getDeltaDepth() { - return flags >>> 4; - } - - void updateDeltaDepth() { - final int d; - if (deltaBase instanceof ObjectToPack) - d = ((ObjectToPack) deltaBase).getDeltaDepth() + 1; - else if (deltaBase != null) - d = 1; - else - d = 0; - flags = (d << 4) | flags & 0x15; - } - - boolean wantWrite() { - return (flags & 1) == 1; - } - - void markWantWrite() { - flags |= 1; - } - } -} 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 026b008f1a..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> - * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com> - * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * 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. - * <p> - * 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. - * <ul> - * <li>{@link #getType()}</li> - * <li>{@link #getSize()}</li> - * <li>{@link #getBytes()}, {@link #getCachedBytes}</li> - * <li>{@link #getRawSize()}</li> - * <li>{@link #getRawType()}</li> - * </ul> - * - * @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; - } - - /** - * Peg the pack file open to support data copying. - * <p> - * Applications trying to copy raw pack data should ensure the pack stays - * open and available throughout the entire copy. To do that use: - * - * <pre> - * loader.beginCopyRawData(); - * try { - * loader.copyRawData(out, tmpbuf, curs); - * } finally { - * loader.endCopyRawData(); - * } - * </pre> - * - * @throws IOException - * this loader contains stale information and cannot be used. - * The most likely cause is the underlying pack file has been - * deleted, and the object has moved to another pack file. - */ - public void beginCopyRawData() throws IOException { - pack.beginCopyRawData(); - } - - /** - * Copy raw object representation from storage to provided output stream. - * <p> - * Copied data doesn't include object header. User must provide temporary - * buffer used during copying by underlying I/O layer. - * </p> - * - * @param out - * output stream when data is copied. No buffering is guaranteed. - * @param buf - * temporary buffer used during copying. Recommended size is at - * least few kB. - * @param curs - * temporary thread storage during data access. - * @throws IOException - * when the object cannot be read. - * @see #beginCopyRawData() - */ - public void copyRawData(OutputStream out, byte buf[], WindowCursor curs) - throws IOException { - pack.copyRawData(this, out, buf, curs); - } - - /** Release resources after {@link #beginCopyRawData()}. */ - public void endCopyRawData() { - pack.endCopyRawData(); - } - - /** - * @return true if this loader is capable of fast raw-data copying basing on - * compressed data checksum; false if raw-data copying needs - * uncompressing and compressing data - * @throws IOException - * the index file format cannot be determined. - */ - public boolean supportsFastCopyRawData() throws IOException { - return pack.supportsFastCopyRawData(); - } - - /** - * @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/PersonIdent.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java index 522f8477b1..0406684ea7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java @@ -77,7 +77,7 @@ public class PersonIdent { * @param repo */ public PersonIdent(final Repository repo) { - final RepositoryConfig config = repo.getConfig(); + final UserConfig config = repo.getConfig().get(UserConfig.KEY); name = config.getCommitterName(); emailAddress = config.getCommitterEmail(); when = SystemReader.getInstance().getCurrentTime(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java index e04a587ac2..e6f8933389 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java @@ -440,7 +440,12 @@ public abstract class RefUpdate { * an unexpected IO error occurred while writing changes. */ public Result update() throws IOException { - return update(new RevWalk(getRepository())); + RevWalk rw = new RevWalk(getRepository()); + try { + return update(rw); + } finally { + rw.release(); + } } /** @@ -485,7 +490,12 @@ public abstract class RefUpdate { * @throws IOException */ public Result delete() throws IOException { - return delete(new RevWalk(getRepository())); + RevWalk rw = new RevWalk(getRepository()); + try { + return delete(rw); + } finally { + rw.release(); + } } /** 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/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index 62e1578350..6dfbc9d2da 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -49,9 +49,6 @@ package org.eclipse.jgit.lib; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -59,279 +56,103 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.dircache.DirCache; -import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.errors.RevisionSyntaxException; +import org.eclipse.jgit.events.ListenerList; +import org.eclipse.jgit.events.RepositoryEvent; +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; -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. - * - * <ul> - * <li>GIT_DIR - * <ul> - * <li>objects/ - objects</li> - * <li>refs/ - tags and heads</li> - * <li>config - configuration</li> - * <li>info/ - more configurations</li> - * </ul> - * </li> - * </ul> + * Represents a Git repository. * <p> - * This class is thread-safe. + * A repository holds all objects and refs used for managing source code (could + * be any type of file, but source code is what SCM's are typically used for). * <p> - * This implementation only handles a subtly undocumented subset of git features. - * + * This class is thread-safe. */ -public class Repository { +public abstract class Repository { + private static final ListenerList globalListeners = new ListenerList(); + + /** @return the global listener list observing all events in this JVM. */ + public static ListenerList getGlobalListenerList() { + return globalListeners; + } + private final AtomicInteger useCnt = new AtomicInteger(1); + /** Metadata directory holding the repository's critical files. */ private final File gitDir; + /** File abstraction used to resolve paths. */ private final FS fs; - private final FileBasedConfig userConfig; - - private final RepositoryConfig config; - - private final RefDatabase refs; - - private final ObjectDirectory objectDatabase; - private GitIndex index; - private final List<RepositoryListener> listeners = new Vector<RepositoryListener>(); // thread safe - static private final List<RepositoryListener> allListeners = new Vector<RepositoryListener>(); // thread safe + private final ListenerList myListeners = new ListenerList(); - private File workDir; + /** If not bare, the top level directory of the working files. */ + private final File workTree; - private File indexFile; + /** If not bare, the index file caching the working file states. */ + private final File indexFile; /** - * 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. + * Initialize a new repository instance. * - * @param d - * GIT_DIR (the location of the repository metadata). - * @throws IOException - * the repository appears to already exist but cannot be - * accessed. + * @param options + * options to configure the repository. */ - public Repository(final File d) throws IOException { - this(d, null, null, null, null); // go figure it out + protected Repository(final BaseRepositoryBuilder options) { + gitDir = options.getGitDir(); + fs = options.getFS(); + workTree = options.getWorkTree(); + indexFile = options.getIndexFile(); } - /** - * 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. - * - * @param d - * GIT_DIR (the location of the repository metadata). May be - * null work workTree is set - * @param workTree - * GIT_WORK_TREE (the root of the checkout). May be null for - * default value. - * @throws IOException - * the repository appears to already exist but cannot be - * accessed. - */ - public Repository(final File d, final File workTree) throws IOException { - this(d, workTree, null, null, null); // go figure it out + /** @return listeners observing only events on this repository. */ + public ListenerList getListenerList() { + return myListeners; } /** - * Construct a representation of a Git repository using the given parameters - * possibly overriding default conventions. - * - * @param d - * GIT_DIR (the location of the repository metadata). May be null - * for default value in which case it depends on GIT_WORK_TREE. - * @param workTree - * GIT_WORK_TREE (the root of the checkout). May be null for - * default value if GIT_DIR is provided. - * @param objectDir - * GIT_OBJECT_DIRECTORY (where objects and are stored). May be - * null for default value. Relative names ares resolved against - * GIT_WORK_TREE. - * @param alternateObjectDir - * GIT_ALTERNATE_OBJECT_DIRECTORIES (where more objects are read - * from). May be null for default value. Relative names ares - * resolved against GIT_WORK_TREE. - * @param indexFile - * GIT_INDEX_FILE (the location of the index file). May be null - * for default value. Relative names ares resolved against - * GIT_WORK_TREE. - * @throws IOException - * the repository appears to already exist but cannot be - * accessed. - */ - public Repository(final File d, final File workTree, final File objectDir, - final File[] alternateObjectDir, final File indexFile) throws IOException { - this(d, workTree, objectDir, alternateObjectDir, indexFile, FS.DETECTED); - } - - /** - * Construct a representation of a Git repository using the given parameters - * possibly overriding default conventions. + * Fire an event to all registered listeners. + * <p> + * The source repository of the event is automatically set to this + * repository, before the event is delivered to any listeners. * - * @param d - * GIT_DIR (the location of the repository metadata). May be null - * for default value in which case it depends on GIT_WORK_TREE. - * @param workTree - * GIT_WORK_TREE (the root of the checkout). May be null for - * default value if GIT_DIR is provided. - * @param objectDir - * GIT_OBJECT_DIRECTORY (where objects and are stored). May be - * null for default value. Relative names ares resolved against - * GIT_WORK_TREE. - * @param alternateObjectDir - * GIT_ALTERNATE_OBJECT_DIRECTORIES (where more objects are read - * from). May be null for default value. Relative names ares - * resolved against GIT_WORK_TREE. - * @param indexFile - * GIT_INDEX_FILE (the location of the index file). May be null - * for default value. Relative names ares resolved against - * GIT_WORK_TREE. - * @param fs - * the file system abstraction which will be necessary to - * perform certain file system operations. - * @throws IOException - * the repository appears to already exist but cannot be - * accessed. + * @param event + * the event to deliver. */ - public Repository(final File d, final File workTree, final File objectDir, - final File[] alternateObjectDir, final File indexFile, FS fs) - throws IOException { - - if (workTree != null) { - workDir = workTree; - if (d == null) - gitDir = new File(workTree, Constants.DOT_GIT); - else - gitDir = d; - } else { - if (d != null) - gitDir = d; - else - throw new IllegalArgumentException( - JGitText.get().eitherGIT_DIRorGIT_WORK_TREEmustBePassed); - } - - this.fs = fs; - - userConfig = SystemReader.getInstance().openUserConfig(fs); - config = new RepositoryConfig(userConfig, fs.resolve(gitDir, "config")); - - loadUserConfig(); - loadConfig(); - - if (workDir == null) { - // if the working directory was not provided explicitly, - // we need to decide if this is a "bare" repository or not - // first, we check the working tree configuration - String workTreeConfig = getConfig().getString( - ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_WORKTREE); - if (workTreeConfig != null) { - // the working tree configuration wins - workDir = fs.resolve(d, workTreeConfig); - } else if (getConfig().getString( - ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_BARE) != null) { - // we have asserted that a value for the "bare" flag was set - if (!getConfig().getBoolean(ConfigConstants.CONFIG_CORE_SECTION, - ConfigConstants.CONFIG_KEY_BARE, true)) - // the "bare" flag is false -> use the parent of the - // meta data directory - workDir = gitDir.getParentFile(); - else - // the "bare" flag is true - workDir = null; - } else if (Constants.DOT_GIT.equals(gitDir.getName())) { - // no value for the "bare" flag, but the meta data directory - // is named ".git" -> use the parent of the meta data directory - workDir = gitDir.getParentFile(); - } else { - workDir = null; - } - } - - refs = new RefDirectory(this); - if (objectDir != null) - objectDatabase = new ObjectDirectory(fs.resolve(objectDir, ""), - alternateObjectDir, fs); - else - objectDatabase = new ObjectDirectory(fs.resolve(gitDir, "objects"), - alternateObjectDir, fs); - - if (indexFile != null) - this.indexFile = indexFile; - else - this.indexFile = new File(gitDir, "index"); - - 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)); - } - } + public void fireEvent(RepositoryEvent<?> event) { + event.setRepository(this); + myListeners.dispatch(event); + globalListeners.dispatch(event); } - 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 loadConfig() throws IOException { - try { - config.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. Repository with working tree is created using this method. + * Create a new Git repository. + * <p> + * Repository with working tree is created using this method. This method is + * the same as {@code create(false)}. * * @throws IOException * @see #create(boolean) */ - public synchronized void create() throws IOException { + public void create() throws IOException { create(false); } @@ -340,44 +161,14 @@ public class Repository { * directories. * * @param bare - * if true, a bare repository is created. - * + * if true, a bare repository (a repository without a working + * directory) is created. * @throws IOException * in case of IO problem */ - public void create(boolean bare) throws IOException { - final RepositoryConfig cfg = getConfig(); - if (cfg.getFile().exists()) { - throw new IllegalStateException(MessageFormat.format( - JGitText.get().repositoryAlreadyExists, gitDir)); - } - gitDir.mkdirs(); - refs.create(); - objectDatabase.create(); - - new File(gitDir, "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(); - } + public abstract void create(boolean bare) throws IOException; - /** - * @return GIT_DIR - */ + /** @return local metadata directory; null if repository isn't local. */ public File getDirectory() { return gitDir; } @@ -385,42 +176,30 @@ public class Repository { /** * @return the directory containing the objects owned by this repository. */ - public File getObjectsDirectory() { - return objectDatabase.getDirectory(); - } + public abstract File getObjectsDirectory(); /** * @return the object database which stores this repository's data. */ - public ObjectDatabase getObjectDatabase() { - return objectDatabase; + public abstract ObjectDatabase getObjectDatabase(); + + /** @return a new inserter to create objects in {@link #getObjectDatabase()} */ + public ObjectInserter newObjectInserter() { + return getObjectDatabase().newInserter(); } - /** @return the reference database which stores the reference namespace. */ - public RefDatabase getRefDatabase() { - return refs; + /** @return a new inserter to create objects in {@link #getObjectDatabase()} */ + public ObjectReader newObjectReader() { + return getObjectDatabase().newReader(); } + /** @return the reference database which stores the reference namespace. */ + public abstract RefDatabase getRefDatabase(); + /** * @return the configuration of this repository */ - public RepositoryConfig getConfig() { - if (userConfig.isOutdated()) { - try { - loadUserConfig(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - if (config.isOutdated()) { - try { - loadConfig(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - return config; - } + public abstract Config getConfig(); /** * @return the used file system abstraction @@ -430,116 +209,63 @@ public class Repository { } /** - * Construct a filename where the loose object having a specified SHA-1 - * should be stored. If the object is stored in a shared repository the path - * to the alternative repo will be returned. If the object is not yet store - * a usable path in this repo will be returned. It is assumed that callers - * will look for objects in a pack first. - * - * @param objectId - * @return suggested file name - */ - public File toFile(final AnyObjectId objectId) { - return objectDatabase.fileFor(objectId); - } - - /** * @param objectId * @return true if the specified object is stored in this repo or any of the * known shared repositories. */ - public boolean hasObject(final AnyObjectId objectId) { - return objectDatabase.hasObject(objectId); - } - - /** - * @param id - * SHA-1 of an object. - * - * @return a {@link ObjectLoader} for accessing the data of the named - * object, or null if the object does not exist. - * @throws IOException - */ - public ObjectLoader openObject(final AnyObjectId id) - throws IOException { - final WindowCursor wc = new WindowCursor(); + public boolean hasObject(AnyObjectId objectId) { try { - return openObject(wc, id); - } finally { - wc.release(); + return getObjectDatabase().has(objectId); + } catch (IOException e) { + // Legacy API, assume error means "no" + return false; } } /** - * @param curs - * temporary working space associated with the calling thread. - * @param id - * SHA-1 of an object. - * - * @return a {@link ObjectLoader} for accessing the data of the named - * object, or null if the object does not exist. - * @throws IOException - */ - public ObjectLoader openObject(final WindowCursor curs, final AnyObjectId id) - throws IOException { - return objectDatabase.openObject(curs, id); - } - - /** - * Open object in all packs containing specified object. + * Open an object from this repository. + * <p> + * This is a one-shot call interface which may be faster than allocating a + * {@link #newObjectReader()} to perform the lookup. * * @param objectId - * id of object to search for - * @param curs - * temporary working space associated with the calling thread. - * @return collection of loaders for this object, from all packs containing - * this object + * identity of the object to open. + * @return a {@link ObjectLoader} for accessing the object. + * @throws MissingObjectException + * the object does not exist. * @throws IOException + * the object store cannot be accessed. */ - public Collection<PackedObjectLoader> openObjectInAllPacks( - final AnyObjectId objectId, final WindowCursor curs) - throws IOException { - Collection<PackedObjectLoader> result = new LinkedList<PackedObjectLoader>(); - openObjectInAllPacks(objectId, result, curs); - return result; + public ObjectLoader open(final AnyObjectId objectId) + throws MissingObjectException, IOException { + return getObjectDatabase().open(objectId); } /** - * Open object in all packs containing specified object. + * Open an object from this repository. + * <p> + * This is a one-shot call interface which may be faster than allocating a + * {@link #newObjectReader()} to perform the lookup. * * @param objectId - * id of object to search for - * @param resultLoaders - * result collection of loaders for this object, filled with - * loaders from all packs containing specified object - * @param curs - * temporary working space associated with the calling thread. - * @throws IOException - */ - void openObjectInAllPacks(final AnyObjectId objectId, - final Collection<PackedObjectLoader> resultLoaders, - final WindowCursor curs) throws IOException { - objectDatabase.openObjectInAllPacks(resultLoaders, curs, objectId); - } - - /** - * @param id - * SHA'1 of a blob - * @return an {@link ObjectLoader} for accessing the data of a named blob + * identity of the object to open. + * @param typeHint + * hint about the type of object being requested; + * {@link ObjectReader#OBJ_ANY} if the object type is not known, + * or does not matter to the caller. + * @return a {@link ObjectLoader} for accessing the object. + * @throws MissingObjectException + * the object does not exist. + * @throws IncorrectObjectTypeException + * typeHint was not OBJ_ANY, and the object's actual type does + * not match typeHint. * @throws IOException + * the object store cannot be accessed. */ - public ObjectLoader openBlob(final ObjectId id) throws IOException { - return openObject(id); - } - - /** - * @param id - * SHA'1 of a tree - * @return an {@link ObjectLoader} for accessing the data of a named tree - * @throws IOException - */ - public ObjectLoader openTree(final ObjectId id) throws IOException { - return openObject(id); + public ObjectLoader open(AnyObjectId objectId, int typeHint) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + return getObjectDatabase().open(objectId, typeHint); } /** @@ -572,23 +298,26 @@ public class Repository { * @deprecated Use {@link org.eclipse.jgit.revwalk.RevWalk#parseCommit(AnyObjectId)}, * or {@link org.eclipse.jgit.revwalk.RevWalk#parseTag(AnyObjectId)}. * To read a tree, use {@link org.eclipse.jgit.treewalk.TreeWalk#addTree(AnyObjectId)}. - * To read a blob, open it with {@link #openObject(AnyObjectId)}. + * To read a blob, open it with {@link #open(AnyObjectId)}. */ @Deprecated public Object mapObject(final ObjectId id, final String refName) throws IOException { - final ObjectLoader or = openObject(id); - if (or == null) + final ObjectLoader or; + try { + or = open(id); + } catch (MissingObjectException notFound) { return null; - final byte[] raw = or.getBytes(); + } + final byte[] raw = or.getCachedBytes(); switch (or.getType()) { case Constants.OBJ_TREE: - return makeTree(id, raw); + return new Tree(this, id, raw); case Constants.OBJ_COMMIT: - return makeCommit(id, raw); + return new Commit(this, id, raw); case Constants.OBJ_TAG: - return makeTag(id, refName, raw); + return new Tag(this, id, refName, raw); case Constants.OBJ_BLOB: return raw; @@ -608,18 +337,13 @@ public class Repository { */ @Deprecated public Commit mapCommit(final ObjectId id) throws IOException { - final ObjectLoader or = openObject(id); - if (or == null) + final ObjectLoader or; + try { + or = open(id, Constants.OBJ_COMMIT); + } catch (MissingObjectException notFound) { return null; - final byte[] raw = or.getBytes(); - if (Constants.OBJ_COMMIT == or.getType()) - return new Commit(this, id, raw); - throw new IncorrectObjectTypeException(id, Constants.TYPE_COMMIT); - } - - private Commit makeCommit(final ObjectId id, final byte[] raw) { - Commit ret = new Commit(this, id, raw); - return ret; + } + return new Commit(this, id, or.getCachedBytes()); } /** @@ -650,10 +374,13 @@ public class Repository { */ @Deprecated public Tree mapTree(final ObjectId id) throws IOException { - final ObjectLoader or = openObject(id); - if (or == null) + final ObjectLoader or; + try { + or = open(id); + } catch (MissingObjectException notFound) { return null; - final byte[] raw = or.getBytes(); + } + final byte[] raw = or.getCachedBytes(); switch (or.getType()) { case Constants.OBJ_TREE: return new Tree(this, id, raw); @@ -666,16 +393,6 @@ public class Repository { } } - private Tree makeTree(final ObjectId id, final byte[] raw) throws IOException { - Tree ret = new Tree(this, id, raw); - return ret; - } - - private Tag makeTag(final ObjectId id, final String refName, final byte[] raw) { - Tag ret = new Tag(this, id, refName, raw); - return ret; - } - /** * Access a tag by symbolic name. * @@ -701,12 +418,14 @@ public class Repository { */ @Deprecated public Tag mapTag(final String refName, final ObjectId id) throws IOException { - final ObjectLoader or = openObject(id); - if (or == null) + final ObjectLoader or; + try { + or = open(id); + } catch (MissingObjectException notFound) { return null; - final byte[] raw = or.getBytes(); - if (Constants.OBJ_TAG == or.getType()) - return new Tag(this, id, refName, raw); + } + if (or.getType() == Constants.OBJ_TAG) + return new Tag(this, id, refName, or.getCachedBytes()); return new Tag(this, id, refName, null); } @@ -741,7 +460,7 @@ public class Repository { * to the base ref, as the symbolic ref could not be read. */ public RefUpdate updateRef(final String ref, final boolean detach) throws IOException { - return refs.newUpdate(ref, detach); + return getRefDatabase().newUpdate(ref, detach); } /** @@ -757,7 +476,7 @@ public class Repository { * */ public RefRename renameRef(final String fromRef, final String toRef) throws IOException { - return refs.newRename(fromRef, toRef); + return getRefDatabase().newRename(fromRef, toRef); } /** @@ -765,36 +484,45 @@ public class Repository { * * Currently supported is combinations of these. * <ul> - * <li>SHA-1 - a SHA-1</li> - * <li>refs/... - a ref name</li> - * <li>ref^n - nth parent reference</li> - * <li>ref~n - distance via parent reference</li> - * <li>ref@{n} - nth version of ref</li> - * <li>ref^{tree} - tree references by ref</li> - * <li>ref^{commit} - commit references by ref</li> + * <li>SHA-1 - a SHA-1</li> + * <li>refs/... - a ref name</li> + * <li>ref^n - nth parent reference</li> + * <li>ref~n - distance via parent reference</li> + * <li>ref@{n} - nth version of ref</li> + * <li>ref^{tree} - tree references by ref</li> + * <li>ref^{commit} - commit references by ref</li> * </ul> * - * Not supported is + * Not supported is: * <ul> * <li>timestamps in reflogs, ref@{full or relative timestamp}</li> * <li>abbreviated SHA-1's</li> * </ul> * - * @param revstr A git object references expression + * @param revstr + * A git object references expression * @return an ObjectId or null if revstr can't be resolved to any ObjectId - * @throws IOException on serious errors + * @throws IOException + * on serious errors */ public ObjectId resolve(final String revstr) throws IOException { + RevWalk rw = new RevWalk(this); + try { + return resolve(rw, revstr); + } finally { + rw.release(); + } + } + + private ObjectId resolve(final RevWalk rw, final String revstr) throws IOException { char[] rev = revstr.toCharArray(); - Object ref = null; - ObjectId refId = null; + RevObject ref = null; for (int i = 0; i < rev.length; ++i) { switch (rev[i]) { case '^': - if (refId == null) { - String refstr = new String(rev,0,i); - refId = resolveSimple(refstr); - if (refId == null) + if (ref == null) { + ref = parseSimple(rw, new String(rev, 0, i)); + if (ref == null) return null; } if (i + 1 < rev.length) { @@ -810,19 +538,12 @@ public class Repository { case '8': case '9': int j; - ref = mapObject(refId, null); - while (ref instanceof Tag) { - Tag tag = (Tag)ref; - refId = tag.getObjId(); - ref = mapObject(refId, null); - } - if (!(ref instanceof Commit)) - throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT); - for (j=i+1; j<rev.length; ++j) { + ref = rw.parseCommit(ref); + for (j = i + 1; j < rev.length; ++j) { if (!Character.isDigit(rev[j])) break; } - String parentnum = new String(rev, i+1, j-i-1); + String parentnum = new String(rev, i + 1, j - i - 1); int pnum; try { pnum = Integer.parseInt(parentnum); @@ -832,123 +553,83 @@ public class Repository { revstr); } if (pnum != 0) { - final ObjectId parents[] = ((Commit) ref) - .getParentIds(); - if (pnum > parents.length) - refId = null; + RevCommit commit = (RevCommit) ref; + if (pnum > commit.getParentCount()) + ref = null; else - refId = parents[pnum - 1]; + ref = commit.getParent(pnum - 1); } i = j - 1; break; case '{': int k; String item = null; - for (k=i+2; k<rev.length; ++k) { + for (k = i + 2; k < rev.length; ++k) { if (rev[k] == '}') { - item = new String(rev, i+2, k-i-2); + item = new String(rev, i + 2, k - i - 2); break; } } i = k; if (item != null) if (item.equals("tree")) { - ref = mapObject(refId, null); - while (ref instanceof Tag) { - Tag t = (Tag)ref; - refId = t.getObjId(); - ref = mapObject(refId, null); - } - if (ref instanceof Treeish) - refId = ((Treeish)ref).getTreeId(); - else - throw new IncorrectObjectTypeException(refId, Constants.TYPE_TREE); - } - else if (item.equals("commit")) { - ref = mapObject(refId, null); - while (ref instanceof Tag) { - Tag t = (Tag)ref; - refId = t.getObjId(); - ref = mapObject(refId, null); - } - if (!(ref instanceof Commit)) - throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT); - } - else if (item.equals("blob")) { - ref = mapObject(refId, null); - while (ref instanceof Tag) { - Tag t = (Tag)ref; - refId = t.getObjId(); - ref = mapObject(refId, null); - } - if (!(ref instanceof byte[])) - throw new IncorrectObjectTypeException(refId, Constants.TYPE_BLOB); - } - else if (item.equals("")) { - ref = mapObject(refId, null); - while (ref instanceof Tag) { - Tag t = (Tag)ref; - refId = t.getObjId(); - ref = mapObject(refId, null); - } - } - else + ref = rw.parseTree(ref); + } else if (item.equals("commit")) { + ref = rw.parseCommit(ref); + } else if (item.equals("blob")) { + ref = rw.peel(ref); + if (!(ref instanceof RevBlob)) + throw new IncorrectObjectTypeException(ref, + Constants.TYPE_BLOB); + } else if (item.equals("")) { + ref = rw.peel(ref); + } else throw new RevisionSyntaxException(revstr); else throw new RevisionSyntaxException(revstr); break; default: - ref = mapObject(refId, null); - if (ref instanceof Commit) { - final ObjectId parents[] = ((Commit) ref) - .getParentIds(); - if (parents.length == 0) - refId = null; + ref = rw.parseAny(ref); + if (ref instanceof RevCommit) { + RevCommit commit = ((RevCommit) ref); + if (commit.getParentCount() == 0) + ref = null; else - refId = parents[0]; + ref = commit.getParent(0); } else - throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT); + throw new IncorrectObjectTypeException(ref, + Constants.TYPE_COMMIT); } } else { - ref = mapObject(refId, null); - while (ref instanceof Tag) { - Tag tag = (Tag)ref; - refId = tag.getObjId(); - ref = mapObject(refId, null); - } - if (ref instanceof Commit) { - final ObjectId parents[] = ((Commit) ref) - .getParentIds(); - if (parents.length == 0) - refId = null; + ref = rw.peel(ref); + if (ref instanceof RevCommit) { + RevCommit commit = ((RevCommit) ref); + if (commit.getParentCount() == 0) + ref = null; else - refId = parents[0]; + ref = commit.getParent(0); } else - throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT); + throw new IncorrectObjectTypeException(ref, + Constants.TYPE_COMMIT); } break; case '~': if (ref == null) { - String refstr = new String(rev,0,i); - refId = resolveSimple(refstr); - if (refId == null) + ref = parseSimple(rw, new String(rev, 0, i)); + if (ref == null) return null; - ref = mapObject(refId, null); } - while (ref instanceof Tag) { - Tag tag = (Tag)ref; - refId = tag.getObjId(); - ref = mapObject(refId, null); - } - if (!(ref instanceof Commit)) - throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT); + ref = rw.peel(ref); + if (!(ref instanceof RevCommit)) + throw new IncorrectObjectTypeException(ref, + Constants.TYPE_COMMIT); int l; for (l = i + 1; l < rev.length; ++l) { if (!Character.isDigit(rev[l])) break; } - String distnum = new String(rev, i+1, l-i-1); + String distnum = new String(rev, i + 1, l - i - 1); int dist; try { dist = Integer.parseInt(distnum); @@ -957,13 +638,14 @@ public class Repository { JGitText.get().invalidAncestryLength, revstr); } while (dist > 0) { - final ObjectId[] parents = ((Commit) ref).getParentIds(); - if (parents.length == 0) { - refId = null; + RevCommit commit = (RevCommit) ref; + if (commit.getParentCount() == 0) { + ref = null; break; } - refId = parents[0]; - ref = mapCommit(refId); + commit = commit.getParent(0); + rw.parseHeaders(commit); + ref = commit; --dist; } i = l - 1; @@ -971,30 +653,35 @@ public class Repository { case '@': int m; String time = null; - for (m=i+2; m<rev.length; ++m) { + for (m = i + 2; m < rev.length; ++m) { if (rev[m] == '}') { - time = new String(rev, i+2, m-i-2); + time = new String(rev, i + 2, m - i - 2); break; } } if (time != null) - throw new RevisionSyntaxException(JGitText.get().reflogsNotYetSupportedByRevisionParser, revstr); + throw new RevisionSyntaxException( + JGitText.get().reflogsNotYetSupportedByRevisionParser, + revstr); i = m - 1; break; default: - if (refId != null) + if (ref != null) throw new RevisionSyntaxException(revstr); } } - if (refId == null) - refId = resolveSimple(revstr); - return refId; + return ref != null ? ref.copy() : resolveSimple(revstr); + } + + private RevObject parseSimple(RevWalk rw, String revstr) throws IOException { + ObjectId id = resolveSimple(revstr); + return id != null ? rw.parseAny(id) : null; } private ObjectId resolveSimple(final String revstr) throws IOException { if (ObjectId.isId(revstr)) return ObjectId.fromString(revstr); - final Ref r = refs.getRef(revstr); + final Ref r = getRefDatabase().getRef(revstr); return r != null ? r.getObjectId() : null; } @@ -1003,17 +690,24 @@ public class Repository { useCnt.incrementAndGet(); } - /** - * Close all resources used by this repository - */ + /** Decrement the use count, and maybe close resources. */ public void close() { if (useCnt.decrementAndGet() == 0) { - objectDatabase.close(); - refs.close(); + doClose(); } } /** + * Invoked when the use count drops to zero during {@link #close()}. + * <p> + * The default implementation closes the object and ref databases. + */ + protected void doClose() { + getObjectDatabase().close(); + getRefDatabase().close(); + } + + /** * Add a single existing pack to the list of available pack files. * * @param pack @@ -1024,12 +718,16 @@ public class Repository { * 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); - } + public abstract void openPack(File pack, File idx) throws IOException; public String toString() { - return "Repository[" + getDirectory() + "]"; + String desc; + if (getDirectory() != null) + desc = getDirectory().getPath(); + else + desc = getClass().getSimpleName() + "-" + + System.identityHashCode(this); + return "Repository[" + desc + "]"; } /** @@ -1078,6 +776,20 @@ public class Repository { } /** + * Objects known to exist but not expressed by {@link #getAllRefs()}. + * <p> + * 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<ObjectId> getAdditionalHaves() { + return Collections.emptySet(); + } + + /** * Get a ref by name. * * @param name @@ -1088,7 +800,7 @@ public class Repository { * @throws IOException */ public Ref getRef(final String name) throws IOException { - return refs.getRef(name); + return getRefDatabase().getRef(name); } /** @@ -1096,7 +808,7 @@ public class Repository { */ public Map<String, Ref> getAllRefs() { try { - return refs.getRefs(RefDatabase.ALL); + return getRefDatabase().getRefs(RefDatabase.ALL); } catch (IOException e) { return new HashMap<String, Ref>(); } @@ -1109,7 +821,7 @@ public class Repository { */ public Map<String, Ref> getTags() { try { - return refs.getRefs(Constants.R_TAGS); + return getRefDatabase().getRefs(Constants.R_TAGS); } catch (IOException e) { return new HashMap<String, Ref>(); } @@ -1130,7 +842,7 @@ public class Repository { */ public Ref peel(final Ref ref) { try { - return refs.peel(ref); + return getRefDatabase().peel(ref); } catch (IOException e) { // Historical accident; if the reference cannot be peeled due // to some sort of repository access problem we claim that the @@ -1170,13 +882,13 @@ public class Repository { * {@link Repository} * @throws IOException * if the index can not be read - * @throws IllegalStateException - * if this is bare (see {@link #isBare()}) + * @throws NoWorkTreeException + * if this is bare, which implies it has no working directory. + * See {@link #isBare()}. */ - public GitIndex getIndex() throws IOException, IllegalStateException { + public GitIndex getIndex() throws IOException, NoWorkTreeException { if (isBare()) - throw new IllegalStateException( - JGitText.get().bareRepositoryNoWorkdirAndIndex); + throw new NoWorkTreeException(); if (index == null) { index = new GitIndex(this); index.read(); @@ -1188,16 +900,63 @@ public class Repository { /** * @return the index file location - * @throws IllegalStateException - * if this is bare (see {@link #isBare()}) + * @throws NoWorkTreeException + * if this is bare, which implies it has no working directory. + * See {@link #isBare()}. */ - public File getIndexFile() throws IllegalStateException { + public File getIndexFile() throws NoWorkTreeException { if (isBare()) - throw new IllegalStateException( - JGitText.get().bareRepositoryNoWorkdirAndIndex); + throw new NoWorkTreeException(); return indexFile; } + /** + * Create a new in-core index representation and read an index from disk. + * <p> + * The new index will be read before it is returned to the caller. Read + * failures are reported as exceptions and therefore prevent the method from + * returning a partially populated index. + * + * @return a cache representing the contents of the specified index file (if + * it exists) or an empty cache if the file does not exist. + * @throws NoWorkTreeException + * if this is bare, which implies it has no working directory. + * See {@link #isBare()}. + * @throws IOException + * the index file is present but could not be read. + * @throws CorruptObjectException + * the index file is using a format or extension that this + * library does not support. + */ + public DirCache readDirCache() throws NoWorkTreeException, + CorruptObjectException, IOException { + return DirCache.read(getIndexFile()); + } + + /** + * Create a new in-core index representation, lock it, and read from disk. + * <p> + * The new index will be locked and then read before it is returned to the + * caller. Read failures are reported as exceptions and therefore prevent + * the method from returning a partially populated index. + * + * @return a cache representing the contents of the specified index file (if + * it exists) or an empty cache if the file does not exist. + * @throws NoWorkTreeException + * if this is bare, which implies it has no working directory. + * See {@link #isBare()}. + * @throws IOException + * the index file is present but could not be read, or the lock + * could not be obtained. + * @throws CorruptObjectException + * the index file is using a format or extension that this + * library does not support. + */ + public DirCache lockDirCache() throws NoWorkTreeException, + CorruptObjectException, IOException { + return DirCache.lock(getIndexFile()); + } + static byte[] gitInternalSlash(byte[] bytes) { if (File.separatorChar == '/') return bytes; @@ -1211,10 +970,13 @@ public class Repository { * @return an important state */ public RepositoryState getRepositoryState() { + if (isBare() || getDirectory() == null) + return RepositoryState.BARE; + // Pre Git-1.6 logic - if (new File(getWorkDir(), ".dotest").exists()) + if (new File(getWorkTree(), ".dotest").exists()) return RepositoryState.REBASING; - if (new File(gitDir,".dotest-merge").exists()) + if (new File(getDirectory(), ".dotest-merge").exists()) return RepositoryState.REBASING_INTERACTIVE; // From 1.6 onwards @@ -1231,10 +993,10 @@ public class Repository { return RepositoryState.REBASING_MERGE; // Both versions - if (new File(gitDir, "MERGE_HEAD").exists()) { + if (new File(getDirectory(), "MERGE_HEAD").exists()) { // we are merging - now check whether we have unmerged paths try { - if (!DirCache.read(this).hasUnmergedPaths()) { + if (!readDirCache().hasUnmergedPaths()) { // no unmerged paths -> return the MERGING_RESOLVED state return RepositoryState.MERGING_RESOLVED; } @@ -1247,7 +1009,7 @@ public class Repository { return RepositoryState.MERGING; } - if (new File(gitDir,"BISECT_LOG").exists()) + if (new File(getDirectory(), "BISECT_LOG").exists()) return RepositoryState.BISECTING; return RepositoryState.SAFE; @@ -1334,96 +1096,23 @@ public class Repository { } /** - * @return the "bare"-ness of this Repository + * @return true if this is bare, which implies it has no working directory. */ public boolean isBare() { - return workDir == null; + return workTree == null; } /** - * @return the workdir file, i.e. where the files are checked out - * @throws IllegalStateException - * if the repository is "bare" + * @return the root directory of the working tree, where files are checked + * out for viewing and editing. + * @throws NoWorkTreeException + * if this is bare, which implies it has no working directory. + * See {@link #isBare()}. */ - public File getWorkDir() throws IllegalStateException { + public File getWorkTree() throws NoWorkTreeException { if (isBare()) - throw new IllegalStateException( - JGitText.get().bareRepositoryNoWorkdirAndIndex); - return workDir; - } - - /** - * Override default workdir - * - * @param workTree - * the work tree directory - */ - public void setWorkDir(File workTree) { - this.workDir = workTree; - } - - /** - * Register a {@link RepositoryListener} which will be notified - * when ref changes are detected. - * - * @param l - */ - public void addRepositoryChangedListener(final RepositoryListener l) { - listeners.add(l); - } - - /** - * Remove a registered {@link RepositoryListener} - * @param l - */ - public void removeRepositoryChangedListener(final RepositoryListener l) { - listeners.remove(l); - } - - /** - * Register a global {@link RepositoryListener} which will be notified - * when a ref changes in any repository are detected. - * - * @param l - */ - public static void addAnyRepositoryChangedListener(final RepositoryListener l) { - allListeners.add(l); - } - - /** - * Remove a globally registered {@link RepositoryListener} - * @param l - */ - public static void removeAnyRepositoryChangedListener(final RepositoryListener l) { - allListeners.remove(l); - } - - void fireRefsChanged() { - final RefsChangedEvent event = new RefsChangedEvent(this); - List<RepositoryListener> all; - synchronized (listeners) { - all = new ArrayList<RepositoryListener>(listeners); - } - synchronized (allListeners) { - all.addAll(allListeners); - } - for (final RepositoryListener l : all) { - l.refsChanged(event); - } - } - - void fireIndexChanged() { - final IndexChangedEvent event = new IndexChangedEvent(this); - List<RepositoryListener> all; - synchronized (listeners) { - all = new ArrayList<RepositoryListener>(listeners); - } - synchronized (allListeners) { - all.addAll(allListeners); - } - for (final RepositoryListener l : all) { - l.indexChanged(event); - } + throw new NoWorkTreeException(); + return workTree; } /** @@ -1431,11 +1120,7 @@ public class Repository { * * @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 - } + public abstract void scanForRepoChanges() throws IOException; /** * @param refName @@ -1458,12 +1143,8 @@ public class Repository { * 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; - } + public abstract ReflogReader getReflogReader(String refName) + throws IOException; /** * Return the information stored in the file $GIT_DIR/MERGE_MSG. In this @@ -1473,9 +1154,15 @@ public class Repository { * @return a String containing the content of the MERGE_MSG file or * {@code null} if this file doesn't exist * @throws IOException + * @throws NoWorkTreeException + * if this is bare, which implies it has no working directory. + * See {@link #isBare()}. */ - public String readMergeCommitMsg() throws IOException { - File mergeMsgFile = new File(gitDir, Constants.MERGE_MSG); + public String readMergeCommitMsg() throws IOException, NoWorkTreeException { + if (isBare() || getDirectory() == null) + throw new NoWorkTreeException(); + + File mergeMsgFile = new File(getDirectory(), Constants.MERGE_MSG); try { return new String(IO.readFully(mergeMsgFile)); } catch (FileNotFoundException e) { @@ -1494,9 +1181,15 @@ public class Repository { * file or {@code null} if this file doesn't exist. Also if the file * exists but is empty {@code null} will be returned * @throws IOException + * @throws NoWorkTreeException + * if this is bare, which implies it has no working directory. + * See {@link #isBare()}. */ - public List<ObjectId> readMergeHeads() throws IOException { - File mergeHeadFile = new File(gitDir, Constants.MERGE_HEAD); + public List<ObjectId> readMergeHeads() throws IOException, NoWorkTreeException { + if (isBare() || getDirectory() == null) + throw new NoWorkTreeException(); + + File mergeHeadFile = new File(getDirectory(), Constants.MERGE_HEAD); byte[] raw; try { raw = IO.readFully(mergeHeadFile); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java new file mode 100644 index 0000000000..f9185e8f26 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java @@ -0,0 +1,74 @@ +/* + * 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 org.eclipse.jgit.storage.file.FileRepositoryBuilder; + +/** + * Base class to support constructing a {@link Repository}. + * <p> + * 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. + * <p> + * Single repository applications trying to be compatible with other Git + * implementations are encouraged to use a model such as: + * + * <pre> + * new RepositoryBuilder() // + * .setGitDir(gitDirArgument) // --git-dir if supplied, no-op if null + * .readEnviroment() // scan environment GIT_* variables + * .findGitDir() // scan up the file system tree + * .build() + * </pre> + * + * @see FileRepositoryBuilder + */ +public class RepositoryBuilder extends + BaseRepositoryBuilder<RepositoryBuilder, Repository> { + // Empty implementation, everything is inherited. +} 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 0b0260a4cc..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; @@ -120,7 +121,10 @@ public class RepositoryCache { * repository to register. */ public static void register(final Repository db) { - cache.registerRepository(FileKey.exact(db.getDirectory(), db.getFS()), db); + if (db.getDirectory() != null) { + FileKey key = FileKey.exact(db.getDirectory(), db.getFS()); + cache.registerRepository(key, db); + } } /** @@ -133,7 +137,10 @@ public class RepositoryCache { * repository to unregister. */ public static void close(final Repository db) { - cache.unregisterRepository(FileKey.exact(db.getDirectory(), db.getFS())); + if (db.getDirectory() != null) { + FileKey key = FileKey.exact(db.getDirectory(), db.getFS()); + cache.unregisterRepository(key); + } } /** Unregister all repositories from the cache. */ @@ -313,7 +320,7 @@ public class RepositoryCache { public Repository open(final boolean mustExist) throws IOException { if (mustExist && !isGitRepository(path, fs)) throw new RepositoryNotFoundException(path); - return new Repository(path); + return new FileRepository(path); } @Override diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryConfig.java deleted file mode 100644 index 805975a8d1..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryConfig.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com> - * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com> - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2009, JetBrains s.r.o. - * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com> - * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> - * Copyright (C) 2008, Thad Hughes <thadh@thad.corp.google.com> - * Copyright (C) 2009, Yann Simon <yann.simon.fr@gmail.com> - * 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; - -/** - * An object representing the Git config file. - * - * This can be either the repository specific file or the user global - * file depending on how it is instantiated. - */ -public class RepositoryConfig extends FileBasedConfig { - /** Section name for a branch configuration. */ - public static final String BRANCH_SECTION = "branch"; - - /** - * Create a Git configuration file reader/writer/cache for a specific file. - * - * @param base - * configuration that provides default values if this file does - * not set/override a particular key. Often this is the user's - * global configuration file, or the system level configuration. - * @param cfgLocation - * path of the file to load (or save). - */ - public RepositoryConfig(final Config base, final File cfgLocation) { - super(base, cfgLocation); - } - - /** - * @return Core configuration values - */ - public CoreConfig getCore() { - return get(CoreConfig.KEY); - } - - /** - * @return transfer, fetch and receive configuration values - */ - public TransferConfig getTransfer() { - return get(TransferConfig.KEY); - } - - /** @return standard user configuration data */ - public UserConfig getUserConfig() { - return get(UserConfig.KEY); - } - - /** - * @return the author name as defined in the git variables - * and configurations. If no name could be found, try - * to use the system user name instead. - */ - public String getAuthorName() { - return getUserConfig().getAuthorName(); - } - - /** - * @return the committer name as defined in the git variables - * and configurations. If no name could be found, try - * to use the system user name instead. - */ - public String getCommitterName() { - return getUserConfig().getCommitterName(); - } - - /** - * @return the author email as defined in git variables and - * configurations. If no email could be found, try to - * propose one default with the user name and the - * host name. - */ - public String getAuthorEmail() { - return getUserConfig().getAuthorEmail(); - } - - /** - * @return the committer email as defined in git variables and - * configurations. If no email could be found, try to - * propose one default with the user name and the - * host name. - */ - public String getCommitterEmail() { - return getUserConfig().getCommitterEmail(); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java index 2cf5225793..0a59906e94 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java @@ -55,6 +55,14 @@ import org.eclipse.jgit.JGitText; * on the state are the only supported means of deciding what to do. */ public enum RepositoryState { + /** Has no work tree and cannot be used for normal editing. */ + BARE { + public boolean canCheckout() { return false; } + public boolean canResetHead() { return false; } + public boolean canCommit() { return false; } + public String getDescription() { return "Bare"; } + }, + /** * A safe state for working normally * */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tag.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tag.java index 5b3531eb1f..25a06c9c6c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tag.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tag.java @@ -203,9 +203,14 @@ public class Tag { final RefUpdate ru; if (tagger!=null || message!=null || type!=null) { - ObjectId tagid = new ObjectWriter(objdb).writeTag(this); - setTagId(tagid); - id = tagid; + ObjectInserter odi = objdb.newObjectInserter(); + try { + id = odi.insert(Constants.OBJ_TAG, odi.format(this)); + odi.flush(); + setTagId(id); + } finally { + odi.release(); + } } else { id = objId; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java index 9f7589c291..9708bb2f92 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java @@ -1,8 +1,5 @@ /* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> - * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com> - * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> + * 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 @@ -46,35 +43,69 @@ package org.eclipse.jgit.lib; -import java.io.IOException; +import java.util.concurrent.locks.ReentrantLock; -import org.eclipse.jgit.errors.MissingObjectException; +/** + * Wrapper around the general {@link ProgressMonitor} to make it thread safe. + */ +public class ThreadSafeProgressMonitor implements ProgressMonitor { + private final ProgressMonitor pm; + + private final ReentrantLock lock; -/** Reads a deltified object which uses an {@link ObjectId} to find its base. */ -class DeltaRefPackedObjectLoader extends DeltaPackedObjectLoader { - private final ObjectId deltaBase; + /** + * Wrap a ProgressMonitor to be thread safe. + * + * @param pm + * the underlying monitor to receive events. + */ + public ThreadSafeProgressMonitor(ProgressMonitor pm) { + this.pm = pm; + this.lock = new ReentrantLock(); + } + + public void start(int totalTasks) { + lock.lock(); + try { + pm.start(totalTasks); + } finally { + lock.unlock(); + } + } - DeltaRefPackedObjectLoader(final PackFile pr, final long objectOffset, - final int headerSz, final int deltaSz, final ObjectId base) { - super(pr, objectOffset, headerSz, deltaSz); - deltaBase = base; + public void beginTask(String title, int totalWork) { + lock.lock(); + try { + pm.beginTask(title, totalWork); + } finally { + lock.unlock(); + } } - 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; + public void update(int completed) { + lock.lock(); + try { + pm.update(completed); + } finally { + lock.unlock(); + } } - @Override - public int getRawType() { - return Constants.OBJ_REF_DELTA; + public boolean isCancelled() { + lock.lock(); + try { + return pm.isCancelled(); + } finally { + lock.unlock(); + } } - @Override - public ObjectId getDeltaBase() throws IOException { - return deltaBase; + public void endTask() { + lock.lock(); + try { + pm.endTask(); + } finally { + lock.unlock(); + } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java index 0872c96250..d68b9f6380 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java @@ -537,10 +537,8 @@ public class Tree extends TreeEntry implements Treeish { private void ensureLoaded() throws IOException, MissingObjectException { if (!isLoaded()) { - final ObjectLoader or = db.openTree(getId()); - if (or == null) - throw new MissingObjectException(getId(), Constants.TYPE_TREE); - readTree(or.getBytes()); + ObjectLoader ldr = db.open(getId(), Constants.OBJ_TREE); + readTree(ldr.getCachedBytes()); } } 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 <robin.rosenberg@dewire.com> - * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.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 <marek.zawirski@gmail.com> - * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.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/merge/Merger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java index 38af20fb8f..68d60c0077 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java @@ -51,9 +51,9 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectWriter; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.WindowCursor; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTree; @@ -70,10 +70,13 @@ public abstract class Merger { /** The repository this merger operates on. */ protected final Repository db; + /** Reader to support {@link #walk} and other object loading. */ + protected final ObjectReader reader; + /** A RevWalk for computing merge bases, or listing incoming commits. */ protected final RevWalk walk; - private ObjectWriter writer; + private ObjectInserter inserter; /** The original objects supplied in the merge; this can be any tree-ish. */ protected RevObject[] sourceObjects; @@ -92,7 +95,8 @@ public abstract class Merger { */ protected Merger(final Repository local) { db = local; - walk = new RevWalk(db); + reader = db.newObjectReader(); + walk = new RevWalk(reader); } /** @@ -105,10 +109,10 @@ public abstract class Merger { /** * @return an object writer to create objects in {@link #getRepository()}. */ - public ObjectWriter getObjectWriter() { - if (writer == null) - writer = new ObjectWriter(getRepository()); - return writer; + public ObjectInserter getObjectInserter() { + if (inserter == null) + inserter = getRepository().newObjectInserter(); + return inserter; } /** @@ -148,7 +152,13 @@ public abstract class Merger { for (int i = 0; i < sourceObjects.length; i++) sourceTrees[i] = walk.parseTree(sourceObjects[i]); - return mergeImpl(); + try { + return mergeImpl(); + } finally { + if (inserter != null) + inserter.release(); + reader.release(); + } } /** @@ -202,12 +212,7 @@ public abstract class Merger { */ protected AbstractTreeIterator openTree(final AnyObjectId treeId) throws IncorrectObjectTypeException, IOException { - final WindowCursor curs = new WindowCursor(); - try { - return new CanonicalTreeParser(null, db, treeId, curs); - } finally { - curs.release(); - } + return new CanonicalTreeParser(null, reader, treeId); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java index 6cd244599e..29342a7308 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java @@ -51,6 +51,7 @@ import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.errors.UnmergedPathException; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.NameConflictTreeWalk; @@ -99,7 +100,7 @@ public class StrategySimpleTwoWayInCore extends ThreeWayMergeStrategy { InCoreMerger(final Repository local) { super(local); - tw = new NameConflictTreeWalk(db); + tw = new NameConflictTreeWalk(reader); cache = DirCache.newInCore(); } @@ -152,7 +153,9 @@ public class StrategySimpleTwoWayInCore extends ThreeWayMergeStrategy { if (hasConflict) return false; try { - resultTree = cache.writeTree(getObjectWriter()); + ObjectInserter odi = getObjectInserter(); + resultTree = cache.writeTree(odi); + odi.flush(); return true; } catch (UnmergedPathException upe) { resultTree = null; @@ -168,7 +171,7 @@ public class StrategySimpleTwoWayInCore extends ThreeWayMergeStrategy { final AbstractTreeIterator i = getTree(tree); if (i != null) { if (FileMode.TREE.equals(tw.getRawMode(tree))) { - builder.addTree(tw.getRawPath(), stage, db, tw + builder.addTree(tw.getRawPath(), stage, reader, tw .getObjectId(tree)); } else { final DirCacheEntry e; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java index 6b4ed80e15..476592f5d9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java @@ -53,12 +53,13 @@ import java.util.Set; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.lib.AnyObjectId; -import org.eclipse.jgit.lib.Commit; +import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.Tag; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevSort; +import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; /** Specialized RevWalk for visualization of a commit graph. */ @@ -115,8 +116,8 @@ public class PlotWalk extends RevWalk { class PlotRefComparator implements Comparator<Ref> { public int compare(Ref o1, Ref o2) { try { - Object obj1 = getRepository().mapObject(o1.getObjectId(), o1.getName()); - Object obj2 = getRepository().mapObject(o2.getObjectId(), o2.getName()); + RevObject obj1 = parseAny(o1.getObjectId()); + RevObject obj2 = parseAny(o2.getObjectId()); long t1 = timeof(obj1); long t2 = timeof(obj2); if (t1 > t2) @@ -129,11 +130,15 @@ public class PlotWalk extends RevWalk { return 0; } } - long timeof(Object o) { - if (o instanceof Commit) - return ((Commit)o).getCommitter().getWhen().getTime(); - if (o instanceof Tag) - return ((Tag)o).getTagger().getWhen().getTime(); + + long timeof(RevObject o) { + if (o instanceof RevCommit) + return ((RevCommit) o).getCommitTime(); + if (o instanceof RevTag) { + RevTag tag = (RevTag) o; + PersonIdent who = tag.getTaggerIdent(); + return who != null ? who.getWhen().getTime() : 0; + } return 0; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java index edb883714b..76510ce387 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java @@ -137,7 +137,7 @@ class MergeBaseGenerator extends Generator { for (;;) { final RevCommit c = pending.next(); if (c == null) { - walker.curs.release(); + walker.reader.release(); return null; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java index 11d40012c5..a6ecfe2192 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java @@ -53,6 +53,7 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.treewalk.CanonicalTreeParser; @@ -97,7 +98,19 @@ public class ObjectWalk extends RevWalk { * the repository the walker will obtain data from. */ public ObjectWalk(final Repository repo) { - super(repo); + this(repo.newObjectReader()); + } + + /** + * Create a new revision and object walker for a given repository. + * + * @param or + * the reader the walker will obtain data from. The reader should + * be released by the caller when the walker is no longer + * required. + */ + public ObjectWalk(ObjectReader or) { + super(or); pendingObjects = new BlockObjQueue(); treeWalk = new CanonicalTreeParser(); } @@ -294,14 +307,14 @@ public class ObjectWalk extends RevWalk { continue; if (o instanceof RevTree) { currentTree = (RevTree) o; - treeWalk = treeWalk.resetRoot(db, currentTree, curs); + treeWalk = treeWalk.resetRoot(reader, currentTree); } return o; } } private CanonicalTreeParser enter(RevObject tree) throws IOException { - CanonicalTreeParser p = treeWalk.createSubtreeIterator0(db, tree, curs); + CanonicalTreeParser p = treeWalk.createSubtreeIterator0(reader, tree); if (p.eof()) { // We can't tolerate the subtree being an empty tree, as // that will break us out early before we visit all names. @@ -349,7 +362,7 @@ public class ObjectWalk extends RevWalk { final RevObject o = nextObject(); if (o == null) break; - if (o instanceof RevBlob && !db.hasObject(o)) + if (o instanceof RevBlob && !reader.has(o)) throw new MissingObjectException(o, Constants.TYPE_BLOB); } } @@ -371,6 +384,18 @@ public class ObjectWalk extends RevWalk { return last != null ? treeWalk.getEntryPathString() : null; } + /** + * Get the current object's path hash code. + * <p> + * This method computes a hash code on the fly for this path, the hash is + * suitable to cluster objects that may have similar paths together. + * + * @return path hash code; any integer may be returned. + */ + public int getPathHashCode() { + return last != null ? treeWalk.getEntryPathHashCode() : 0; + } + @Override public void dispose() { super.dispose(); @@ -403,7 +428,7 @@ public class ObjectWalk extends RevWalk { return; tree.flags |= UNINTERESTING; - treeWalk = treeWalk.resetRoot(db, tree, curs); + treeWalk = treeWalk.resetRoot(reader, tree); while (!treeWalk.eof()) { final FileMode mode = treeWalk.getEntryFileMode(); final int sType = mode.getObjectType(); @@ -419,7 +444,7 @@ public class ObjectWalk extends RevWalk { final RevTree t = lookupTree(idBuffer); if ((t.flags & UNINTERESTING) == 0) { t.flags |= UNINTERESTING; - treeWalk = treeWalk.createSubtreeIterator0(db, t, curs); + treeWalk = treeWalk.createSubtreeIterator0(reader, t); continue; } break; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java index e723bce51b..0e2bb98320 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java @@ -128,7 +128,7 @@ class PendingGenerator extends Generator { for (;;) { final RevCommit c = pending.next(); if (c == null) { - walker.curs.release(); + walker.reader.release(); return null; } @@ -174,7 +174,7 @@ class PendingGenerator extends Generator { c.disposeBody(); } } catch (StopWalkException swe) { - walker.curs.release(); + walker.reader.release(); pending.clear(); return null; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java index 2d96bbf676..84cc704c34 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java @@ -214,7 +214,8 @@ public class RevCommit extends RevObject { * @return parsed commit. */ public final Commit asCommit(final RevWalk walk) { - return new Commit(walk.db, this, buffer); + // TODO(spearce) Remove repository when this method dies. + return new Commit(walk.repository, this, buffer); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java index 5dde43b271..a19f4d83ea 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java @@ -51,7 +51,6 @@ 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.lib.ObjectLoader; /** Base object type accessed during revision walking. */ public abstract class RevObject extends ObjectId { @@ -78,13 +77,7 @@ public abstract class RevObject extends ObjectId { final byte[] loadCanonical(final RevWalk walk) throws IOException, MissingObjectException, IncorrectObjectTypeException, CorruptObjectException { - final ObjectLoader ldr = walk.db.openObject(walk.curs, this); - if (ldr == null) - throw new MissingObjectException(this, getType()); - final byte[] data = ldr.getCachedBytes(); - if (getType() != ldr.getType()) - throw new IncorrectObjectTypeException(this, getType()); - return data; + return walk.reader.open(this, getType()).getCachedBytes(); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java index d2a665e078..a04ea7154e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java @@ -194,7 +194,7 @@ public class RevTag extends RevObject { * @return parsed tag. */ public Tag asTag(final RevWalk walk) { - return new Tag(walk.db, this, tagName, buffer); + return new Tag(walk.repository, this, tagName, buffer); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java index a8c67c60a8..7406bb60cd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java @@ -62,7 +62,7 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdSubclassMap; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.WindowCursor; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; @@ -157,9 +157,10 @@ public class RevWalk implements Iterable<RevCommit> { private static final int APP_FLAGS = -1 & ~((1 << RESERVED_FLAGS) - 1); - final Repository db; + /** Exists <b>ONLY</b> to support legacy Tag and Commit objects. */ + final Repository repository; - final WindowCursor curs; + final ObjectReader reader; final MutableObjectId idBuffer; @@ -189,11 +190,29 @@ public class RevWalk implements Iterable<RevCommit> { * Create a new revision walker for a given repository. * * @param repo - * the repository the walker will obtain data from. + * the repository the walker will obtain data from. An + * ObjectReader will be created by the walker, and must be + * released by the caller. */ public RevWalk(final Repository repo) { - db = repo; - curs = new WindowCursor(); + this(repo, repo.newObjectReader()); + } + + /** + * Create a new revision walker for a given repository. + * + * @param or + * the reader the walker will obtain data from. The reader should + * be released by the caller when the walker is no longer + * required. + */ + public RevWalk(ObjectReader or) { + this(null, or); + } + + private RevWalk(final Repository repo, final ObjectReader or) { + repository = repo; + reader = or; idBuffer = new MutableObjectId(); objects = new ObjectIdSubclassMap<RevObject>(); roots = new ArrayList<RevCommit>(); @@ -205,13 +224,19 @@ public class RevWalk implements Iterable<RevCommit> { retainBody = true; } + /** @return the reader this walker is using to load objects. */ + public ObjectReader getObjectReader() { + return reader; + } + /** - * Get the repository this walker loads objects from. - * - * @return the repository this walker was created to read. + * Release any resources used by this walker's reader. + * <p> + * A walker that has been released can be used again, but may need to be + * released after the subsequent usage. */ - public Repository getRepository() { - return db; + public void release() { + reader.release(); } /** @@ -678,11 +703,7 @@ public class RevWalk implements Iterable<RevCommit> { public RevCommit parseCommit(final AnyObjectId id) throws MissingObjectException, IncorrectObjectTypeException, IOException { - RevObject c = parseAny(id); - while (c instanceof RevTag) { - c = ((RevTag) c).getObject(); - parseHeaders(c); - } + RevObject c = peel(parseAny(id)); if (!(c instanceof RevCommit)) throw new IncorrectObjectTypeException(id.toObjectId(), Constants.TYPE_COMMIT); @@ -709,11 +730,7 @@ public class RevWalk implements Iterable<RevCommit> { public RevTree parseTree(final AnyObjectId id) throws MissingObjectException, IncorrectObjectTypeException, IOException { - RevObject c = parseAny(id); - while (c instanceof RevTag) { - c = ((RevTag) c).getObject(); - parseHeaders(c); - } + RevObject c = peel(parseAny(id)); final RevTree t; if (c instanceof RevCommit) @@ -773,15 +790,12 @@ public class RevWalk implements Iterable<RevCommit> { throws MissingObjectException, IOException { RevObject r = objects.get(id); if (r == null) { - final ObjectLoader ldr = db.openObject(curs, id); - if (ldr == null) - throw new MissingObjectException(id.toObjectId(), "unknown"); - final byte[] data = ldr.getCachedBytes(); + final ObjectLoader ldr = reader.open(id); final int type = ldr.getType(); switch (type) { case Constants.OBJ_COMMIT: { final RevCommit c = createCommit(id); - c.parseCanonical(this, data); + c.parseCanonical(this, ldr.getCachedBytes()); r = c; break; } @@ -797,7 +811,7 @@ public class RevWalk implements Iterable<RevCommit> { } case Constants.OBJ_TAG: { final RevTag t = new RevTag(id); - t.parseCanonical(this, data); + t.parseCanonical(this, ldr.getCachedBytes()); r = t; break; } @@ -848,6 +862,29 @@ public class RevWalk implements Iterable<RevCommit> { } /** + * Peel back annotated tags until a non-tag object is found. + * + * @param obj + * the starting object. + * @return If {@code obj} is not an annotated tag, {@code obj}. Otherwise + * the first non-tag object that {@code obj} references. The + * returned object's headers have been parsed. + * @throws MissingObjectException + * a referenced object cannot be found. + * @throws IOException + * a pack file or loose object could not be read. + */ + public RevObject peel(RevObject obj) throws MissingObjectException, + IOException { + while (obj instanceof RevTag) { + parseHeaders(obj); + obj = ((RevTag) obj).getObject(); + } + parseHeaders(obj); + return obj; + } + + /** * Create a new flag for application use during walking. * <p> * Applications are only assured to be able to create 24 unique flags on any @@ -1023,7 +1060,7 @@ public class RevWalk implements Iterable<RevCommit> { } } - curs.release(); + reader.release(); roots.clear(); queue = new DateRevQueue(); pending = new StartGenerator(this); @@ -1038,11 +1075,12 @@ public class RevWalk implements Iterable<RevCommit> { * All RevFlag instances are also invalidated, and must not be reused. */ public void dispose() { + reader.release(); freeFlags = APP_FLAGS; delayFreeFlags = 0; carryFlags = UNINTERESTING; objects.clear(); - curs.release(); + reader.release(); roots.clear(); queue = new DateRevQueue(); pending = new StartGenerator(this); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java index 4c5a2a77eb..8ec2d2b091 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java @@ -82,11 +82,11 @@ class RewriteTreeFilter extends RevFilter { private final TreeWalk pathFilter; - private final Repository repo; + private final Repository repository; RewriteTreeFilter(final RevWalk walker, final TreeFilter t) { - repo = walker.db; - pathFilter = new TreeWalk(repo); + repository = walker.repository; + pathFilter = new TreeWalk(walker.reader); pathFilter.setFilter(t); pathFilter.setRecursive(t.shouldBeRecursive()); } @@ -239,7 +239,7 @@ class RewriteTreeFilter extends RevFilter { tw.reset(trees); List<DiffEntry> files = DiffEntry.scan(tw); - RenameDetector rd = new RenameDetector(repo); + RenameDetector rd = new RenameDetector(repository); rd.addAll(files); files = rd.compute(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java index 8042610310..457c8dc90a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java @@ -43,8 +43,11 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +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; @@ -67,31 +70,25 @@ final class ByteArrayWindow extends ByteWindow { } @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; + protected int setInput(final int pos, final Inflater inf) + throws DataFormatException { + int n = array.length - pos; + inf.setInput(array, pos, n); + return n; } - @Override - protected void inflateVerify(final int pos, final Inflater inf) + 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 { - while (!inf.finished()) { - if (inf.needsInput()) { - inf.setInput(array, pos, array.length - pos); - break; - } - inf.inflate(verifyGarbageBuffer, 0, verifyGarbageBuffer.length); - } - while (!inf.finished() && !inf.needsInput()) - inf.inflate(verifyGarbageBuffer, 0, verifyGarbageBuffer.length); + 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/storage/file/ByteBufferWindow.java index 1b29934d28..29a0159950 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java @@ -43,7 +43,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.nio.ByteBuffer; import java.util.zip.DataFormatException; @@ -72,39 +72,13 @@ final class ByteBufferWindow extends ByteWindow { } @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; - } - - @Override - protected void inflateVerify(final int pos, final Inflater inf) + protected int setInput(final int pos, 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); - } - inf.inflate(verifyGarbageBuffer, 0, verifyGarbageBuffer.length); - } - while (!inf.finished() && !inf.needsInput()) - inf.inflate(verifyGarbageBuffer, 0, verifyGarbageBuffer.length); + final byte[] tmp = new byte[Math.min(s.remaining(), 512)]; + s.get(tmp, 0, tmp.length); + inf.setInput(tmp, 0, tmp.length); + return tmp.length; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java index cbef4218af..f92efb4ac9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java @@ -42,7 +42,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.util.zip.DataFormatException; import java.util.zip.Inflater; @@ -117,69 +117,10 @@ abstract class ByteWindow { */ 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 <code>dstbuf</code> 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 <code>dstoff</code> based on the number of bytes - * successfully copied into <code>dstbuf</code> by - * <code>inf</code>. 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 <code>dstbuf</code> 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 <code>dstoff</code> based on the number of bytes - * successfully copied into <code>dstbuf</code> by - * <code>inf</code>. 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; - - protected static final byte[] verifyGarbageBuffer = new byte[2048]; - - final void inflateVerify(final long pos, final Inflater inf) - throws DataFormatException { - inflateVerify((int) (pos - start), inf); + final int setInput(long pos, Inflater inf) throws DataFormatException { + return setInput((int) (pos - start), inf); } - protected abstract void inflateVerify(int pos, Inflater inf) + protected abstract int setInput(int pos, 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/storage/file/CachedObjectDirectory.java index 3724f84463..8ea0b854c0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java @@ -42,32 +42,47 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +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.storage.pack.ObjectToPack; +import org.eclipse.jgit.storage.pack.PackWriter; + /** * The cached instance of an {@link ObjectDirectory}. * <p> * This class caches the list of loose objects in memory, so the file system is * not queried with stat calls. */ -public class CachedObjectDirectory extends CachedObjectDatabase { +class CachedObjectDirectory extends FileObjectDatabase { /** * The set that contains unpacked objects identifiers, it is created when * the cached instance is created. */ private final ObjectIdSubclassMap<ObjectId> unpackedObjects = new ObjectIdSubclassMap<ObjectId>(); + private final ObjectDirectory wrapped; + + private AlternateHandle[] alts; + /** * The constructor * * @param wrapped * the wrapped database */ - public CachedObjectDirectory(ObjectDirectory wrapped) { - super(wrapped); + CachedObjectDirectory(ObjectDirectory wrapped) { + this.wrapped = wrapped; + File objects = wrapped.getDirectory(); String[] fanout = objects.list(); if (fanout == null) @@ -91,22 +106,108 @@ public class CachedObjectDirectory extends CachedObjectDatabase { } @Override - protected ObjectLoader openObject2(WindowCursor curs, String objectName, + 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 has(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 { - if (unpackedObjects.get(objectId) == null) - return null; - return super.openObject2(curs, objectName, objectId); + // This method should never be invoked. + throw new UnsupportedOperationException(); + } + + @Override + long getObjectSize1(WindowCursor curs, AnyObjectId objectId) throws IOException { + if (unpackedObjects.contains(objectId)) + return wrapped.getObjectSize2(curs, objectId.name(), objectId); + return wrapped.getObjectSize1(curs, objectId); + } + + @Override + long getObjectSize2(WindowCursor curs, String objectName, AnyObjectId objectId) + throws IOException { + // This method should never be invoked. + throw new UnsupportedOperationException(); } @Override - protected boolean hasObject1(AnyObjectId objectId) { - if (unpackedObjects.get(objectId) != null) - return true; // known to be loose - return super.hasObject1(objectId); + void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, + WindowCursor curs) throws IOException { + wrapped.selectObjectRepresentation(packer, otp, curs); } @Override - protected boolean hasObject2(String name) { - return false; // loose objects were tested by hasObject1 + int getStreamFileThreshold() { + return wrapped.getStreamFileThreshold(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java index eb00917917..b39efb0e8b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java @@ -47,7 +47,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.File; import java.io.FileNotFoundException; @@ -56,6 +56,8 @@ 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; 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..250c7cac0d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java @@ -0,0 +1,271 @@ +/* + * 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.storage.pack.ObjectToPack; +import org.eclipse.jgit.storage.pack.PackWriter; + +abstract class FileObjectDatabase extends ObjectDatabase { + @Override + public ObjectReader newReader() { + return new WindowCursor(this); + } + + /** + * Does the requested object exist in this database? + * <p> + * 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 has(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. + * <p> + * 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; + } + + long getObjectSize(WindowCursor curs, AnyObjectId objectId) + throws IOException { + long sz = getObjectSizeImpl1(curs, objectId); + if (0 <= sz) + return sz; + return getObjectSizeImpl2(curs, objectId.name(), objectId); + } + + final long getObjectSizeImpl1(final WindowCursor curs, + final AnyObjectId objectId) throws IOException { + long sz; + + sz = getObjectSize1(curs, objectId); + if (0 <= sz) + return sz; + + for (final AlternateHandle alt : myAlternates()) { + sz = alt.db.getObjectSizeImpl1(curs, objectId); + if (0 <= sz) + return sz; + } + + if (tryAgain1()) { + sz = getObjectSize1(curs, objectId); + if (0 <= sz) + return sz; + } + + return -1; + } + + final long getObjectSizeImpl2(final WindowCursor curs, + final String objectName, final AnyObjectId objectId) + throws IOException { + long sz; + + sz = getObjectSize2(curs, objectName, objectId); + if (0 <= sz) + return sz; + + for (final AlternateHandle alt : myAlternates()) { + sz = alt.db.getObjectSizeImpl2(curs, objectName, objectId); + if (0 <= sz) + return sz; + } + + return -1; + } + + 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 long getObjectSize1(WindowCursor curs, AnyObjectId objectId) + throws IOException; + + abstract long getObjectSize2(WindowCursor curs, String objectName, + AnyObjectId objectId) throws IOException; + + abstract FileObjectDatabase newCachedFileObjectDatabase(); + + abstract int getStreamFileThreshold(); + + 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..15aafbdaeb --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepository.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com> + * Copyright (C) 2008-2010, Google Inc. + * Copyright (C) 2006-2010, Robin Rosenberg <robin.rosenberg@dewire.com> + * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.storage.file; + +import java.io.File; +import java.io.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. + * + * <ul> + * <li>GIT_DIR + * <ul> + * <li>objects/ - objects</li> + * <li>refs/ - tags and heads</li> + * <li>config - configuration</li> + * <li>info/ - more configurations</li> + * </ul> + * </li> + * </ul> + * <p> + * This class is thread-safe. + * <p> + * 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. + * <p> + * 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: + * + * <pre> + * new FileRepositoryBuilder().setGitDir(gitDir).build() + * </pre> + * + * @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()); + getListenerList().addConfigChangedListener(objectDatabase); + + 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()}. + * <p> + * 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<ObjectId> getAdditionalHaves() { + HashSet<ObjectId> r = new HashSet<ObjectId>(); + 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/DeltaOfsPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java index d0e98a2a92..31d3e99ad6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java @@ -1,8 +1,5 @@ /* - * Copyright (C) 2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> - * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com> - * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> + * 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 @@ -44,39 +41,50 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; +import java.io.File; 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; - } +import org.eclipse.jgit.lib.BaseRepositoryBuilder; +/** + * Constructs a {@link FileRepository}. + * <p> + * 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. + * <p> + * Single repository applications trying to be compatible with other Git + * implementations are encouraged to use a model such as: + * + * <pre> + * 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() + * </pre> + */ +public class FileRepositoryBuilder extends + BaseRepositoryBuilder<FileRepositoryBuilder, FileRepository> { + /** + * Create a repository matching the configuration in this builder. + * <p> + * 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 ObjectId getDeltaBase() throws IOException { - final ObjectId id = pack.findObjectForOffset(deltaBase); - if (id == null) - throw new CorruptObjectException( - JGitText.get().offsetWrittenDeltaBaseForObjectNotFoundInAPack); - return id; + public FileRepository build() throws IOException { + return new FileRepository(setup()); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedDeltaObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedDeltaObject.java new file mode 100644 index 0000000000..53a0e617fc --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedDeltaObject.java @@ -0,0 +1,230 @@ +/* + * 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.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.DataFormatException; +import java.util.zip.InflaterInputStream; + +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.LargeObjectException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectStream; +import org.eclipse.jgit.storage.pack.BinaryDelta; +import org.eclipse.jgit.storage.pack.DeltaStream; + +class LargePackedDeltaObject extends ObjectLoader { + private static final long SIZE_UNKNOWN = -1; + + private int type; + + private long size; + + private final long objectOffset; + + private final long baseOffset; + + private final int headerLength; + + private final PackFile pack; + + private final FileObjectDatabase db; + + LargePackedDeltaObject(long objectOffset, + long baseOffset, int headerLength, PackFile pack, + FileObjectDatabase db) { + this.type = Constants.OBJ_BAD; + this.size = SIZE_UNKNOWN; + this.objectOffset = objectOffset; + this.baseOffset = baseOffset; + this.headerLength = headerLength; + this.pack = pack; + this.db = db; + } + + @Override + public int getType() { + if (type == Constants.OBJ_BAD) { + WindowCursor wc = new WindowCursor(db); + try { + type = pack.getObjectType(wc, objectOffset); + } catch (IOException packGone) { + // If the pack file cannot be pinned into the cursor, it + // probably was repacked recently. Go find the object + // again and get the type from that location instead. + // + try { + type = wc.open(getObjectId()).getType(); + } catch (IOException packGone2) { + // "He's dead, Jim." We just can't discover the type + // and the interface isn't supposed to be lazy here. + // Report an invalid type code instead, callers will + // wind up bailing out with an error at some point. + } + } finally { + wc.release(); + } + } + return type; + } + + @Override + public long getSize() { + if (size == SIZE_UNKNOWN) { + WindowCursor wc = new WindowCursor(db); + try { + byte[] b = pack.getDeltaHeader(wc, objectOffset + headerLength); + size = BinaryDelta.getResultSize(b); + } catch (DataFormatException objectCorrupt) { + // The zlib stream for the delta is corrupt. We probably + // cannot access the object. Keep the size negative and + // report that bogus result to the caller. + } catch (IOException packGone) { + // If the pack file cannot be pinned into the cursor, it + // probably was repacked recently. Go find the object + // again and get the size from that location instead. + // + try { + size = wc.open(getObjectId()).getSize(); + } catch (IOException packGone2) { + // "He's dead, Jim." We just can't discover the size + // and the interface isn't supposed to be lazy here. + // Report an invalid type code instead, callers will + // wind up bailing out with an error at some point. + } + } finally { + wc.release(); + } + } + return size; + } + + @Override + public boolean isLarge() { + return true; + } + + @Override + public byte[] getCachedBytes() throws LargeObjectException { + try { + throw new LargeObjectException(getObjectId()); + } catch (IOException cannotObtainId) { + throw new LargeObjectException(); + } + } + + @Override + public ObjectStream openStream() throws MissingObjectException, IOException { + final WindowCursor wc = new WindowCursor(db); + InputStream in = open(wc); + in = new BufferedInputStream(in, 8192); + return new ObjectStream.Filter(getType(), size, in) { + @Override + public void close() throws IOException { + wc.release(); + super.close(); + } + }; + } + + private InputStream open(final WindowCursor wc) + throws MissingObjectException, IOException, + IncorrectObjectTypeException { + InputStream delta; + try { + delta = new PackInputStream(pack, objectOffset + headerLength, wc); + } catch (IOException packGone) { + // If the pack file cannot be pinned into the cursor, it + // probably was repacked recently. Go find the object + // again and open the stream from that location instead. + // + return wc.open(getObjectId()).openStream(); + } + delta = new InflaterInputStream(delta); + + final ObjectLoader base = pack.load(wc, baseOffset); + DeltaStream ds = new DeltaStream(delta) { + private long baseSize = SIZE_UNKNOWN; + + @Override + protected InputStream openBase() throws IOException { + InputStream in; + if (base instanceof LargePackedDeltaObject) + in = ((LargePackedDeltaObject) base).open(wc); + else + in = base.openStream(); + if (baseSize == SIZE_UNKNOWN) { + if (in instanceof DeltaStream) + baseSize = ((DeltaStream) in).getSize(); + else if (in instanceof ObjectStream) + baseSize = ((ObjectStream) in).getSize(); + } + return in; + } + + @Override + protected long getBaseSize() throws IOException { + if (baseSize == SIZE_UNKNOWN) { + // This code path should never be used as DeltaStream + // is supposed to open the stream first, which would + // initialize the size for us directly from the stream. + baseSize = base.getSize(); + } + return baseSize; + } + }; + if (size == SIZE_UNKNOWN) + size = ds.getSize(); + return ds; + } + + private ObjectId getObjectId() throws IOException { + return pack.findObjectForOffset(objectOffset); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedWholeObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedWholeObject.java new file mode 100644 index 0000000000..9f5b804ce4 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedWholeObject.java @@ -0,0 +1,130 @@ +/* + * 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.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.InflaterInputStream; + +import org.eclipse.jgit.errors.LargeObjectException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectStream; + +class LargePackedWholeObject extends ObjectLoader { + private final int type; + + private final long size; + + private final long objectOffset; + + private final int headerLength; + + private final PackFile pack; + + private final FileObjectDatabase db; + + LargePackedWholeObject(int type, long size, long objectOffset, + int headerLength, PackFile pack, FileObjectDatabase db) { + this.type = type; + this.size = size; + this.objectOffset = objectOffset; + this.headerLength = headerLength; + this.pack = pack; + this.db = db; + } + + @Override + public int getType() { + return type; + } + + @Override + public long getSize() { + return size; + } + + @Override + public boolean isLarge() { + return true; + } + + @Override + public byte[] getCachedBytes() throws LargeObjectException { + try { + throw new LargeObjectException(getObjectId()); + } catch (IOException cannotObtainId) { + throw new LargeObjectException(); + } + } + + @Override + public ObjectStream openStream() throws MissingObjectException, IOException { + WindowCursor wc = new WindowCursor(db); + InputStream in; + try { + in = new PackInputStream(pack, objectOffset + headerLength, wc); + } catch (IOException packGone) { + // If the pack file cannot be pinned into the cursor, it + // probably was repacked recently. Go find the object + // again and open the stream from that location instead. + // + return wc.open(getObjectId(), type).openStream(); + } + + in = new BufferedInputStream( // + new InflaterInputStream( // + in, // + wc.inflater(), // + 8192), // + 8192); + return new ObjectStream.Filter(type, size, in); + } + + private ObjectId getObjectId() throws IOException { + return pack.findObjectForOffset(objectOffset); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java index a348f1e547..08bb8e60d5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java @@ -1,6 +1,5 @@ /* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> + * 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 @@ -42,66 +41,78 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.IOException; -import java.io.OutputStream; -import java.security.MessageDigest; -import java.util.zip.CRC32; -/** Custom output stream to support {@link PackWriter}. */ -final class PackOutputStream extends OutputStream { - private final OutputStream out; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.storage.pack.StoredObjectRepresentation; - private final CRC32 crc = new CRC32(); - - private final MessageDigest md = Constants.newMessageDigest(); - - private long count; - - PackOutputStream(final OutputStream out) { - this.out = out; +class LocalObjectRepresentation extends StoredObjectRepresentation { + static LocalObjectRepresentation newWhole(PackFile f, long p, long length) { + LocalObjectRepresentation r = new LocalObjectRepresentation() { + @Override + public int getFormat() { + return PACK_WHOLE; + } + }; + r.pack = f; + r.offset = p; + r.length = length; + return r; } - @Override - public void write(final int b) throws IOException { - out.write(b); - crc.update(b); - md.update((byte) b); - count++; + static LocalObjectRepresentation newDelta(PackFile f, long p, long n, + ObjectId base) { + LocalObjectRepresentation r = new Delta(); + r.pack = f; + r.offset = p; + r.length = n; + r.baseId = base; + return r; } - @Override - public void write(final byte[] b, final int off, final int len) - throws IOException { - out.write(b, off, len); - crc.update(b, off, len); - md.update(b, off, len); - count += len; + static LocalObjectRepresentation newDelta(PackFile f, long p, long n, + long base) { + LocalObjectRepresentation r = new Delta(); + r.pack = f; + r.offset = p; + r.length = n; + r.baseOffset = base; + return r; } - @Override - public void flush() throws IOException { - out.flush(); - } + PackFile pack; - /** @return total number of bytes written since stream start. */ - long length() { - return count; - } + long offset; + + long length; + + private long baseOffset; - /** @return obtain the current CRC32 register. */ - int getCRC32() { - return (int) crc.getValue(); + private ObjectId baseId; + + @Override + public int getWeight() { + return (int) Math.min(length, Integer.MAX_VALUE); } - /** Reinitialize the CRC32 register for a new region. */ - void resetCRC32() { - crc.reset(); + @Override + public ObjectId getDeltaBase() { + if (baseId == null && getFormat() == PACK_DELTA) { + try { + baseId = pack.findObjectForOffset(baseOffset); + } catch (IOException error) { + return null; + } + } + return baseId; } - /** @return obtain the current SHA-1 digest. */ - byte[] getDigest() { - return md.digest(); + private static final class Delta extends LocalObjectRepresentation { + @Override + public int getFormat() { + return PACK_DELTA; + } } } 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..c7ef2c9133 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java @@ -0,0 +1,78 @@ +/* + * 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.revwalk.RevObject; +import org.eclipse.jgit.storage.pack.ObjectToPack; +import org.eclipse.jgit.storage.pack.StoredObjectRepresentation; + +/** {@link ObjectToPack} for {@link ObjectDirectory}. */ +class LocalObjectToPack extends ObjectToPack { + /** Pack to reuse compressed data from, otherwise null. */ + PackFile pack; + + /** Offset of the object's header in {@link #pack}. */ + long offset; + + /** Length of the data section of the object. */ + long length; + + LocalObjectToPack(RevObject obj) { + super(obj); + } + + @Override + protected void clearReuseAsIs() { + super.clearReuseAsIs(); + pack = null; + } + + @Override + public void select(StoredObjectRepresentation ref) { + LocalObjectRepresentation ptr = (LocalObjectRepresentation) ref; + this.pack = ptr.pack; + this.offset = ptr.offset; + this.length = ptr.length; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java index 13f158dedf..ad89a24847 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java @@ -42,7 +42,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.BufferedOutputStream; import java.io.File; @@ -57,6 +57,8 @@ 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. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java index 9a5bcdb68c..6fe4fd754e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java @@ -41,10 +41,11 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.BufferedReader; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; @@ -62,7 +63,19 @@ import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.PackMismatchException; +import org.eclipse.jgit.events.ConfigChangedEvent; +import org.eclipse.jgit.events.ConfigChangedListener; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.CoreConfig; +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.RepositoryCache; import org.eclipse.jgit.lib.RepositoryCache.FileKey; +import org.eclipse.jgit.storage.pack.ObjectToPack; +import org.eclipse.jgit.storage.pack.PackWriter; import org.eclipse.jgit.util.FS; /** @@ -72,10 +85,23 @@ import org.eclipse.jgit.util.FS; * 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. + * <p> + * Optionally an object database can reference one or more alternates; other + * ObjectDatabase instances that are searched in addition to the current + * database. + * <p> + * 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 ObjectDatabase { +public class ObjectDirectory extends FileObjectDatabase implements + ConfigChangedListener { 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; @@ -86,29 +112,53 @@ public class ObjectDirectory extends ObjectDatabase { private final AtomicReference<PackList> packList; - private final File[] alternateObjectDir; - private final FS fs; + private final AtomicReference<AlternateHandle[]> alternates; + + private int streamFileThreshold; + /** * Initialize a reference to an on-disk object directory. * + * @param cfg + * configuration this directory consults for write settings. * @param dir * the location of the <code>objects</code> directory. - * @param alternateObjectDir + * @param alternatePaths * a list of alternate object directories * @param fs - * the file system abstraction which will be necessary to - * perform certain file system operations. + * 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 File dir, File[] alternateObjectDir, FS fs) { + public ObjectDirectory(final Config cfg, final File dir, + File[] alternatePaths, FS fs) throws IOException { + config = cfg; objects = dir; - this.alternateObjectDir = alternateObjectDir; infoDirectory = new File(objects, "info"); packDirectory = new File(objects, "pack"); alternatesFile = new File(infoDirectory, "alternates"); packList = new AtomicReference<PackList>(NO_PACKS); this.fs = fs; + + alternates = new AtomicReference<AlternateHandle[]>(); + 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); + } + + onConfigChanged(new ConfigChangedEvent()); + } + + public void onConfigChanged(ConfigChangedEvent event) { + CoreConfig core = config.get(CoreConfig.KEY); + streamFileThreshold = core.getStreamFileThreshold(); } /** @@ -131,11 +181,24 @@ public class ObjectDirectory extends ObjectDatabase { } @Override - public void closeSelf() { + 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(); + } } /** @@ -199,8 +262,7 @@ public class ObjectDirectory extends ObjectDatabase { return "ObjectDirectory[" + getDirectory() + "]"; } - @Override - protected boolean hasObject1(final AnyObjectId objectId) { + boolean hasObject1(final AnyObjectId objectId) { for (final PackFile p : packList.get().packs) { try { if (p.hasObject(objectId)) { @@ -218,18 +280,15 @@ public class ObjectDirectory extends ObjectDatabase { return false; } - @Override - protected ObjectLoader openObject1(final WindowCursor curs, + 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); + final ObjectLoader ldr = p.get(curs, objectId); + if (ldr != null) return ldr; - } } catch (PackMismatchException e) { // Pack was modified; refresh the entire pack list. // @@ -245,18 +304,15 @@ public class ObjectDirectory extends ObjectDatabase { } } - @Override - void openObjectInAllPacks1(final Collection<PackedObjectLoader> out, - final WindowCursor curs, final AnyObjectId objectId) + long getObjectSize1(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) { - out.add(ldr); - } + long sz = p.getObjectSize(curs, objectId); + if (0 <= sz) + return sz; } catch (PackMismatchException e) { // Pack was modified; refresh the entire pack list. // @@ -268,28 +324,75 @@ public class ObjectDirectory extends ObjectDatabase { removePack(p); } } - break SEARCH; + return -1; } } @Override - protected boolean hasObject2(final String objectName) { - return fileFor(objectName).exists(); + long getObjectSize2(WindowCursor curs, String objectName, + AnyObjectId objectId) throws IOException { + try { + File path = fileFor(objectName); + FileInputStream in = new FileInputStream(path); + try { + return UnpackedObject.getSize(in, objectId, curs); + } finally { + in.close(); + } + } catch (FileNotFoundException noFile) { + return -1; + } } @Override - protected ObjectLoader openObject2(final WindowCursor curs, + void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, + WindowCursor curs) throws IOException { + PackList pList = packList.get(); + SEARCH: for (;;) { + for (final PackFile p : pList.packs) { + try { + LocalObjectRepresentation rep = p.representation(curs, otp); + if (rep != null) + packer.select(otp, rep); + } 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); + File path = fileFor(objectName); + FileInputStream in = new FileInputStream(path); + try { + return UnpackedObject.open(in, path, objectId, curs); + } finally { + in.close(); + } } catch (FileNotFoundException noFile) { return null; } } - @Override - protected boolean tryAgain1() { + boolean tryAgain1() { final PackList old = packList.get(); if (old.tryAgain(packDirectory.lastModified())) return old != scanPacks(old); @@ -459,29 +562,36 @@ public class ObjectDirectory extends ObjectDatabase { return nameSet; } - @Override - protected ObjectDatabase[] loadAlternates() throws IOException { - final List<ObjectDatabase> l = new ArrayList<ObjectDatabase>(4); - if (alternateObjectDir != null) { - for (File d : alternateObjectDir) { - l.add(openAlternate(d)); - } - } else { - final BufferedReader br = open(alternatesFile); - try { - String line; - while ((line = br.readLine()) != null) { - l.add(openAlternate(line)); + 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); } - } finally { - br.close(); } } + return alt; + } - if (l.isEmpty()) { - return NO_ALTERNATES; + private AlternateHandle[] loadAlternates() throws IOException { + final List<AlternateHandle> l = new ArrayList<AlternateHandle>(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 ObjectDatabase[l.size()]); + return l.toArray(new AlternateHandle[l.size()]); } private static BufferedReader open(final File f) @@ -489,19 +599,22 @@ public class ObjectDirectory extends ObjectDatabase { return new BufferedReader(new FileReader(f)); } - private ObjectDatabase openAlternate(final String location) + private AlternateHandle openAlternate(final String location) throws IOException { final File objdir = fs.resolve(objects, location); return openAlternate(objdir); } - private ObjectDatabase openAlternate(File objdir) throws IOException { + private AlternateHandle openAlternate(File objdir) throws IOException { final File parent = objdir.getParentFile(); if (FileKey.isGitRepository(parent, fs)) { - final Repository db = RepositoryCache.open(FileKey.exact(parent, fs)); - return new AlternateRepositoryDatabase(db); + FileKey key = FileKey.exact(parent, fs); + FileRepository db = (FileRepository) RepositoryCache.open(key); + return new AlternateRepository(db); } - return new ObjectDirectory(objdir, null, fs); + + ObjectDirectory db = new ObjectDirectory(config, objdir, null, fs); + return new AlternateHandle(db); } private static final class PackList { @@ -566,6 +679,15 @@ public class ObjectDirectory extends ObjectDatabase { @Override public ObjectDatabase newCachedDatabase() { + return newCachedFileObjectDatabase(); + } + + FileObjectDatabase newCachedFileObjectDatabase() { return new CachedObjectDirectory(this); } + + @Override + int getStreamFileThreshold() { + return streamFileThreshold; + } } 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..5016679894 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com> + * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> + * 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.has(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.has(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..e74a7c0142 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java @@ -0,0 +1,920 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com> + * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.storage.file; + +import java.io.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.MissingObjectException; +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.ObjectLoader; +import org.eclipse.jgit.storage.pack.BinaryDelta; +import org.eclipse.jgit.storage.pack.ObjectToPack; +import org.eclipse.jgit.storage.pack.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<PackIndex.MutableEntry> { + /** Sorts PackFiles to be most recently created to least recently created. */ + public static Comparator<PackFile> SORT = new Comparator<PackFile>() { + 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. + * <p> + * 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 <code>.idx</code> file listing the contents. + * @param packFile + * path of the <code>.pack</code> 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; + } + + /** @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. + * <p> + * For performance reasons only the index file is searched; the main pack + * content is ignored entirely. + * </p> + * + * @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. + */ + ObjectLoader get(final WindowCursor curs, final AnyObjectId id) + throws IOException { + final long offset = idx().findOffset(id); + return 0 < offset && !isCorrupt(offset) ? load(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. + * <p> + * Iterator returns objects in SHA-1 lexicographical order. + * </p> + * + * @return iterator over entries of associated pack index + * + * @see PackIndex#iterator() + */ + public Iterator<PackIndex.MutableEntry> iterator() { + try { + return idx().iterator(); + } catch (IOException e) { + return Collections.<PackIndex.MutableEntry> 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); + } + + private final UnpackedObjectCache.Entry readCache(final long position) { + return UnpackedObjectCache.get(this, position); + } + + private final void saveCache(final long position, final byte[] data, final int type) { + UnpackedObjectCache.store(this, position, data, type); + } + + private final byte[] decompress(final long position, final long totalSize, + final WindowCursor curs) throws IOException, DataFormatException { + final byte[] dstbuf = new byte[(int) 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.offset, 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.offset + 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.offset + headerCnt; + final long dataLength = src.length; + final long expectedCRC; + final ByteArrayWindow quickCopy; + + // Verify the object isn't corrupt before sending. If it is, + // we report it missing instead. + // + try { + 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.offset); + throw new CorruptObjectException(MessageFormat.format( + JGitText.get().objectAtHasBadZlibStream, + src.offset, 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.offset); + throw new EOFException(MessageFormat.format( + JGitText.get().shortCompressedStreamAt, + src.offset)); + } + expectedCRC = crc1.getValue(); + } + } catch (DataFormatException dataFormat) { + setCorrupt(src.offset); + + CorruptObjectException corruptObject = new CorruptObjectException( + MessageFormat.format( + JGitText.get().objectAtHasBadZlibStream, + src.offset, 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.offset, + 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())); + } + + ObjectLoader load(final WindowCursor curs, final long pos) + throws IOException { + final byte[] ib = curs.tempId; + readFully(pos, ib, 0, 20, curs); + int c = ib[0] & 0xff; + final int type = (c >> 4) & 7; + long sz = c & 15; + int shift = 4; + int p = 1; + while ((c & 0x80) != 0) { + c = ib[p++] & 0xff; + sz += (c & 0x7f) << shift; + shift += 7; + } + + try { + switch (type) { + case Constants.OBJ_COMMIT: + case Constants.OBJ_TREE: + case Constants.OBJ_BLOB: + case Constants.OBJ_TAG: { + if (sz < curs.getStreamFileThreshold()) { + byte[] data = decompress(pos + p, sz, curs); + return new ObjectLoader.SmallObject(type, data); + } + return new LargePackedWholeObject(type, sz, pos, p, this, curs.db); + } + + 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 loadDelta(pos, p, sz, pos - ofs, curs); + } + + case Constants.OBJ_REF_DELTA: { + readFully(pos + p, ib, 0, 20, curs); + long ofs = findDeltaBase(ObjectId.fromRaw(ib)); + return loadDelta(pos, p + 20, sz, ofs, curs); + } + + default: + throw new IOException(MessageFormat.format( + JGitText.get().unknownObjectType, type)); + } + } catch (DataFormatException dfe) { + CorruptObjectException coe = new CorruptObjectException( + MessageFormat.format( + JGitText.get().objectAtHasBadZlibStream, pos, + getPackFile())); + coe.initCause(dfe); + throw coe; + } + } + + private long findDeltaBase(ObjectId baseId) throws IOException, + MissingObjectException { + long ofs = idx().findOffset(baseId); + if (ofs < 0) + throw new MissingObjectException(baseId, + JGitText.get().missingDeltaBase); + return ofs; + } + + private ObjectLoader loadDelta(long posSelf, int hdrLen, long sz, + long posBase, WindowCursor curs) throws IOException, + DataFormatException { + if (curs.getStreamFileThreshold() <= sz) { + // The delta instruction stream itself is pretty big, and + // that implies the resulting object is going to be massive. + // Use only the large delta format here. + // + return new LargePackedDeltaObject(posSelf, posBase, hdrLen, // + this, curs.db); + } + + byte[] data; + int type; + + UnpackedObjectCache.Entry e = readCache(posBase); + if (e != null) { + data = e.data; + type = e.type; + } else { + ObjectLoader p = load(curs, posBase); + if (p.isLarge()) { + // The base itself is large. We have to produce a large + // delta stream as we don't want to build the whole base. + // + return new LargePackedDeltaObject(posSelf, posBase, hdrLen, + this, curs.db); + } + data = p.getCachedBytes(); + type = p.getType(); + saveCache(posBase, data, type); + } + + // At this point we have the base, and its small, and the delta + // stream also is small, so the result object cannot be more than + // 2x our small size. This occurs if the delta instructions were + // "copy entire base, literal insert entire delta". Go with the + // faster small object style at this point. + // + data = BinaryDelta.apply(data, decompress(posSelf + hdrLen, sz, curs)); + return new ObjectLoader.SmallObject(type, data); + } + + byte[] getDeltaHeader(WindowCursor wc, long pos) + throws IOException, DataFormatException { + // The delta stream starts as two variable length integers. If we + // assume they are 64 bits each, we need 16 bytes to encode them, + // plus 2 extra bytes for the variable length overhead. So 18 is + // the longest delta instruction header. + // + final byte[] hdr = new byte[18]; + wc.inflate(this, pos, hdr, 0); + return hdr; + } + + int getObjectType(final WindowCursor curs, long pos) throws IOException { + final byte[] ib = curs.tempId; + for (;;) { + readFully(pos, ib, 0, 20, curs); + int c = ib[0] & 0xff; + final int type = (c >> 4) & 7; + int shift = 4; + int p = 1; + while ((c & 0x80) != 0) { + c = ib[p++] & 0xff; + shift += 7; + } + + switch (type) { + case Constants.OBJ_COMMIT: + case Constants.OBJ_TREE: + case Constants.OBJ_BLOB: + case Constants.OBJ_TAG: + return type; + + 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); + } + pos = pos - ofs; + continue; + } + + case Constants.OBJ_REF_DELTA: { + readFully(pos + p, ib, 0, 20, curs); + pos = findDeltaBase(ObjectId.fromRaw(ib)); + continue; + } + + default: + throw new IOException(MessageFormat.format( + JGitText.get().unknownObjectType, type)); + } + } + } + + long getObjectSize(final WindowCursor curs, final AnyObjectId id) + throws IOException { + final long offset = idx().findOffset(id); + return 0 < offset ? getObjectSize(curs, offset) : -1; + } + + long getObjectSize(final WindowCursor curs, final long pos) + throws IOException { + final byte[] ib = curs.tempId; + readFully(pos, ib, 0, 20, curs); + int c = ib[0] & 0xff; + final int type = (c >> 4) & 7; + long sz = c & 15; + int shift = 4; + int p = 1; + while ((c & 0x80) != 0) { + c = ib[p++] & 0xff; + sz += (c & 0x7f) << shift; + shift += 7; + } + + long deltaAt; + switch (type) { + case Constants.OBJ_COMMIT: + case Constants.OBJ_TREE: + case Constants.OBJ_BLOB: + case Constants.OBJ_TAG: + return sz; + + case Constants.OBJ_OFS_DELTA: + c = ib[p++] & 0xff; + while ((c & 128) != 0) + c = ib[p++] & 0xff; + deltaAt = pos + p; + break; + + case Constants.OBJ_REF_DELTA: + deltaAt = pos + p + 20; + break; + + default: + throw new IOException(MessageFormat.format( + JGitText.get().unknownObjectType, type)); + } + + try { + return BinaryDelta.getResultSize(getDeltaHeader(curs, deltaAt)); + } catch (DataFormatException e) { + throw new CorruptObjectException(MessageFormat.format(JGitText + .get().objectAtHasBadZlibStream, pos, getPackFile())); + } + } + + LocalObjectRepresentation representation(final WindowCursor curs, + final AnyObjectId objectId) throws IOException { + final long pos = idx().findOffset(objectId); + if (pos < 0) + return null; + + final byte[] ib = curs.tempId; + readFully(pos, ib, 0, 20, curs); + int c = ib[0] & 0xff; + int p = 1; + final int typeCode = (c >> 4) & 7; + while ((c & 0x80) != 0) + c = ib[p++] & 0xff; + + long len = (findEndOffset(pos) - pos); + switch (typeCode) { + case Constants.OBJ_COMMIT: + case Constants.OBJ_TREE: + case Constants.OBJ_BLOB: + case Constants.OBJ_TAG: + return LocalObjectRepresentation.newWhole(this, pos, len - p); + + 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); + } + ofs = pos - ofs; + return LocalObjectRepresentation.newDelta(this, pos, len - p, ofs); + } + + case Constants.OBJ_REF_DELTA: { + len -= p; + len -= Constants.OBJECT_ID_LENGTH; + readFully(pos + p, ib, 0, 20, curs); + ObjectId id = ObjectId.fromRaw(ib); + return LocalObjectRepresentation.newDelta(this, pos, len, id); + } + + 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/storage/file/PackIndex.java index 13985e78e9..62d1c9d8f8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java @@ -42,7 +42,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.File; import java.io.FileInputStream; @@ -53,6 +53,9 @@ 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; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java index bb7cd8b53c..3b68edc191 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java @@ -44,7 +44,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.IOException; import java.io.InputStream; @@ -53,6 +53,9 @@ 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; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java index 128b2df8cb..cef7cc429e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java @@ -41,7 +41,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.IOException; import java.io.InputStream; @@ -51,6 +51,9 @@ 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; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriter.java index 4d2714bc55..6bd73adcb1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriter.java @@ -42,7 +42,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.BufferedOutputStream; import java.io.IOException; @@ -52,6 +52,8 @@ 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; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV1.java index eb44b3a8c7..722ab0e06b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV1.java @@ -42,7 +42,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.IOException; import java.io.OutputStream; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java index b6ac7b89e3..21ebd1ca9c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java @@ -41,7 +41,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.IOException; import java.io.OutputStream; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackInputStream.java new file mode 100644 index 0000000000..5425eedbe9 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackInputStream.java @@ -0,0 +1,85 @@ +/* + * 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 java.io.InputStream; + +class PackInputStream extends InputStream { + private final WindowCursor wc; + + private final PackFile pack; + + private long pos; + + PackInputStream(PackFile pack, long pos, WindowCursor wc) + throws IOException { + this.pack = pack; + this.pos = pos; + this.wc = wc; + + // Pin the first window, to ensure the pack is open and valid. + // + wc.pin(pack, pos); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int n = wc.copy(pack, pos, b, off, len); + pos += n; + return n; + } + + @Override + public int read() throws IOException { + byte[] buf = new byte[1]; + int n = read(buf, 0, 1); + return n == 1 ? buf[0] & 0xff : -1; + } + + @Override + public void close() { + wc.release(); + } +}
\ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackLock.java index de8e3fa637..be250114c2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackLock.java @@ -41,11 +41,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +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 <code>.keep</code> file. */ public class PackLock { private final File keepFile; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java index f4f57aed43..96abaeefd3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java @@ -41,14 +41,15 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +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.PackIndex.MutableEntry; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.storage.file.PackIndex.MutableEntry; /** * <p> diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java index 302b63b486..68b0270df9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java @@ -44,7 +44,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import static org.eclipse.jgit.lib.Constants.CHARSET; import static org.eclipse.jgit.lib.Constants.HEAD; @@ -74,7 +74,21 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.MissingObjectException; 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; @@ -109,7 +123,7 @@ public class RefDirectory extends RefDatabase { /** If in the header, denotes the file has peeled data. */ public static final String PACKED_REFS_PEELED = " peeled"; //$NON-NLS-1$ - private final Repository parent; + private final FileRepository parent; private final File gitDir; @@ -150,7 +164,7 @@ public class RefDirectory extends RefDatabase { */ private final AtomicInteger lastNotifiedModCnt = new AtomicInteger(); - RefDirectory(final Repository db) { + RefDirectory(final FileRepository db) { final FS fs = db.getFS(); parent = db; gitDir = db.getDirectory(); @@ -405,20 +419,7 @@ public class RefDirectory extends RefDatabase { 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) { - do { - obj = rw.parseAny(((RevTag) obj).getObject()); - } while (obj instanceof RevTag); - - newLeaf = new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf - .getName(), leaf.getObjectId(), obj.copy()); - } else { - newLeaf = new ObjectIdRef.PeeledNonTag(leaf.getStorage(), leaf - .getName(), leaf.getObjectId()); - } + ObjectIdRef newLeaf = doPeel(leaf); // 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. @@ -435,6 +436,23 @@ public class RefDirectory extends RefDatabase { return recreate(ref, newLeaf); } + private ObjectIdRef doPeel(final Ref leaf) throws MissingObjectException, + IOException { + RevWalk rw = new RevWalk(getRepository()); + try { + RevObject obj = rw.parseAny(leaf.getObjectId()); + if (obj instanceof RevTag) { + return new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf + .getName(), leaf.getObjectId(), rw.peel(obj).copy()); + } else { + return new ObjectIdRef.PeeledNonTag(leaf.getStorage(), leaf + .getName(), leaf.getObjectId()); + } + } finally { + rw.release(); + } + } + private static Ref recreate(final Ref old, final ObjectIdRef leaf) { if (old.isSymbolic()) { Ref dst = recreate(old.getTarget(), leaf); @@ -590,7 +608,7 @@ public class RefDirectory extends RefDatabase { } private boolean isLogAllRefUpdates() { - return parent.getConfig().getCore().isLogAllRefUpdates(); + return parent.getConfig().get(CoreConfig.KEY).isLogAllRefUpdates(); } private boolean shouldAutoCreateLog(final String refName) { @@ -833,7 +851,7 @@ public class RefDirectory extends RefDatabase { final int last = lastNotifiedModCnt.get(); final int curr = modCnt.get(); if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr)) - parent.fireRefsChanged(); + parent.fireEvent(new RefsChangedEvent()); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java index fec00d9325..4f3efe343e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java @@ -42,11 +42,15 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +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; @@ -88,10 +92,10 @@ class RefDirectoryRename extends RefRename { 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(); + final RevWalk rw = new RevWalk(refdb.getRepository()); try { // First backup the source so its never unreachable. tmp.setNewObjectId(objId); @@ -173,6 +177,7 @@ class RefDirectoryRename extends RefRename { } catch (IOException err) { refdb.fileFor(tmp.getName()).delete(); } + rw.release(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryUpdate.java index 447be104ab..8d35ec34f6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryUpdate.java @@ -42,12 +42,16 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +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; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ReflogReader.java index 4c5503f321..75214308d6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ReflogReader.java @@ -42,7 +42,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.File; import java.io.FileNotFoundException; @@ -52,6 +52,10 @@ 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; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java new file mode 100644 index 0000000000..59f9c82670 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com> + * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> + * 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.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; +import java.util.zip.ZipException; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.LargeObjectException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.InflaterCache; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectStream; +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 UnpackedObject { + private static final int BUFFER_SIZE = 8192; + + /** + * Parse an object from the unpacked object format. + * + * @param raw + * complete contents of the compressed object. + * @param id + * expected ObjectId of the object, used only for error reporting + * in exceptions. + * @return loader to read the inflated contents. + * @throws IOException + * the object cannot be parsed. + */ + public static ObjectLoader parse(byte[] raw, AnyObjectId id) + throws IOException { + WindowCursor wc = new WindowCursor(null); + try { + return open(new ByteArrayInputStream(raw), null, id, wc); + } finally { + wc.release(); + } + } + + static ObjectLoader open(InputStream in, File path, AnyObjectId id, + WindowCursor wc) throws IOException { + try { + in = buffer(in); + in.mark(20); + final byte[] hdr = new byte[64]; + IO.readFully(in, hdr, 0, 2); + + if (isStandardFormat(hdr)) { + in.reset(); + Inflater inf = wc.inflater(); + InputStream zIn = inflate(in, inf); + int avail = readSome(zIn, hdr, 0, 64); + if (avail < 5) + throw new CorruptObjectException(id, + JGitText.get().corruptObjectNoHeader); + + final MutableInteger p = new MutableInteger(); + int type = Constants.decodeTypeString(id, hdr, (byte) ' ', p); + long size = RawParseUtils.parseLongBase10(hdr, p.value, p); + if (size < 0) + throw new CorruptObjectException(id, + JGitText.get().corruptObjectNegativeSize); + if (hdr[p.value++] != 0) + throw new CorruptObjectException(id, + JGitText.get().corruptObjectGarbageAfterSize); + if (path == null && Integer.MAX_VALUE < size) + throw new LargeObjectException(id.copy()); + if (size < wc.getStreamFileThreshold() || path == null) { + byte[] data = new byte[(int) size]; + int n = avail - p.value; + if (n > 0) + System.arraycopy(hdr, p.value, data, 0, n); + IO.readFully(zIn, data, n, data.length - n); + checkValidEndOfStream(in, inf, id, hdr); + return new ObjectLoader.SmallObject(type, data); + } + return new LargeObject(type, size, path, id, wc.db); + + } else { + readSome(in, hdr, 2, 18); + int c = hdr[0] & 0xff; + int type = (c >> 4) & 7; + long size = c & 15; + int shift = 4; + int p = 1; + while ((c & 0x80) != 0) { + c = hdr[p++] & 0xff; + size += (c & 0x7f) << shift; + shift += 7; + } + + switch (type) { + case Constants.OBJ_COMMIT: + case Constants.OBJ_TREE: + case Constants.OBJ_BLOB: + case Constants.OBJ_TAG: + // Acceptable types for a loose object. + break; + default: + throw new CorruptObjectException(id, + JGitText.get().corruptObjectInvalidType); + } + + if (path == null && Integer.MAX_VALUE < size) + throw new LargeObjectException(id.copy()); + if (size < wc.getStreamFileThreshold() || path == null) { + in.reset(); + IO.skipFully(in, p); + Inflater inf = wc.inflater(); + InputStream zIn = inflate(in, inf); + byte[] data = new byte[(int) size]; + IO.readFully(zIn, data, 0, data.length); + checkValidEndOfStream(in, inf, id, hdr); + return new ObjectLoader.SmallObject(type, data); + } + return new LargeObject(type, size, path, id, wc.db); + } + } catch (ZipException badStream) { + throw new CorruptObjectException(id, + JGitText.get().corruptObjectBadStream); + } + } + + static long getSize(InputStream in, AnyObjectId id, WindowCursor wc) + throws IOException { + try { + in = buffer(in); + in.mark(20); + final byte[] hdr = new byte[64]; + IO.readFully(in, hdr, 0, 2); + + if (isStandardFormat(hdr)) { + in.reset(); + Inflater inf = wc.inflater(); + InputStream zIn = inflate(in, inf); + int avail = readSome(zIn, hdr, 0, 64); + if (avail < 5) + throw new CorruptObjectException(id, + JGitText.get().corruptObjectNoHeader); + + final MutableInteger p = new MutableInteger(); + Constants.decodeTypeString(id, hdr, (byte) ' ', p); + long size = RawParseUtils.parseLongBase10(hdr, p.value, p); + if (size < 0) + throw new CorruptObjectException(id, + JGitText.get().corruptObjectNegativeSize); + return size; + + } else { + readSome(in, hdr, 2, 18); + int c = hdr[0] & 0xff; + long size = c & 15; + int shift = 4; + int p = 1; + while ((c & 0x80) != 0) { + c = hdr[p++] & 0xff; + size += (c & 0x7f) << shift; + shift += 7; + } + return size; + } + } catch (ZipException badStream) { + throw new CorruptObjectException(id, + JGitText.get().corruptObjectBadStream); + } + } + + private static void checkValidEndOfStream(InputStream in, Inflater inf, + AnyObjectId id, final byte[] buf) throws IOException, + CorruptObjectException { + for (;;) { + int r; + try { + r = inf.inflate(buf); + } catch (DataFormatException e) { + throw new CorruptObjectException(id, + JGitText.get().corruptObjectBadStream); + } + if (r != 0) + throw new CorruptObjectException(id, + JGitText.get().corruptObjectIncorrectLength); + + if (inf.finished()) { + if (inf.getRemaining() != 0 || in.read() != -1) + throw new CorruptObjectException(id, + JGitText.get().corruptObjectBadStream); + break; + } + + if (!inf.needsInput()) + throw new CorruptObjectException(id, + JGitText.get().corruptObjectBadStream); + + r = in.read(buf); + if (r <= 0) + throw new CorruptObjectException(id, + JGitText.get().corruptObjectBadStream); + inf.setInput(buf, 0, r); + } + } + + private static boolean isStandardFormat(final byte[] hdr) { + // Try to determine if this is a standard format loose object or + // a pack style loose object. The standard format is 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 pack style object. + // + final int fb = hdr[0] & 0xff; + return fb == 0x78 && (((fb << 8) | hdr[1] & 0xff) % 31) == 0; + } + + private static InputStream inflate(final InputStream in, final long size, + final ObjectId id) { + final Inflater inf = InflaterCache.get(); + return new InflaterInputStream(in, inf) { + private long remaining = size; + + @Override + public int read(byte[] b, int off, int cnt) throws IOException { + try { + int r = super.read(b, off, cnt); + if (r > 0) + remaining -= r; + return r; + } catch (ZipException badStream) { + throw new CorruptObjectException(id, + JGitText.get().corruptObjectBadStream); + } + } + + @Override + public void close() throws IOException { + try { + if (remaining <= 0) + checkValidEndOfStream(in, inf, id, new byte[64]); + super.close(); + } finally { + InflaterCache.release(inf); + } + } + }; + } + + private static InflaterInputStream inflate(InputStream in, Inflater inf) { + return new InflaterInputStream(in, inf, BUFFER_SIZE); + } + + private static BufferedInputStream buffer(InputStream in) { + return new BufferedInputStream(in, BUFFER_SIZE); + } + + private static int readSome(InputStream in, final byte[] hdr, int off, + int cnt) throws IOException { + int avail = 0; + while (0 < cnt) { + int n = in.read(hdr, off, cnt); + if (n < 0) + break; + avail += n; + cnt -= n; + } + return avail; + } + + private static final class LargeObject extends ObjectLoader { + private final int type; + + private final long size; + + private final File path; + + private final ObjectId id; + + private final FileObjectDatabase source; + + private LargeObject(int type, long size, File path, AnyObjectId id, + FileObjectDatabase db) { + this.type = type; + this.size = size; + this.path = path; + this.id = id.copy(); + this.source = db; + } + + @Override + public int getType() { + return type; + } + + @Override + public long getSize() { + return size; + } + + @Override + public boolean isLarge() { + return true; + } + + @Override + public byte[] getCachedBytes() throws LargeObjectException { + throw new LargeObjectException(id); + } + + @Override + public ObjectStream openStream() throws MissingObjectException, + IOException { + InputStream in; + try { + in = buffer(new FileInputStream(path)); + } catch (FileNotFoundException gone) { + // If the loose file no longer exists, it may have been + // moved into a pack file in the mean time. Try again + // to locate the object. + // + return source.open(id, type).openStream(); + } + + boolean ok = false; + try { + final byte[] hdr = new byte[64]; + in.mark(20); + IO.readFully(in, hdr, 0, 2); + + if (isStandardFormat(hdr)) { + in.reset(); + in = buffer(inflate(in, size, id)); + while (0 < in.read()) + continue; + } else { + readSome(in, hdr, 2, 18); + int c = hdr[0] & 0xff; + int p = 1; + while ((c & 0x80) != 0) + c = hdr[p++] & 0xff; + + in.reset(); + IO.skipFully(in, p); + in = buffer(inflate(in, size, id)); + } + + ok = true; + return new ObjectStream.Filter(type, size, in); + } finally { + if (!ok) + in.close(); + } + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectCache.java index 3cef48242d..92f4824254 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectCache.java @@ -41,7 +41,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.lang.ref.SoftReference; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCache.java index a44a30ee2d..39633ee5ef 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCache.java @@ -42,7 +42,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.IOException; import java.lang.ref.ReferenceQueue; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java index 2d8aef34ca..48d7018e42 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java @@ -41,7 +41,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; + +import org.eclipse.jgit.lib.Config; /** Configuration parameters for {@link WindowCache}. */ public class WindowCacheConfig { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java index 968c92e5ce..5376d077ad 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java @@ -42,14 +42,28 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +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.IncorrectObjectTypeException; +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.revwalk.RevObject; +import org.eclipse.jgit.storage.pack.ObjectReuseAsIs; +import org.eclipse.jgit.storage.pack.ObjectToPack; +import org.eclipse.jgit.storage.pack.PackOutputStream; +import org.eclipse.jgit.storage.pack.PackWriter; + /** Active handle to a ByteWindow. */ -public final class WindowCursor { +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]; @@ -57,6 +71,62 @@ public final class WindowCursor { private ByteWindow window; + final FileObjectDatabase db; + + WindowCursor(FileObjectDatabase db) { + this.db = db; + } + + @Override + public ObjectReader newReader() { + return new WindowCursor(db); + } + + public boolean has(AnyObjectId objectId) throws IOException { + return db.has(objectId); + } + + public ObjectLoader open(AnyObjectId objectId, int typeHint) + throws MissingObjectException, IncorrectObjectTypeException, + 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); + } + if (typeHint != OBJ_ANY && ldr.getType() != typeHint) + throw new IncorrectObjectTypeException(objectId.copy(), typeHint); + return ldr; + } + + public long getObjectSize(AnyObjectId objectId, int typeHint) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + long sz = db.getObjectSize(this, objectId); + if (sz < 0) { + if (typeHint == OBJ_ANY) + throw new MissingObjectException(objectId.copy(), "unknown"); + throw new MissingObjectException(objectId.copy(), typeHint); + } + return sz; + } + + 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.pack.copyAsIs(out, src, this); + } + /** * Copy bytes from the window to a caller supplied buffer. * @@ -73,8 +143,8 @@ public final class WindowCursor { * bytes remaining in the window starting at offset * <code>pos</code>. * @return number of bytes actually copied; this may be less than - * <code>cnt</code> if <code>cnt</code> exceeded the number of - * bytes available. + * <code>cnt</code> if <code>cnt</code> 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. @@ -94,7 +164,7 @@ public final class WindowCursor { } /** - * Pump bytes into the supplied inflater as input. + * Inflate a region of the pack starting at {@code position}. * * @param pack * the file the desired window is stored within. @@ -117,25 +187,36 @@ public final class WindowCursor { 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; - } + pin(pack, position); + position += window.setInput(position, inf); + do { + int n = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff); + if (n == 0) { + if (inf.needsInput()) { + pin(pack, position); + position += window.setInput(position, inf); + } else if (inf.finished()) + return dstoff; + else + throw new DataFormatException(); + } + dstoff += n; + } while (dstoff < dstbuf.length); + return dstoff; } - void inflateVerify(final PackFile pack, long position) - throws IOException, DataFormatException { + 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(); - for (;;) { - pin(pack, position); - window.inflateVerify(position, inf); - if (inf.finished()) - return; - position = window.end; - } + return inf; } private void prepareInflater() { @@ -145,7 +226,7 @@ public final class WindowCursor { inf.reset(); } - private void pin(final PackFile pack, final long position) + void pin(final PackFile pack, final long position) throws IOException { final ByteWindow w = window; if (w == null || !w.contains(pack, position)) { @@ -159,6 +240,12 @@ public final class WindowCursor { } } + int getStreamFileThreshold() { + if (db == null) + return ObjectLoader.STREAM_THRESHOLD; + return db.getStreamFileThreshold(); + } + /** Release the current window cursor. */ public void release() { window = null; @@ -168,14 +255,4 @@ public final class WindowCursor { inf = null; } } - - /** - * @param curs cursor to release; may be null. - * @return always null. - */ - public static WindowCursor release(final WindowCursor curs) { - if (curs != null) - curs.release(); - return null; - } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BinaryDelta.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/BinaryDelta.java index a59b335330..1d433d78a6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BinaryDelta.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/BinaryDelta.java @@ -42,9 +42,11 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.pack; import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.util.QuotedString; +import org.eclipse.jgit.util.RawParseUtils; /** * Recreate a stream from a base stream and a GIT pack delta. @@ -55,6 +57,51 @@ import org.eclipse.jgit.JGitText; * </p> */ public class BinaryDelta { + /** + * Length of the base object in the delta stream. + * + * @param delta + * the delta stream, or at least the header of it. + * @return the base object's size. + */ + public static long getBaseSize(final byte[] delta) { + int p = 0; + long baseLen = 0; + int c, shift = 0; + do { + c = delta[p++] & 0xff; + baseLen |= (c & 0x7f) << shift; + shift += 7; + } while ((c & 0x80) != 0); + return baseLen; + } + + /** + * Length of the resulting object in the delta stream. + * + * @param delta + * the delta stream, or at least the header of it. + * @return the resulting object's size. + */ + public static long getResultSize(final byte[] delta) { + int p = 0; + + // Skip length of the base object. + // + int c; + do { + c = delta[p++] & 0xff; + } while ((c & 0x80) != 0); + + long resLen = 0; + int shift = 0; + do { + c = delta[p++] & 0xff; + resLen |= (c & 0x7f) << shift; + shift += 7; + } while ((c & 0x80) != 0); + return resLen; + } /** * Apply the changes defined by delta to the data in base, yielding a new @@ -80,7 +127,8 @@ public class BinaryDelta { shift += 7; } while ((c & 0x80) != 0); if (base.length != baseLen) - throw new IllegalArgumentException(JGitText.get().baseLengthIncorrect); + throw new IllegalArgumentException( + JGitText.get().baseLengthIncorrect); // Length of the resulting object (a variable length int). // @@ -134,10 +182,105 @@ public class BinaryDelta { // cmd == 0 has been reserved for future encoding but // for now its not acceptable. // - throw new IllegalArgumentException(JGitText.get().unsupportedCommand0); + throw new IllegalArgumentException( + JGitText.get().unsupportedCommand0); } } return result; } + + /** + * Format this delta as a human readable string. + * + * @param delta + * the delta instruction sequence to format. + * @return the formatted delta. + */ + public static String format(byte[] delta) { + return format(delta, true); + } + + /** + * Format this delta as a human readable string. + * + * @param delta + * the delta instruction sequence to format. + * @param includeHeader + * true if the header (base size and result size) should be + * included in the formatting. + * @return the formatted delta. + */ + public static String format(byte[] delta, boolean includeHeader) { + StringBuilder r = new StringBuilder(); + int deltaPtr = 0; + + long baseLen = 0; + int c, shift = 0; + do { + c = delta[deltaPtr++] & 0xff; + baseLen |= (c & 0x7f) << shift; + shift += 7; + } while ((c & 0x80) != 0); + + long resLen = 0; + shift = 0; + do { + c = delta[deltaPtr++] & 0xff; + resLen |= (c & 0x7f) << shift; + shift += 7; + } while ((c & 0x80) != 0); + + if (includeHeader) + r.append("DELTA( BASE=" + baseLen + " RESULT=" + resLen + " )\n"); + + while (deltaPtr < delta.length) { + final int cmd = delta[deltaPtr++] & 0xff; + if ((cmd & 0x80) != 0) { + // Determine the segment of the base which should + // be copied into the output. The segment is given + // as an offset and a length. + // + int copyOffset = 0; + if ((cmd & 0x01) != 0) + copyOffset = delta[deltaPtr++] & 0xff; + if ((cmd & 0x02) != 0) + copyOffset |= (delta[deltaPtr++] & 0xff) << 8; + if ((cmd & 0x04) != 0) + copyOffset |= (delta[deltaPtr++] & 0xff) << 16; + if ((cmd & 0x08) != 0) + copyOffset |= (delta[deltaPtr++] & 0xff) << 24; + + int copySize = 0; + if ((cmd & 0x10) != 0) + copySize = delta[deltaPtr++] & 0xff; + if ((cmd & 0x20) != 0) + copySize |= (delta[deltaPtr++] & 0xff) << 8; + if ((cmd & 0x40) != 0) + copySize |= (delta[deltaPtr++] & 0xff) << 16; + if (copySize == 0) + copySize = 0x10000; + + r.append(" COPY (" + copyOffset + ", " + copySize + ")\n"); + + } else if (cmd != 0) { + // Anything else the data is literal within the delta + // itself. + // + r.append(" INSERT("); + r.append(QuotedString.GIT_PATH.quote(RawParseUtils.decode( + delta, deltaPtr, deltaPtr + cmd))); + r.append(")\n"); + deltaPtr += cmd; + } else { + // cmd == 0 has been reserved for future encoding but + // for now its not acceptable. + // + throw new IllegalArgumentException( + JGitText.get().unsupportedCommand0); + } + } + + return r.toString(); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaCache.java new file mode 100644 index 0000000000..b6a7436f16 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaCache.java @@ -0,0 +1,134 @@ +/* + * 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.pack; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; + +class DeltaCache { + private final long size; + + private final int entryLimit; + + private final ReferenceQueue<byte[]> queue; + + private long used; + + DeltaCache(PackWriter pw) { + size = pw.getDeltaCacheSize(); + entryLimit = pw.getDeltaCacheLimit(); + queue = new ReferenceQueue<byte[]>(); + } + + boolean canCache(int length, ObjectToPack src, ObjectToPack res) { + // If the cache would overflow, don't store. + // + if (0 < size && size < used + length) { + checkForGarbageCollectedObjects(); + if (0 < size && size < used + length) + return false; + } + + if (length < entryLimit) { + used += length; + return true; + } + + // If the combined source files are multiple megabytes but the delta + // is on the order of a kilobyte or two, this was likely costly to + // construct. Cache it anyway, even though its over the limit. + // + if (length >> 10 < (src.getWeight() >> 20) + (res.getWeight() >> 21)) { + used += length; + return true; + } + + return false; + } + + void credit(int reservedSize) { + used -= reservedSize; + } + + Ref cache(byte[] data, int actLen, int reservedSize) { + // The caller may have had to allocate more space than is + // required. If we are about to waste anything, shrink it. + // + data = resize(data, actLen); + + // When we reserved space for this item we did it for the + // inflated size of the delta, but we were just given the + // compressed version. Adjust the cache cost to match. + // + if (reservedSize != data.length) { + used -= reservedSize; + used += data.length; + } + return new Ref(data, queue); + } + + byte[] resize(byte[] data, int actLen) { + if (data.length != actLen) { + byte[] nbuf = new byte[actLen]; + System.arraycopy(data, 0, nbuf, 0, actLen); + data = nbuf; + } + return data; + } + + private void checkForGarbageCollectedObjects() { + Ref r; + while ((r = (Ref) queue.poll()) != null) + used -= r.cost; + } + + static class Ref extends SoftReference<byte[]> { + final int cost; + + Ref(byte[] array, ReferenceQueue<byte[]> queue) { + super(array, queue); + cost = array.length; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaEncoder.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaEncoder.java new file mode 100644 index 0000000000..204030b4a7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaEncoder.java @@ -0,0 +1,294 @@ +/* + * 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.pack; + +import java.io.IOException; +import java.io.OutputStream; + +import org.eclipse.jgit.lib.Constants; + +/** Encodes an instruction stream for {@link BinaryDelta}. */ +public class DeltaEncoder { + /** + * Maximum number of bytes to be copied in pack v2 format. + * <p> + * Historical limitations have this at 64k, even though current delta + * decoders recognize larger copy instructions. + */ + private static final int MAX_V2_COPY = 0x10000; + + /* + * Maximum number of bytes to be copied in pack v3 format. + * + * Current delta decoders can recognize a copy instruction with a count that + * is this large, but the historical limitation of {@link MAX_V2_COPY} is + * still used. + */ + // private static final int MAX_V3_COPY = (0xff << 16) | (0xff << 8) | 0xff; + + /** Maximum number of bytes used by a copy instruction. */ + private static final int MAX_COPY_CMD_SIZE = 8; + + /** Maximum length that an an insert command can encode at once. */ + private static final int MAX_INSERT_DATA_SIZE = 127; + + private final OutputStream out; + + private final byte[] buf = new byte[MAX_COPY_CMD_SIZE * 4]; + + private final int limit; + + private int size; + + /** + * Create an encoder with no upper bound on the instruction stream size. + * + * @param out + * buffer to store the instructions written. + * @param baseSize + * size of the base object, in bytes. + * @param resultSize + * size of the resulting object, after applying this instruction + * stream to the base object, in bytes. + * @throws IOException + * the output buffer cannot store the instruction stream's + * header with the size fields. + */ + public DeltaEncoder(OutputStream out, long baseSize, long resultSize) + throws IOException { + this(out, baseSize, resultSize, 0); + } + + /** + * Create an encoder with an upper limit on the instruction size. + * + * @param out + * buffer to store the instructions written. + * @param baseSize + * size of the base object, in bytes. + * @param resultSize + * size of the resulting object, after applying this instruction + * stream to the base object, in bytes. + * @param limit + * maximum number of bytes to write to the out buffer declaring + * the stream is over limit and should be discarded. May be 0 to + * specify an infinite limit. + * @throws IOException + * the output buffer cannot store the instruction stream's + * header with the size fields. + */ + public DeltaEncoder(OutputStream out, long baseSize, long resultSize, + int limit) throws IOException { + this.out = out; + this.limit = limit; + writeVarint(baseSize); + writeVarint(resultSize); + } + + private void writeVarint(long sz) throws IOException { + int p = 0; + while (sz >= 0x80) { + buf[p++] = (byte) (0x80 | (((int) sz) & 0x7f)); + sz >>>= 7; + } + buf[p++] = (byte) (((int) sz) & 0x7f); + size += p; + if (limit <= 0 || size < limit) + out.write(buf, 0, p); + } + + /** @return current size of the delta stream, in bytes. */ + public int getSize() { + return size; + } + + /** + * Insert a literal string of text, in UTF-8 encoding. + * + * @param text + * the string to insert. + * @return true if the insert fits within the limit; false if the insert + * would cause the instruction stream to exceed the limit. + * @throws IOException + * the instruction buffer can't store the instructions. + */ + public boolean insert(String text) throws IOException { + return insert(Constants.encode(text)); + } + + /** + * Insert a literal binary sequence. + * + * @param text + * the binary to insert. + * @return true if the insert fits within the limit; false if the insert + * would cause the instruction stream to exceed the limit. + * @throws IOException + * the instruction buffer can't store the instructions. + */ + public boolean insert(byte[] text) throws IOException { + return insert(text, 0, text.length); + } + + /** + * Insert a literal binary sequence. + * + * @param text + * the binary to insert. + * @param off + * offset within {@code text} to start copying from. + * @param cnt + * number of bytes to insert. + * @return true if the insert fits within the limit; false if the insert + * would cause the instruction stream to exceed the limit. + * @throws IOException + * the instruction buffer can't store the instructions. + */ + public boolean insert(byte[] text, int off, int cnt) + throws IOException { + if (cnt <= 0) + return true; + if (0 < limit) { + int hdrs = cnt / MAX_INSERT_DATA_SIZE; + if (cnt % MAX_INSERT_DATA_SIZE != 0) + hdrs++; + if (limit < size + hdrs + cnt) + return false; + } + do { + int n = Math.min(MAX_INSERT_DATA_SIZE, cnt); + out.write((byte) n); + out.write(text, off, n); + off += n; + cnt -= n; + size += 1 + n; + } while (0 < cnt); + return true; + } + + /** + * Create a copy instruction to copy from the base object. + * + * @param offset + * position in the base object to copy from. This is absolute, + * from the beginning of the base. + * @param cnt + * number of bytes to copy. + * @return true if the copy fits within the limit; false if the copy + * would cause the instruction stream to exceed the limit. + * @throws IOException + * the instruction buffer cannot store the instructions. + */ + public boolean copy(long offset, int cnt) throws IOException { + if (cnt == 0) + return true; + + int p = 0; + + // We cannot encode more than MAX_V2_COPY bytes in a single + // command, so encode that much and start a new command. + // This limit is imposed by the pack file format rules. + // + while (MAX_V2_COPY < cnt) { + p = encodeCopy(p, offset, MAX_V2_COPY); + offset += MAX_V2_COPY; + cnt -= MAX_V2_COPY; + + if (buf.length < p + MAX_COPY_CMD_SIZE) { + if (0 < limit && limit < size + p) + return false; + out.write(buf, 0, p); + size += p; + p = 0; + } + } + + p = encodeCopy(p, offset, cnt); + if (0 < limit && limit < size + p) + return false; + out.write(buf, 0, p); + size += p; + return true; + } + + private int encodeCopy(int p, long offset, int cnt) { + int cmd = 0x80; + final int cmdPtr = p++; // save room for the command + + if ((offset & 0xff) != 0) { + cmd |= 0x01; + buf[p++] = (byte) (offset & 0xff); + } + if ((offset & (0xff << 8)) != 0) { + cmd |= 0x02; + buf[p++] = (byte) ((offset >>> 8) & 0xff); + } + if ((offset & (0xff << 16)) != 0) { + cmd |= 0x04; + buf[p++] = (byte) ((offset >>> 16) & 0xff); + } + if ((offset & (0xff << 24)) != 0) { + cmd |= 0x08; + buf[p++] = (byte) ((offset >>> 24) & 0xff); + } + + if (cnt != MAX_V2_COPY) { + if ((cnt & 0xff) != 0) { + cmd |= 0x10; + buf[p++] = (byte) (cnt & 0xff); + } + if ((cnt & (0xff << 8)) != 0) { + cmd |= 0x20; + buf[p++] = (byte) ((cnt >>> 8) & 0xff); + } + if ((cnt & (0xff << 16)) != 0) { + cmd |= 0x40; + buf[p++] = (byte) ((cnt >>> 16) & 0xff); + } + } + + buf[cmdPtr] = (byte) cmd; + return p; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaIndex.java new file mode 100644 index 0000000000..e548cc9365 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaIndex.java @@ -0,0 +1,586 @@ +/* + * 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.pack; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Index of blocks in a source file. + * <p> + * The index can be passed a result buffer, and output an instruction sequence + * that transforms the source buffer used by the index into the result buffer. + * The instruction sequence can be executed by {@link BinaryDelta} or + * {@link DeltaStream} to recreate the result buffer. + * <p> + * An index stores the entire contents of the source buffer, but also a table of + * block identities mapped to locations where the block appears in the source + * buffer. The mapping table uses 12 bytes for every 16 bytes of source buffer, + * and is therefore ~75% of the source buffer size. The overall index is ~1.75x + * the size of the source buffer. This relationship holds for any JVM, as only a + * constant number of objects are allocated per index. Callers can use the + * method {@link #getIndexSize()} to obtain a reasonably accurate estimate of + * the complete heap space used by this index. + * <p> + * A {@code DeltaIndex} is thread-safe. Concurrent threads can use the same + * index to encode delta instructions for different result buffers. + */ +public class DeltaIndex { + /** Number of bytes in a block. */ + static final int BLKSZ = 16; // must be 16, see unrolled loop in hashBlock + + /** + * Estimate the size of an index for a given source. + * <p> + * This is roughly a worst-case estimate. The actual index may be smaller. + * + * @param sourceLength + * length of the source, in bytes. + * @return estimated size. Approximately {@code 1.75 * sourceLength}. + */ + public static long estimateIndexSize(int sourceLength) { + return sourceLength + (sourceLength * 3 / 4); + } + + /** + * Maximum number of positions to consider for a given content hash. + * <p> + * All positions with the same content hash are stored into a single chain. + * The chain size is capped to ensure delta encoding stays linear time at + * O(len_src + len_dst) rather than quadratic at O(len_src * len_dst). + */ + private static final int MAX_CHAIN_LENGTH = 64; + + /** Original source file that we indexed. */ + private final byte[] src; + + /** + * Pointers into the {@link #entries} table, indexed by block hash. + * <p> + * A block hash is masked with {@link #tableMask} to become the array index + * of this table. The value stored here is the first index within + * {@link #entries} that starts the consecutive list of blocks with that + * same masked hash. If there are no matching blocks, 0 is stored instead. + * <p> + * Note that this table is always a power of 2 in size, to support fast + * normalization of a block hash into an array index. + */ + private final int[] table; + + /** + * Pairs of block hash value and {@link #src} offsets. + * <p> + * The very first entry in this table at index 0 is always empty, this is to + * allow fast evaluation when {@link #table} has no values under any given + * slot. Remaining entries are pairs of integers, with the upper 32 bits + * holding the block hash and the lower 32 bits holding the source offset. + */ + private final long[] entries; + + /** Mask to make block hashes into an array index for {@link #table}. */ + private final int tableMask; + + /** + * Construct an index from the source file. + * + * @param sourceBuffer + * the source file's raw contents. The buffer will be held by the + * index instance to facilitate matching, and therefore must not + * be modified by the caller. + */ + public DeltaIndex(byte[] sourceBuffer) { + src = sourceBuffer; + + DeltaIndexScanner scan = new DeltaIndexScanner(src, src.length); + + // Reuse the same table the scanner made. We will replace the + // values at each position, but we want the same-length array. + // + table = scan.table; + tableMask = scan.tableMask; + + // Because entry index 0 means there are no entries for the + // slot in the table, we have to allocate one extra position. + // + entries = new long[1 + countEntries(scan)]; + copyEntries(scan); + } + + private int countEntries(DeltaIndexScanner scan) { + // Figure out exactly how many entries we need. As we do the + // enumeration truncate any delta chains longer than what we + // are willing to scan during encode. This keeps the encode + // logic linear in the size of the input rather than quadratic. + // + int cnt = 0; + for (int i = 0; i < table.length; i++) { + int h = table[i]; + if (h == 0) + continue; + + int len = 0; + do { + if (++len == MAX_CHAIN_LENGTH) { + scan.next[h] = 0; + break; + } + h = scan.next[h]; + } while (h != 0); + cnt += len; + } + return cnt; + } + + private void copyEntries(DeltaIndexScanner scan) { + // Rebuild the entries list from the scanner, positioning all + // blocks in the same hash chain next to each other. We can + // then later discard the next list, along with the scanner. + // + int next = 1; + for (int i = 0; i < table.length; i++) { + int h = table[i]; + if (h == 0) + continue; + + table[i] = next; + do { + entries[next++] = scan.entries[h]; + h = scan.next[h]; + } while (h != 0); + } + } + + /** @return size of the source buffer this index has scanned. */ + public long getSourceSize() { + return src.length; + } + + /** + * Get an estimate of the memory required by this index. + * + * @return an approximation of the number of bytes used by this index in + * memory. The size includes the cached source buffer size from + * {@link #getSourceSize()}, as well as a rough approximation of JVM + * object overheads. + */ + public long getIndexSize() { + long sz = 8 /* object header */; + sz += 4 /* fields */* 4 /* guessed size per field */; + sz += sizeOf(src); + sz += sizeOf(table); + sz += sizeOf(entries); + return sz; + } + + private static long sizeOf(byte[] b) { + return sizeOfArray(1, b.length); + } + + private static long sizeOf(int[] b) { + return sizeOfArray(4, b.length); + } + + private static long sizeOf(long[] b) { + return sizeOfArray(8, b.length); + } + + private static int sizeOfArray(int entSize, int len) { + return 12 /* estimated array header size */+ (len * entSize); + } + + /** + * Generate a delta sequence to recreate the result buffer. + * <p> + * There is no limit on the size of the delta sequence created. This is the + * same as {@code encode(out, res, 0)}. + * + * @param out + * stream to receive the delta instructions that can transform + * this index's source buffer into {@code res}. This stream + * should be buffered, as instructions are written directly to it + * in small bursts. + * @param res + * the desired result buffer. The generated instructions will + * recreate this buffer when applied to the source buffer stored + * within this index. + * @throws IOException + * the output stream refused to write the instructions. + */ + public void encode(OutputStream out, byte[] res) throws IOException { + encode(out, res, 0 /* no limit */); + } + + /** + * Generate a delta sequence to recreate the result buffer. + * + * @param out + * stream to receive the delta instructions that can transform + * this index's source buffer into {@code res}. This stream + * should be buffered, as instructions are written directly to it + * in small bursts. If the caller might need to discard the + * instructions (such as when deltaSizeLimit would be exceeded) + * the caller is responsible for discarding or rewinding the + * stream when this method returns false. + * @param res + * the desired result buffer. The generated instructions will + * recreate this buffer when applied to the source buffer stored + * within this index. + * @param deltaSizeLimit + * maximum number of bytes that the delta instructions can + * occupy. If the generated instructions would be longer than + * this amount, this method returns false. If 0, there is no + * limit on the length of delta created. + * @return true if the delta is smaller than deltaSizeLimit; false if the + * encoder aborted because the encoded delta instructions would be + * longer than deltaSizeLimit bytes. + * @throws IOException + * the output stream refused to write the instructions. + */ + public boolean encode(OutputStream out, byte[] res, int deltaSizeLimit) + throws IOException { + final int end = res.length; + final DeltaEncoder enc = newEncoder(out, end, deltaSizeLimit); + + // If either input is smaller than one full block, we simply punt + // and construct a delta as a literal. This implies that any file + // smaller than our block size is never delta encoded as the delta + // will always be larger than the file itself would be. + // + if (end < BLKSZ || table.length == 0) + return enc.insert(res); + + // Bootstrap the scan by constructing a hash for the first block + // in the input. + // + int blkPtr = 0; + int blkEnd = BLKSZ; + int hash = hashBlock(res, 0); + + int resPtr = 0; + while (blkEnd < end) { + final int tableIdx = hash & tableMask; + int entryIdx = table[tableIdx]; + if (entryIdx == 0) { + // No matching blocks, slide forward one byte. + // + hash = step(hash, res[blkPtr++], res[blkEnd++]); + continue; + } + + // For every possible location of the current block, try to + // extend the match out to the longest common substring. + // + int bestLen = -1; + int bestPtr = -1; + int bestNeg = 0; + do { + long ent = entries[entryIdx++]; + if (keyOf(ent) == hash) { + int neg = 0; + if (resPtr < blkPtr) { + // If we need to do an insertion, check to see if + // moving the starting point of the copy backwards + // will allow us to shorten the insert. Our hash + // may not have allowed us to identify this area. + // Since it is quite fast to perform a negative + // scan, try to stretch backwards too. + // + neg = blkPtr - resPtr; + neg = negmatch(res, blkPtr, src, valOf(ent), neg); + } + + int len = neg + fwdmatch(res, blkPtr, src, valOf(ent)); + if (bestLen < len) { + bestLen = len; + bestPtr = valOf(ent); + bestNeg = neg; + } + } else if ((keyOf(ent) & tableMask) != tableIdx) + break; + } while (bestLen < 4096 && entryIdx < entries.length); + + if (bestLen < BLKSZ) { + // All of the locations were false positives, or the copy + // is shorter than a block. In the latter case this won't + // give us a very great copy instruction, so delay and try + // at the next byte. + // + hash = step(hash, res[blkPtr++], res[blkEnd++]); + continue; + } + + blkPtr -= bestNeg; + + if (resPtr < blkPtr) { + // There are bytes between the last instruction we made + // and the current block pointer. None of these matched + // during the earlier iteration so insert them directly + // into the instruction stream. + // + int cnt = blkPtr - resPtr; + if (!enc.insert(res, resPtr, cnt)) + return false; + } + + if (!enc.copy(bestPtr - bestNeg, bestLen)) + return false; + + blkPtr += bestLen; + resPtr = blkPtr; + blkEnd = blkPtr + BLKSZ; + + // If we don't have a full block available to us, abort now. + // + if (end <= blkEnd) + break; + + // Start a new hash of the block after the copy region. + // + hash = hashBlock(res, blkPtr); + } + + if (resPtr < end) { + // There were bytes at the end which didn't match, or maybe + // didn't make a full block. Insert whatever is left over. + // + int cnt = end - resPtr; + return enc.insert(res, resPtr, cnt); + } + return true; + } + + private DeltaEncoder newEncoder(OutputStream out, long resSize, int limit) + throws IOException { + return new DeltaEncoder(out, getSourceSize(), resSize, limit); + } + + private static int fwdmatch(byte[] res, int resPtr, byte[] src, int srcPtr) { + int start = resPtr; + for (; resPtr < res.length && srcPtr < src.length; resPtr++, srcPtr++) { + if (res[resPtr] != src[srcPtr]) + break; + } + return resPtr - start; + } + + private static int negmatch(byte[] res, int resPtr, byte[] src, int srcPtr, + int limit) { + if (srcPtr == 0) + return 0; + + resPtr--; + srcPtr--; + int start = resPtr; + do { + if (res[resPtr] != src[srcPtr]) + break; + resPtr--; + srcPtr--; + } while (0 <= srcPtr && 0 < --limit); + return start - resPtr; + } + + public String toString() { + String[] units = { "bytes", "KiB", "MiB", "GiB" }; + long sz = getIndexSize(); + int u = 0; + while (1024 <= sz && u < units.length - 1) { + int rem = (int) (sz % 1024); + sz /= 1024; + if (rem != 0) + sz++; + u++; + } + return "DeltaIndex[" + sz + " " + units[u] + "]"; + } + + static int hashBlock(byte[] raw, int ptr) { + int hash; + + // The first 4 steps collapse out into a 4 byte big-endian decode, + // with a larger right shift as we combined shift lefts together. + // + hash = ((raw[ptr] & 0xff) << 24) // + | ((raw[ptr + 1] & 0xff) << 16) // + | ((raw[ptr + 2] & 0xff) << 8) // + | (raw[ptr + 3] & 0xff); + hash ^= T[hash >>> 31]; + + hash = ((hash << 8) | (raw[ptr + 4] & 0xff)) ^ T[hash >>> 23]; + hash = ((hash << 8) | (raw[ptr + 5] & 0xff)) ^ T[hash >>> 23]; + hash = ((hash << 8) | (raw[ptr + 6] & 0xff)) ^ T[hash >>> 23]; + hash = ((hash << 8) | (raw[ptr + 7] & 0xff)) ^ T[hash >>> 23]; + + hash = ((hash << 8) | (raw[ptr + 8] & 0xff)) ^ T[hash >>> 23]; + hash = ((hash << 8) | (raw[ptr + 9] & 0xff)) ^ T[hash >>> 23]; + hash = ((hash << 8) | (raw[ptr + 10] & 0xff)) ^ T[hash >>> 23]; + hash = ((hash << 8) | (raw[ptr + 11] & 0xff)) ^ T[hash >>> 23]; + + hash = ((hash << 8) | (raw[ptr + 12] & 0xff)) ^ T[hash >>> 23]; + hash = ((hash << 8) | (raw[ptr + 13] & 0xff)) ^ T[hash >>> 23]; + hash = ((hash << 8) | (raw[ptr + 14] & 0xff)) ^ T[hash >>> 23]; + hash = ((hash << 8) | (raw[ptr + 15] & 0xff)) ^ T[hash >>> 23]; + + return hash; + } + + private static int step(int hash, byte toRemove, byte toAdd) { + hash ^= U[toRemove & 0xff]; + return ((hash << 8) | (toAdd & 0xff)) ^ T[hash >>> 23]; + } + + private static int keyOf(long ent) { + return (int) (ent >>> 32); + } + + private static int valOf(long ent) { + return (int) ent; + } + + private static final int[] T = { 0x00000000, 0xd4c6b32d, 0x7d4bd577, + 0xa98d665a, 0x2e5119c3, 0xfa97aaee, 0x531accb4, 0x87dc7f99, + 0x5ca23386, 0x886480ab, 0x21e9e6f1, 0xf52f55dc, 0x72f32a45, + 0xa6359968, 0x0fb8ff32, 0xdb7e4c1f, 0x6d82d421, 0xb944670c, + 0x10c90156, 0xc40fb27b, 0x43d3cde2, 0x97157ecf, 0x3e981895, + 0xea5eabb8, 0x3120e7a7, 0xe5e6548a, 0x4c6b32d0, 0x98ad81fd, + 0x1f71fe64, 0xcbb74d49, 0x623a2b13, 0xb6fc983e, 0x0fc31b6f, + 0xdb05a842, 0x7288ce18, 0xa64e7d35, 0x219202ac, 0xf554b181, + 0x5cd9d7db, 0x881f64f6, 0x536128e9, 0x87a79bc4, 0x2e2afd9e, + 0xfaec4eb3, 0x7d30312a, 0xa9f68207, 0x007be45d, 0xd4bd5770, + 0x6241cf4e, 0xb6877c63, 0x1f0a1a39, 0xcbcca914, 0x4c10d68d, + 0x98d665a0, 0x315b03fa, 0xe59db0d7, 0x3ee3fcc8, 0xea254fe5, + 0x43a829bf, 0x976e9a92, 0x10b2e50b, 0xc4745626, 0x6df9307c, + 0xb93f8351, 0x1f8636de, 0xcb4085f3, 0x62cde3a9, 0xb60b5084, + 0x31d72f1d, 0xe5119c30, 0x4c9cfa6a, 0x985a4947, 0x43240558, + 0x97e2b675, 0x3e6fd02f, 0xeaa96302, 0x6d751c9b, 0xb9b3afb6, + 0x103ec9ec, 0xc4f87ac1, 0x7204e2ff, 0xa6c251d2, 0x0f4f3788, + 0xdb8984a5, 0x5c55fb3c, 0x88934811, 0x211e2e4b, 0xf5d89d66, + 0x2ea6d179, 0xfa606254, 0x53ed040e, 0x872bb723, 0x00f7c8ba, + 0xd4317b97, 0x7dbc1dcd, 0xa97aaee0, 0x10452db1, 0xc4839e9c, + 0x6d0ef8c6, 0xb9c84beb, 0x3e143472, 0xead2875f, 0x435fe105, + 0x97995228, 0x4ce71e37, 0x9821ad1a, 0x31accb40, 0xe56a786d, + 0x62b607f4, 0xb670b4d9, 0x1ffdd283, 0xcb3b61ae, 0x7dc7f990, + 0xa9014abd, 0x008c2ce7, 0xd44a9fca, 0x5396e053, 0x8750537e, + 0x2edd3524, 0xfa1b8609, 0x2165ca16, 0xf5a3793b, 0x5c2e1f61, + 0x88e8ac4c, 0x0f34d3d5, 0xdbf260f8, 0x727f06a2, 0xa6b9b58f, + 0x3f0c6dbc, 0xebcade91, 0x4247b8cb, 0x96810be6, 0x115d747f, + 0xc59bc752, 0x6c16a108, 0xb8d01225, 0x63ae5e3a, 0xb768ed17, + 0x1ee58b4d, 0xca233860, 0x4dff47f9, 0x9939f4d4, 0x30b4928e, + 0xe47221a3, 0x528eb99d, 0x86480ab0, 0x2fc56cea, 0xfb03dfc7, + 0x7cdfa05e, 0xa8191373, 0x01947529, 0xd552c604, 0x0e2c8a1b, + 0xdaea3936, 0x73675f6c, 0xa7a1ec41, 0x207d93d8, 0xf4bb20f5, + 0x5d3646af, 0x89f0f582, 0x30cf76d3, 0xe409c5fe, 0x4d84a3a4, + 0x99421089, 0x1e9e6f10, 0xca58dc3d, 0x63d5ba67, 0xb713094a, + 0x6c6d4555, 0xb8abf678, 0x11269022, 0xc5e0230f, 0x423c5c96, + 0x96faefbb, 0x3f7789e1, 0xebb13acc, 0x5d4da2f2, 0x898b11df, + 0x20067785, 0xf4c0c4a8, 0x731cbb31, 0xa7da081c, 0x0e576e46, + 0xda91dd6b, 0x01ef9174, 0xd5292259, 0x7ca44403, 0xa862f72e, + 0x2fbe88b7, 0xfb783b9a, 0x52f55dc0, 0x8633eeed, 0x208a5b62, + 0xf44ce84f, 0x5dc18e15, 0x89073d38, 0x0edb42a1, 0xda1df18c, + 0x739097d6, 0xa75624fb, 0x7c2868e4, 0xa8eedbc9, 0x0163bd93, + 0xd5a50ebe, 0x52797127, 0x86bfc20a, 0x2f32a450, 0xfbf4177d, + 0x4d088f43, 0x99ce3c6e, 0x30435a34, 0xe485e919, 0x63599680, + 0xb79f25ad, 0x1e1243f7, 0xcad4f0da, 0x11aabcc5, 0xc56c0fe8, + 0x6ce169b2, 0xb827da9f, 0x3ffba506, 0xeb3d162b, 0x42b07071, + 0x9676c35c, 0x2f49400d, 0xfb8ff320, 0x5202957a, 0x86c42657, + 0x011859ce, 0xd5deeae3, 0x7c538cb9, 0xa8953f94, 0x73eb738b, + 0xa72dc0a6, 0x0ea0a6fc, 0xda6615d1, 0x5dba6a48, 0x897cd965, + 0x20f1bf3f, 0xf4370c12, 0x42cb942c, 0x960d2701, 0x3f80415b, + 0xeb46f276, 0x6c9a8def, 0xb85c3ec2, 0x11d15898, 0xc517ebb5, + 0x1e69a7aa, 0xcaaf1487, 0x632272dd, 0xb7e4c1f0, 0x3038be69, + 0xe4fe0d44, 0x4d736b1e, 0x99b5d833 }; + + private static final int[] U = { 0x00000000, 0x12c6e90f, 0x258dd21e, + 0x374b3b11, 0x4b1ba43c, 0x59dd4d33, 0x6e967622, 0x7c509f2d, + 0x42f1fb55, 0x5037125a, 0x677c294b, 0x75bac044, 0x09ea5f69, + 0x1b2cb666, 0x2c678d77, 0x3ea16478, 0x51254587, 0x43e3ac88, + 0x74a89799, 0x666e7e96, 0x1a3ee1bb, 0x08f808b4, 0x3fb333a5, + 0x2d75daaa, 0x13d4bed2, 0x011257dd, 0x36596ccc, 0x249f85c3, + 0x58cf1aee, 0x4a09f3e1, 0x7d42c8f0, 0x6f8421ff, 0x768c3823, + 0x644ad12c, 0x5301ea3d, 0x41c70332, 0x3d979c1f, 0x2f517510, + 0x181a4e01, 0x0adca70e, 0x347dc376, 0x26bb2a79, 0x11f01168, + 0x0336f867, 0x7f66674a, 0x6da08e45, 0x5aebb554, 0x482d5c5b, + 0x27a97da4, 0x356f94ab, 0x0224afba, 0x10e246b5, 0x6cb2d998, + 0x7e743097, 0x493f0b86, 0x5bf9e289, 0x655886f1, 0x779e6ffe, + 0x40d554ef, 0x5213bde0, 0x2e4322cd, 0x3c85cbc2, 0x0bcef0d3, + 0x190819dc, 0x39dec36b, 0x2b182a64, 0x1c531175, 0x0e95f87a, + 0x72c56757, 0x60038e58, 0x5748b549, 0x458e5c46, 0x7b2f383e, + 0x69e9d131, 0x5ea2ea20, 0x4c64032f, 0x30349c02, 0x22f2750d, + 0x15b94e1c, 0x077fa713, 0x68fb86ec, 0x7a3d6fe3, 0x4d7654f2, + 0x5fb0bdfd, 0x23e022d0, 0x3126cbdf, 0x066df0ce, 0x14ab19c1, + 0x2a0a7db9, 0x38cc94b6, 0x0f87afa7, 0x1d4146a8, 0x6111d985, + 0x73d7308a, 0x449c0b9b, 0x565ae294, 0x4f52fb48, 0x5d941247, + 0x6adf2956, 0x7819c059, 0x04495f74, 0x168fb67b, 0x21c48d6a, + 0x33026465, 0x0da3001d, 0x1f65e912, 0x282ed203, 0x3ae83b0c, + 0x46b8a421, 0x547e4d2e, 0x6335763f, 0x71f39f30, 0x1e77becf, + 0x0cb157c0, 0x3bfa6cd1, 0x293c85de, 0x556c1af3, 0x47aaf3fc, + 0x70e1c8ed, 0x622721e2, 0x5c86459a, 0x4e40ac95, 0x790b9784, + 0x6bcd7e8b, 0x179de1a6, 0x055b08a9, 0x321033b8, 0x20d6dab7, + 0x73bd86d6, 0x617b6fd9, 0x563054c8, 0x44f6bdc7, 0x38a622ea, + 0x2a60cbe5, 0x1d2bf0f4, 0x0fed19fb, 0x314c7d83, 0x238a948c, + 0x14c1af9d, 0x06074692, 0x7a57d9bf, 0x689130b0, 0x5fda0ba1, + 0x4d1ce2ae, 0x2298c351, 0x305e2a5e, 0x0715114f, 0x15d3f840, + 0x6983676d, 0x7b458e62, 0x4c0eb573, 0x5ec85c7c, 0x60693804, + 0x72afd10b, 0x45e4ea1a, 0x57220315, 0x2b729c38, 0x39b47537, + 0x0eff4e26, 0x1c39a729, 0x0531bef5, 0x17f757fa, 0x20bc6ceb, + 0x327a85e4, 0x4e2a1ac9, 0x5cecf3c6, 0x6ba7c8d7, 0x796121d8, + 0x47c045a0, 0x5506acaf, 0x624d97be, 0x708b7eb1, 0x0cdbe19c, + 0x1e1d0893, 0x29563382, 0x3b90da8d, 0x5414fb72, 0x46d2127d, + 0x7199296c, 0x635fc063, 0x1f0f5f4e, 0x0dc9b641, 0x3a828d50, + 0x2844645f, 0x16e50027, 0x0423e928, 0x3368d239, 0x21ae3b36, + 0x5dfea41b, 0x4f384d14, 0x78737605, 0x6ab59f0a, 0x4a6345bd, + 0x58a5acb2, 0x6fee97a3, 0x7d287eac, 0x0178e181, 0x13be088e, + 0x24f5339f, 0x3633da90, 0x0892bee8, 0x1a5457e7, 0x2d1f6cf6, + 0x3fd985f9, 0x43891ad4, 0x514ff3db, 0x6604c8ca, 0x74c221c5, + 0x1b46003a, 0x0980e935, 0x3ecbd224, 0x2c0d3b2b, 0x505da406, + 0x429b4d09, 0x75d07618, 0x67169f17, 0x59b7fb6f, 0x4b711260, + 0x7c3a2971, 0x6efcc07e, 0x12ac5f53, 0x006ab65c, 0x37218d4d, + 0x25e76442, 0x3cef7d9e, 0x2e299491, 0x1962af80, 0x0ba4468f, + 0x77f4d9a2, 0x653230ad, 0x52790bbc, 0x40bfe2b3, 0x7e1e86cb, + 0x6cd86fc4, 0x5b9354d5, 0x4955bdda, 0x350522f7, 0x27c3cbf8, + 0x1088f0e9, 0x024e19e6, 0x6dca3819, 0x7f0cd116, 0x4847ea07, + 0x5a810308, 0x26d19c25, 0x3417752a, 0x035c4e3b, 0x119aa734, + 0x2f3bc34c, 0x3dfd2a43, 0x0ab61152, 0x1870f85d, 0x64206770, + 0x76e68e7f, 0x41adb56e, 0x536b5c61 }; +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaIndexScanner.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaIndexScanner.java new file mode 100644 index 0000000000..d30690d401 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaIndexScanner.java @@ -0,0 +1,130 @@ +/* + * 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.pack; + +/** + * Supports {@link DeltaIndex} by performing a partial scan of the content. + */ +class DeltaIndexScanner { + final int[] table; + + // To save memory the buckets for hash chains are stored in correlated + // arrays. This permits us to get 3 values per entry, without paying + // the penalty for an object header on each entry. + + final long[] entries; + + final int[] next; + + final int tableMask; + + private int entryCnt; + + DeltaIndexScanner(byte[] raw, int len) { + // Clip the length so it falls on a block boundary. We won't + // bother to scan the final partial block. + // + len -= (len % DeltaIndex.BLKSZ); + + final int worstCaseBlockCnt = len / DeltaIndex.BLKSZ; + if (worstCaseBlockCnt < 1) { + table = new int[] {}; + tableMask = 0; + + entries = new long[] {}; + next = new int[] {}; + + } else { + table = new int[tableSize(worstCaseBlockCnt)]; + tableMask = table.length - 1; + + // As we insert blocks we preincrement so that 0 is never a + // valid entry. Therefore we have to allocate one extra space. + // + entries = new long[1 + worstCaseBlockCnt]; + next = new int[entries.length]; + + scan(raw, len); + } + } + + private void scan(byte[] raw, final int end) { + // We scan the input backwards, and always insert onto the + // front of the chain. This ensures that chains will have lower + // offsets at the front of the chain, allowing us to prefer the + // earlier match rather than the later match. + // + int lastHash = 0; + int ptr = end - DeltaIndex.BLKSZ; + do { + final int key = DeltaIndex.hashBlock(raw, ptr); + final int tIdx = key & tableMask; + + final int head = table[tIdx]; + if (head != 0 && lastHash == key) { + // Two consecutive blocks have the same content hash, + // prefer the earlier block because we want to use the + // longest sequence we can during encoding. + // + entries[head] = (((long) key) << 32) | ptr; + } else { + final int eIdx = ++entryCnt; + entries[eIdx] = (((long) key) << 32) | ptr; + next[eIdx] = head; + table[tIdx] = eIdx; + } + + lastHash = key; + ptr -= DeltaIndex.BLKSZ; + } while (0 <= ptr); + } + + private static int tableSize(final int worstCaseBlockCnt) { + int shift = 32 - Integer.numberOfLeadingZeros(worstCaseBlockCnt); + int sz = 1 << (shift - 1); + if (sz < worstCaseBlockCnt) + sz <<= 1; + return sz; + } +}
\ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaStream.java new file mode 100644 index 0000000000..6f479eb905 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaStream.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com> + * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org> + * 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.pack; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.util.IO; + +/** + * Inflates a delta in an incremental way. + * <p> + * Implementations must provide a means to access a stream for the base object. + * This stream may be accessed multiple times, in order to randomly position it + * to match the copy instructions. A {@code DeltaStream} performs an efficient + * skip by only moving through the delta stream, making restarts of stacked + * deltas reasonably efficient. + */ +public abstract class DeltaStream extends InputStream { + private static final int CMD_COPY = 0; + + private static final int CMD_INSERT = 1; + + private static final int CMD_EOF = 2; + + private final InputStream deltaStream; + + private long baseSize; + + private long resultSize; + + private final byte[] cmdbuf = new byte[512]; + + private int cmdptr; + + private int cmdcnt; + + /** Stream to read from the base object. */ + private InputStream baseStream; + + /** Current position within {@link #baseStream}. */ + private long baseOffset; + + private int curcmd; + + /** If {@code curcmd == CMD_COPY}, position the base has to be at. */ + private long copyOffset; + + /** Total number of bytes in this current command. */ + private int copySize; + + /** + * Construct a delta application stream, reading instructions. + * + * @param deltaStream + * the stream to read delta instructions from. + * @throws IOException + * the delta instruction stream cannot be read, or is + * inconsistent with the the base object information. + */ + public DeltaStream(final InputStream deltaStream) throws IOException { + this.deltaStream = deltaStream; + if (!fill(cmdbuf.length)) + throw new EOFException(); + + // Length of the base object. + // + int c, shift = 0; + do { + c = cmdbuf[cmdptr++] & 0xff; + baseSize |= (c & 0x7f) << shift; + shift += 7; + } while ((c & 0x80) != 0); + + // Length of the resulting object. + // + shift = 0; + do { + c = cmdbuf[cmdptr++] & 0xff; + resultSize |= (c & 0x7f) << shift; + shift += 7; + } while ((c & 0x80) != 0); + + curcmd = next(); + } + + /** + * Open the base stream. + * <p> + * The {@code DeltaStream} may close and reopen the base stream multiple + * times if copy instructions use offsets out of order. This can occur if a + * large block in the file was moved from near the top, to near the bottom. + * In such cases the reopened stream is skipped to the target offset, so + * {@code skip(long)} should be as efficient as possible. + * + * @return stream to read from the base object. This stream should not be + * buffered (or should be only minimally buffered), and does not + * need to support mark/reset. + * @throws IOException + * the base object cannot be opened for reading. + */ + protected abstract InputStream openBase() throws IOException; + + /** + * @return length of the base object, in bytes. + * @throws IOException + * the length of the base cannot be determined. + */ + protected abstract long getBaseSize() throws IOException; + + /** @return total size of this stream, in bytes. */ + public long getSize() { + return resultSize; + } + + @Override + public int read() throws IOException { + byte[] buf = new byte[1]; + int n = read(buf, 0, 1); + return n == 1 ? buf[0] & 0xff : -1; + } + + @Override + public void close() throws IOException { + deltaStream.close(); + if (baseStream != null) + baseStream.close(); + } + + @Override + public long skip(long len) throws IOException { + long act = 0; + while (0 < len) { + long n = Math.min(len, copySize); + switch (curcmd) { + case CMD_COPY: + copyOffset += n; + break; + + case CMD_INSERT: + cmdptr += n; + break; + + case CMD_EOF: + return act; + default: + throw new CorruptObjectException( + JGitText.get().unsupportedCommand0); + } + + act += n; + len -= n; + copySize -= n; + if (copySize == 0) + curcmd = next(); + } + return act; + } + + @Override + public int read(byte[] buf, int off, int len) throws IOException { + int act = 0; + while (0 < len) { + int n = Math.min(len, copySize); + switch (curcmd) { + case CMD_COPY: + seekBase(); + n = baseStream.read(buf, off, n); + if (n < 0) + throw new CorruptObjectException( + JGitText.get().baseLengthIncorrect); + baseOffset += n; + break; + + case CMD_INSERT: + System.arraycopy(cmdbuf, cmdptr, buf, off, n); + cmdptr += n; + break; + + case CMD_EOF: + return 0 < act ? act : -1; + default: + throw new CorruptObjectException( + JGitText.get().unsupportedCommand0); + } + + act += n; + off += n; + len -= n; + copySize -= n; + if (copySize == 0) + curcmd = next(); + } + return act; + } + + private boolean fill(final int need) throws IOException { + int n = have(); + if (need < n) + return true; + if (n == 0) { + cmdptr = 0; + cmdcnt = 0; + } else if (cmdbuf.length - cmdptr < need) { + // There isn't room for the entire worst-case copy command, + // so shift the array down to make sure we can use the entire + // command without having it span across the end of the array. + // + System.arraycopy(cmdbuf, cmdptr, cmdbuf, 0, n); + cmdptr = 0; + cmdcnt = n; + } + + do { + n = deltaStream.read(cmdbuf, cmdcnt, cmdbuf.length - cmdcnt); + if (n < 0) + return 0 < have(); + cmdcnt += n; + } while (cmdcnt < cmdbuf.length); + return true; + } + + private int next() throws IOException { + if (!fill(8)) + return CMD_EOF; + + final int cmd = cmdbuf[cmdptr++] & 0xff; + if ((cmd & 0x80) != 0) { + // Determine the segment of the base which should + // be copied into the output. The segment is given + // as an offset and a length. + // + copyOffset = 0; + if ((cmd & 0x01) != 0) + copyOffset = cmdbuf[cmdptr++] & 0xff; + if ((cmd & 0x02) != 0) + copyOffset |= (cmdbuf[cmdptr++] & 0xff) << 8; + if ((cmd & 0x04) != 0) + copyOffset |= (cmdbuf[cmdptr++] & 0xff) << 16; + if ((cmd & 0x08) != 0) + copyOffset |= (cmdbuf[cmdptr++] & 0xff) << 24; + + copySize = 0; + if ((cmd & 0x10) != 0) + copySize = cmdbuf[cmdptr++] & 0xff; + if ((cmd & 0x20) != 0) + copySize |= (cmdbuf[cmdptr++] & 0xff) << 8; + if ((cmd & 0x40) != 0) + copySize |= (cmdbuf[cmdptr++] & 0xff) << 16; + if (copySize == 0) + copySize = 0x10000; + return CMD_COPY; + + } else if (cmd != 0) { + // Anything else the data is literal within the delta + // itself. Page the entire thing into the cmdbuf, if + // its not already there. + // + fill(cmd); + copySize = cmd; + return CMD_INSERT; + + } else { + // cmd == 0 has been reserved for future encoding but + // for now its not acceptable. + // + throw new CorruptObjectException(JGitText.get().unsupportedCommand0); + } + } + + private int have() { + return cmdcnt - cmdptr; + } + + private void seekBase() throws IOException { + if (baseStream == null) { + baseStream = openBase(); + if (getBaseSize() != baseSize) + throw new CorruptObjectException( + JGitText.get().baseLengthIncorrect); + IO.skipFully(baseStream, copyOffset); + baseOffset = copyOffset; + + } else if (baseOffset < copyOffset) { + IO.skipFully(baseStream, copyOffset - baseOffset); + baseOffset = copyOffset; + + } else if (baseOffset > copyOffset) { + baseStream.close(); + baseStream = openBase(); + IO.skipFully(baseStream, copyOffset); + baseOffset = copyOffset; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java new file mode 100644 index 0000000000..6521e6d3ec --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java @@ -0,0 +1,511 @@ +/* + * 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.pack; + +import java.io.EOFException; +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.Deflater; + +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.LargeObjectException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.util.TemporaryBuffer; + +class DeltaWindow { + private static final int NEXT_RES = 0; + + private static final int NEXT_SRC = 1; + + private final PackWriter writer; + + private final DeltaCache deltaCache; + + private final ObjectReader reader; + + private final DeltaWindowEntry[] window; + + /** Maximum number of bytes to admit to the window at once. */ + private final long maxMemory; + + /** Maximum depth we should create for any delta chain. */ + private final int maxDepth; + + /** Amount of memory we have loaded right now. */ + private long loaded; + + // The object we are currently considering needs a lot of state: + + /** Position of {@link #res} within {@link #window} array. */ + private int resSlot; + + /** + * Maximum delta chain depth the current object can have. + * <p> + * This can be smaller than {@link #maxDepth}. + */ + private int resMaxDepth; + + /** Window entry of the object we are currently considering. */ + private DeltaWindowEntry res; + + /** If we have a delta for {@link #res}, this is the shortest found yet. */ + private TemporaryBuffer.Heap bestDelta; + + /** If we have {@link #bestDelta}, the window position it was created by. */ + private int bestSlot; + + /** Used to compress cached deltas. */ + private Deflater deflater; + + DeltaWindow(PackWriter pw, DeltaCache dc, ObjectReader or) { + writer = pw; + deltaCache = dc; + reader = or; + + // C Git increases the window size supplied by the user by 1. + // We don't know why it does this, but if the user asks for + // window=10, it actually processes with window=11. Because + // the window size has the largest direct impact on the final + // pack file size, we match this odd behavior here to give us + // a better chance of producing a similar sized pack as C Git. + // + // We would prefer to directly honor the user's request since + // PackWriter has a minimum of 2 for the window size, but then + // users might complain that JGit is creating a bigger pack file. + // + window = new DeltaWindowEntry[pw.getDeltaSearchWindowSize() + 1]; + for (int i = 0; i < window.length; i++) + window[i] = new DeltaWindowEntry(); + + maxMemory = pw.getDeltaSearchMemoryLimit(); + maxDepth = pw.getMaxDeltaDepth(); + } + + void search(ProgressMonitor monitor, ObjectToPack[] toSearch, int off, + int cnt) throws IOException { + try { + for (int end = off + cnt; off < end; off++) { + monitor.update(1); + + res = window[resSlot]; + if (0 < maxMemory) { + clear(res); + int tail = next(resSlot); + final long need = estimateSize(toSearch[off]); + while (maxMemory < loaded + need && tail != resSlot) { + clear(window[tail]); + tail = next(tail); + } + } + res.set(toSearch[off]); + + if (res.object.isDoNotDelta()) { + // PackWriter marked edge objects with the + // do-not-delta flag. They are the only ones + // that appear in toSearch with it set, but + // we don't actually want to make a delta for + // them, just need to push them into the window + // so they can be read by other objects. + // + keepInWindow(); + } else { + // Search for a delta for the current window slot. + // + search(); + } + } + } finally { + if (deflater != null) + deflater.end(); + } + } + + private static long estimateSize(ObjectToPack ent) { + return DeltaIndex.estimateIndexSize(ent.getWeight()); + } + + private void clear(DeltaWindowEntry ent) { + if (ent.index != null) + loaded -= ent.index.getIndexSize(); + else if (res.buffer != null) + loaded -= ent.buffer.length; + ent.set(null); + } + + private void search() throws IOException { + // TODO(spearce) If the object is used as a base for other + // objects in this pack we should limit the depth we create + // for ourselves to be the remainder of our longest dependent + // chain and the configured maximum depth. This can happen + // when the dependents are being reused out a pack, but we + // cannot be because we are near the edge of a thin pack. + // + resMaxDepth = maxDepth; + + // Loop through the window backwards, considering every entry. + // This lets us look at the bigger objects that came before. + // + for (int srcSlot = prior(resSlot); srcSlot != resSlot; srcSlot = prior(srcSlot)) { + DeltaWindowEntry src = window[srcSlot]; + if (src.empty()) + break; + if (delta(src, srcSlot) == NEXT_RES) { + bestDelta = null; + return; + } + } + + // We couldn't find a suitable delta for this object, but it may + // still be able to act as a base for another one. + // + if (bestDelta == null) { + keepInWindow(); + return; + } + + // Select this best matching delta as the base for the object. + // + ObjectToPack srcObj = window[bestSlot].object; + ObjectToPack resObj = res.object; + if (srcObj.isDoNotDelta()) { + // The source (the delta base) is an edge object outside of the + // pack. Its part of the common base set that the peer already + // has on hand, so we don't want to send it. We have to store + // an ObjectId and *NOT* an ObjectToPack for the base to ensure + // the base isn't included in the outgoing pack file. + // + resObj.setDeltaBase(srcObj.copy()); + } else { + // The base is part of the pack we are sending, so it should be + // a direct pointer to the base. + // + resObj.setDeltaBase(srcObj); + } + resObj.setDeltaDepth(srcObj.getDeltaDepth() + 1); + resObj.clearReuseAsIs(); + cacheDelta(srcObj, resObj); + + // Discard the cached best result, otherwise it leaks. + // + bestDelta = null; + + // If this should be the end of a chain, don't keep + // it in the window. Just move on to the next object. + // + if (resObj.getDeltaDepth() == maxDepth) + return; + + shuffleBaseUpInPriority(); + keepInWindow(); + } + + private int delta(final DeltaWindowEntry src, final int srcSlot) + throws IOException { + // Objects must use only the same type as their delta base. + // If we are looking at something where that isn't true we + // have exhausted everything of the correct type and should + // move on to the next thing to examine. + // + if (src.type() != res.type()) { + keepInWindow(); + return NEXT_RES; + } + + // Only consider a source with a short enough delta chain. + if (src.depth() > resMaxDepth) + return NEXT_SRC; + + // Estimate a reasonable upper limit on delta size. + int msz = deltaSizeLimit(res, resMaxDepth, src); + if (msz <= 8) + return NEXT_SRC; + + // If we have to insert a lot to make this work, find another. + if (res.size() - src.size() > msz) + return NEXT_SRC; + + // If the sizes are radically different, this is a bad pairing. + if (res.size() < src.size() / 16) + return NEXT_SRC; + + DeltaIndex srcIndex; + try { + srcIndex = index(src); + } catch (LargeObjectException tooBig) { + // If the source is too big to work on, skip it. + dropFromWindow(srcSlot); + return NEXT_SRC; + } catch (IOException notAvailable) { + if (src.object.isDoNotDelta()) { + // This is an edge that is suddenly not available. + dropFromWindow(srcSlot); + return NEXT_SRC; + } else { + throw notAvailable; + } + } + + byte[] resBuf; + try { + resBuf = buffer(res); + } catch (LargeObjectException tooBig) { + // If its too big, move on to another item. + return NEXT_RES; + } + + // If we already have a delta for the current object, abort + // encoding early if this new pairing produces a larger delta. + if (bestDelta != null && bestDelta.length() < msz) + msz = (int) bestDelta.length(); + + TemporaryBuffer.Heap delta = new TemporaryBuffer.Heap(msz); + try { + if (!srcIndex.encode(delta, resBuf, msz)) + return NEXT_SRC; + } catch (IOException deltaTooBig) { + // This only happens when the heap overflows our limit. + return NEXT_SRC; + } + + if (isBetterDelta(src, delta)) { + bestDelta = delta; + bestSlot = srcSlot; + } + + return NEXT_SRC; + } + + private void cacheDelta(ObjectToPack srcObj, ObjectToPack resObj) { + if (Integer.MAX_VALUE < bestDelta.length()) + return; + + int rawsz = (int) bestDelta.length(); + if (deltaCache.canCache(rawsz, srcObj, resObj)) { + try { + byte[] zbuf = new byte[deflateBound(rawsz)]; + + ZipStream zs = new ZipStream(deflater(), zbuf); + bestDelta.writeTo(zs, null); + bestDelta = null; + int len = zs.finish(); + + resObj.setCachedDelta(deltaCache.cache(zbuf, len, rawsz)); + resObj.setCachedSize(rawsz); + } catch (IOException err) { + deltaCache.credit(rawsz); + } catch (OutOfMemoryError err) { + deltaCache.credit(rawsz); + } + } + } + + private static int deflateBound(int insz) { + return insz + ((insz + 7) >> 3) + ((insz + 63) >> 6) + 11; + } + + private void shuffleBaseUpInPriority() { + // Shuffle the entire window so that the best match we just used + // is at our current index, and our current object is at the index + // before it. Slide any entries in between to make space. + // + window[resSlot] = window[bestSlot]; + + DeltaWindowEntry next = res; + int slot = prior(resSlot); + for (; slot != bestSlot; slot = prior(slot)) { + DeltaWindowEntry e = window[slot]; + window[slot] = next; + next = e; + } + window[slot] = next; + } + + private void keepInWindow() { + resSlot = next(resSlot); + } + + private int next(int slot) { + if (++slot == window.length) + return 0; + return slot; + } + + private int prior(int slot) { + if (slot == 0) + return window.length - 1; + return slot - 1; + } + + private void dropFromWindow(@SuppressWarnings("unused") int srcSlot) { + // We should drop the current source entry from the window, + // it is somehow invalid for us to work with. + } + + private boolean isBetterDelta(DeltaWindowEntry src, + TemporaryBuffer.Heap resDelta) { + if (bestDelta == null) + return true; + + // If both delta sequences are the same length, use the one + // that has a shorter delta chain since it would be faster + // to access during reads. + // + if (resDelta.length() == bestDelta.length()) + return src.depth() < window[bestSlot].depth(); + + return resDelta.length() < bestDelta.length(); + } + + private static int deltaSizeLimit(DeltaWindowEntry res, int maxDepth, + DeltaWindowEntry src) { + // Ideally the delta is at least 50% of the original size, + // but we also want to account for delta header overhead in + // the pack file (to point to the delta base) so subtract off + // some of those header bytes from the limit. + // + final int limit = res.size() / 2 - 20; + + // Distribute the delta limit over the entire chain length. + // This is weighted such that deeper items in the chain must + // be even smaller than if they were earlier in the chain, as + // they cost significantly more to unpack due to the increased + // number of recursive unpack calls. + // + final int remainingDepth = maxDepth - src.depth(); + return (limit * remainingDepth) / maxDepth; + } + + private DeltaIndex index(DeltaWindowEntry ent) + throws MissingObjectException, IncorrectObjectTypeException, + IOException, LargeObjectException { + DeltaIndex idx = ent.index; + if (idx == null) { + try { + idx = new DeltaIndex(buffer(ent)); + } catch (OutOfMemoryError noMemory) { + LargeObjectException e = new LargeObjectException(ent.object); + e.initCause(noMemory); + throw e; + } + if (0 < maxMemory) + loaded += idx.getIndexSize() - idx.getSourceSize(); + ent.index = idx; + } + return idx; + } + + private byte[] buffer(DeltaWindowEntry ent) throws MissingObjectException, + IncorrectObjectTypeException, IOException, LargeObjectException { + byte[] buf = ent.buffer; + if (buf == null) { + buf = writer.buffer(reader, ent.object); + if (0 < maxMemory) + loaded += buf.length; + ent.buffer = buf; + } + return buf; + } + + private Deflater deflater() { + if (deflater == null) + deflater = new Deflater(writer.getCompressionLevel()); + else + deflater.reset(); + return deflater; + } + + static final class ZipStream extends OutputStream { + private final Deflater deflater; + + private final byte[] zbuf; + + private int outPtr; + + ZipStream(Deflater deflater, byte[] zbuf) { + this.deflater = deflater; + this.zbuf = zbuf; + } + + int finish() throws IOException { + deflater.finish(); + for (;;) { + if (outPtr == zbuf.length) + throw new EOFException(); + + int n = deflater.deflate(zbuf, outPtr, zbuf.length - outPtr); + if (n == 0) { + if (deflater.finished()) + return outPtr; + throw new IOException(); + } + outPtr += n; + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + deflater.setInput(b, off, len); + for (;;) { + if (outPtr == zbuf.length) + throw new EOFException(); + + int n = deflater.deflate(zbuf, outPtr, zbuf.length - outPtr); + if (n == 0) { + if (deflater.needsInput()) + break; + throw new IOException(); + } + outPtr += n; + } + } + + @Override + public void write(int b) throws IOException { + throw new UnsupportedOperationException(); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindowEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindowEntry.java new file mode 100644 index 0000000000..0f1e6329f9 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindowEntry.java @@ -0,0 +1,80 @@ +/* + * 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.pack; + +class DeltaWindowEntry { + ObjectToPack object; + + /** Complete contents of this object. Lazily loaded. */ + byte[] buffer; + + /** Index of this object's content, to encode other deltas. Lazily loaded. */ + DeltaIndex index; + + void set(ObjectToPack object) { + this.object = object; + this.index = null; + this.buffer = null; + } + + /** @return current delta chain depth of this object. */ + int depth() { + return object.getDeltaDepth(); + } + + /** @return type of the object in this window entry. */ + int type() { + return object.getType(); + } + + /** @return estimated unpacked size of the object, in bytes . */ + int size() { + return object.getWeight(); + } + + /** @return true if there is no object stored in this entry. */ + boolean empty() { + return object == null; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectReuseAsIs.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectReuseAsIs.java new file mode 100644 index 0000000000..a815e93542 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectReuseAsIs.java @@ -0,0 +1,136 @@ +/* + * 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.pack; + +import java.io.IOException; + +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.revwalk.RevObject; + +/** + * Extension of {@link ObjectReader} that supports reusing objects in packs. + * <p> + * {@code ObjectReader} implementations may also optionally implement this + * interface to support {@link PackWriter} with a means of copying an object + * that is already in pack encoding format directly into the output stream, + * without incurring decompression and recompression overheads. + */ +public interface ObjectReuseAsIs { + /** + * Allocate a new {@code PackWriter} state structure for an object. + * <p> + * {@link PackWriter} allocates these objects to keep track of the + * per-object state, and how to load the objects efficiently into the + * generated stream. Implementers may subclass this type with additional + * object state, such as to remember what file and offset contains the + * object's pack encoded data. + * + * @param obj + * identity of the object that will be packed. The object's + * parsed status is undefined here. Implementers must not rely on + * the object being parsed. + * @return a new instance for this object. + */ + public ObjectToPack newObjectToPack(RevObject obj); + + /** + * Select the best object representation for a packer. + * <p> + * Implementations should iterate through all available representations of + * an object, and pass them in turn to the PackWriter though + * {@link PackWriter#select(ObjectToPack, StoredObjectRepresentation)} so + * the writer can select the most suitable representation to reuse into the + * output stream. + * + * @param packer + * the packer that will write the object in the near future. + * @param otp + * the object to pack. + * @throws MissingObjectException + * there is no representation available for the object, as it is + * no longer in the repository. Packing will abort. + * @throws IOException + * the repository cannot be accessed. Packing will abort. + */ + public void selectObjectRepresentation(PackWriter packer, ObjectToPack otp) + throws IOException, MissingObjectException; + + /** + * Output a previously selected representation. + * <p> + * {@code PackWriter} invokes this method only if a representation + * previously given to it by {@code selectObjectRepresentation} was chosen + * for reuse into the output stream. The {@code otp} argument is an instance + * created by this reader's own {@code newObjectToPack}, and the + * representation data saved within it also originated from this reader. + * <p> + * Implementors must write the object header before copying the raw data to + * the output stream. The typical implementation is like: + * + * <pre> + * MyToPack mtp = (MyToPack) otp; + * byte[] raw = validate(mtp); // throw SORNAE here, if at all + * out.writeHeader(mtp, mtp.inflatedSize); + * out.write(raw); + * </pre> + * + * @param out + * stream the object should be written to. + * @param otp + * the object's saved representation information. + * @throws StoredObjectRepresentationNotAvailableException + * the previously selected representation is no longer + * available. If thrown before {@code out.writeHeader} the pack + * writer will try to find another representation, and write + * that one instead. If throw after {@code out.writeHeader}, + * packing will abort. + * @throws IOException + * the stream's write method threw an exception. Packing will + * abort. + */ + public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp) + throws IOException, StoredObjectRepresentationNotAvailableException; +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java new file mode 100644 index 0000000000..70188a3803 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2008-2010, Google Inc. + * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> + * 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.pack; + +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.transport.PackedObjectInfo; + +/** + * Per-object state used by {@link PackWriter}. + * <p> + * {@code PackWriter} uses this class to track the things it needs to include in + * the newly generated pack file, and how to efficiently obtain the raw data for + * each object as they are written to the output stream. + */ +public class ObjectToPack extends PackedObjectInfo { + private static final int WANT_WRITE = 1 << 0; + + private static final int REUSE_AS_IS = 1 << 1; + + private static final int DO_NOT_DELTA = 1 << 2; + + private static final int TYPE_SHIFT = 5; + + private static final int DELTA_SHIFT = 8; + + private static final int NON_DELTA_MASK = 0xff; + + /** Other object being packed that this will delta against. */ + private ObjectId deltaBase; + + /** + * Bit field, from bit 0 to bit 31: + * <ul> + * <li>1 bit: wantWrite</li> + * <li>1 bit: canReuseAsIs</li> + * <li>1 bit: doNotDelta</li> + * <li>2 bits: unused</li> + * <li>3 bits: type</li> + * <li>--</li> + * <li>24 bits: deltaDepth</li> + * </ul> + */ + private int flags; + + /** Hash of the object's tree path. */ + private int pathHash; + + /** If present, deflated delta instruction stream for this object. */ + private DeltaCache.Ref cachedDelta; + + /** + * Construct for the specified object id. + * + * @param src + * object id of object for packing + * @param type + * real type code of the object, not its in-pack type. + */ + public ObjectToPack(AnyObjectId src, final int type) { + super(src); + flags = type << TYPE_SHIFT; + } + + /** + * Construct for the specified object. + * + * @param obj + * identity of the object that will be packed. The object's + * parsed status is undefined here. Implementers must not rely on + * the object being parsed. + */ + public ObjectToPack(RevObject obj) { + this(obj, obj.getType()); + } + + /** + * @return delta base object id if object is going to be packed in delta + * representation; null otherwise - if going to be packed as a + * whole object. + */ + ObjectId getDeltaBaseId() { + return deltaBase; + } + + /** + * @return delta base object to pack if object is going to be packed in + * delta representation and delta is specified as object to + * pack; null otherwise - if going to be packed as a whole + * object or delta base is specified only as id. + */ + ObjectToPack getDeltaBase() { + if (deltaBase instanceof ObjectToPack) + return (ObjectToPack) deltaBase; + return null; + } + + /** + * Set delta base for the object. Delta base set by this method is used + * by {@link PackWriter} to write object - determines its representation + * in a created pack. + * + * @param deltaBase + * delta base object or null if object should be packed as a + * whole object. + * + */ + void setDeltaBase(ObjectId deltaBase) { + this.deltaBase = deltaBase; + } + + void setCachedDelta(DeltaCache.Ref data){ + cachedDelta = data; + } + + DeltaCache.Ref popCachedDelta() { + DeltaCache.Ref r = cachedDelta; + if (r != null) + cachedDelta = null; + return r; + } + + void clearDeltaBase() { + this.deltaBase = null; + + if (cachedDelta != null) { + cachedDelta.clear(); + cachedDelta.enqueue(); + cachedDelta = null; + } + } + + /** + * @return true if object is going to be written as delta; false + * otherwise. + */ + boolean isDeltaRepresentation() { + return deltaBase != null; + } + + /** + * Check if object is already written in a pack. This information is + * used to achieve delta-base precedence in a pack file. + * + * @return true if object is already written; false otherwise. + */ + boolean isWritten() { + return getOffset() != 0; + } + + int getType() { + return (flags >> TYPE_SHIFT) & 0x7; + } + + int getDeltaDepth() { + return flags >>> DELTA_SHIFT; + } + + void setDeltaDepth(int d) { + flags = (d << DELTA_SHIFT) | (flags & NON_DELTA_MASK); + } + + boolean wantWrite() { + return (flags & WANT_WRITE) != 0; + } + + void markWantWrite() { + flags |= WANT_WRITE; + } + + boolean isReuseAsIs() { + return (flags & REUSE_AS_IS) != 0; + } + + void setReuseAsIs() { + flags |= REUSE_AS_IS; + } + + /** + * Forget the reuse information previously stored. + * <p> + * Implementations may subclass this method, but they must also invoke the + * super version with {@code super.clearReuseAsIs()} to ensure the flag is + * properly cleared for the writer. + */ + protected void clearReuseAsIs() { + flags &= ~REUSE_AS_IS; + } + + boolean isDoNotDelta() { + return (flags & DO_NOT_DELTA) != 0; + } + + void setDoNotDelta(boolean noDelta) { + if (noDelta) + flags |= DO_NOT_DELTA; + else + flags &= ~DO_NOT_DELTA; + } + + int getFormat() { + if (isReuseAsIs()) { + if (isDeltaRepresentation()) + return StoredObjectRepresentation.PACK_DELTA; + return StoredObjectRepresentation.PACK_WHOLE; + } + return StoredObjectRepresentation.FORMAT_OTHER; + } + + // Overload weight into CRC since we don't need them at the same time. + int getWeight() { + return getCRC(); + } + + void setWeight(int weight) { + setCRC(weight); + } + + int getPathHash() { + return pathHash; + } + + void setPathHash(int hc) { + pathHash = hc; + } + + int getCachedSize() { + return pathHash; + } + + void setCachedSize(int sz) { + pathHash = sz; + } + + /** + * Remember a specific representation for reuse at a later time. + * <p> + * Implementers should remember the representation chosen, so it can be + * reused at a later time. {@link PackWriter} may invoke this method + * multiple times for the same object, each time saving the current best + * representation found. + * + * @param ref + * the object representation. + */ + public void select(StoredObjectRepresentation ref) { + // Empty by default. + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("ObjectToPack["); + buf.append(Constants.typeString(getType())); + buf.append(" "); + buf.append(name()); + if (wantWrite()) + buf.append(" wantWrite"); + if (isReuseAsIs()) + buf.append(" reuseAsIs"); + if (isDoNotDelta()) + buf.append(" doNotDelta"); + if (getDeltaDepth() > 0) + buf.append(" depth=" + getDeltaDepth()); + if (isDeltaRepresentation()) { + if (getDeltaBase() != null) + buf.append(" base=inpack:" + getDeltaBase().name()); + else + buf.append(" base=edge:" + getDeltaBaseId().name()); + } + if (isWritten()) + buf.append(" offset=" + getOffset()); + buf.append("]"); + return buf.toString(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java new file mode 100644 index 0000000000..814ab8f291 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java @@ -0,0 +1,93 @@ +/* + * 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.pack; + +import static java.util.zip.Deflater.DEFAULT_COMPRESSION; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Config.SectionParser; + +class PackConfig { + /** Key for {@link Config#get(SectionParser)}. */ + static final Config.SectionParser<PackConfig> KEY = new SectionParser<PackConfig>() { + public PackConfig parse(final Config cfg) { + return new PackConfig(cfg); + } + }; + + final int deltaWindow; + + final long deltaWindowMemory; + + final int deltaDepth; + + final long deltaCacheSize; + + final int deltaCacheLimit; + + final int compression; + + final int indexVersion; + + final long bigFileThreshold; + + final int threads; + + private PackConfig(Config rc) { + deltaWindow = rc.getInt("pack", "window", PackWriter.DEFAULT_DELTA_SEARCH_WINDOW_SIZE); + deltaWindowMemory = rc.getLong("pack", null, "windowmemory", 0); + deltaCacheSize = rc.getLong("pack", null, "deltacachesize", PackWriter.DEFAULT_DELTA_CACHE_SIZE); + deltaCacheLimit = rc.getInt("pack", "deltacachelimit", PackWriter.DEFAULT_DELTA_CACHE_LIMIT); + deltaDepth = rc.getInt("pack", "depth", PackWriter.DEFAULT_MAX_DELTA_DEPTH); + compression = compression(rc); + indexVersion = rc.getInt("pack", "indexversion", 2); + bigFileThreshold = rc.getLong("core", null, "bigfilethreshold", PackWriter.DEFAULT_BIG_FILE_THRESHOLD); + threads = rc.getInt("pack", "threads", 0); + } + + private static int compression(Config rc) { + if (rc.getString("pack", null, "compression") != null) + return rc.getInt("pack", "compression", DEFAULT_COMPRESSION); + return rc.getInt("core", "compression", DEFAULT_COMPRESSION); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackOutputStream.java new file mode 100644 index 0000000000..92e1a197cd --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackOutputStream.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> + * 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.pack; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.MessageDigest; +import java.util.zip.CRC32; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.util.NB; + +/** Custom output stream to support {@link PackWriter}. */ +public final class PackOutputStream extends OutputStream { + private final ProgressMonitor writeMonitor; + + private final OutputStream out; + + private final boolean ofsDelta; + + private final CRC32 crc = new CRC32(); + + private final MessageDigest md = Constants.newMessageDigest(); + + private long count; + + private byte[] headerBuffer = new byte[32]; + + private byte[] copyBuffer; + + /** + * Initialize a pack output stream. + * <p> + * This constructor is exposed to support debugging the JGit library only. + * Application or storage level code should not create a PackOutputStream, + * instead use {@link PackWriter}, and let the writer create the stream. + * + * @param writeMonitor + * monitor to update on object output progress. + * @param out + * target stream to receive all object contents. + * @param pw + * packer that is going to perform the output. + */ + public PackOutputStream(final ProgressMonitor writeMonitor, + final OutputStream out, final PackWriter pw) { + this.writeMonitor = writeMonitor; + this.out = out; + this.ofsDelta = pw.isDeltaBaseAsOffset(); + } + + @Override + public void write(final int b) throws IOException { + count++; + out.write(b); + crc.update(b); + md.update((byte) b); + } + + @Override + public void write(final byte[] b, final int off, final int len) + throws IOException { + count += len; + out.write(b, off, len); + crc.update(b, off, len); + md.update(b, off, len); + } + + @Override + public void flush() throws IOException { + out.flush(); + } + + void writeFileHeader(int version, int objectCount) throws IOException { + System.arraycopy(Constants.PACK_SIGNATURE, 0, headerBuffer, 0, 4); + NB.encodeInt32(headerBuffer, 4, version); + NB.encodeInt32(headerBuffer, 8, objectCount); + write(headerBuffer, 0, 12); + } + + /** + * Commits the object header onto the stream. + * <p> + * Once the header has been written, the object representation must be fully + * output, or packing must abort abnormally. + * + * @param otp + * the object to pack. Header information is obtained. + * @param rawLength + * number of bytes of the inflated content. For an object that is + * in whole object format, this is the same as the object size. + * For an object that is in a delta format, this is the size of + * the inflated delta instruction stream. + * @throws IOException + * the underlying stream refused to accept the header. + */ + public void writeHeader(ObjectToPack otp, long rawLength) + throws IOException { + if (otp.isDeltaRepresentation()) { + if (ofsDelta) { + ObjectToPack baseInPack = otp.getDeltaBase(); + if (baseInPack != null && baseInPack.isWritten()) { + final long start = count; + int n = encodeTypeSize(Constants.OBJ_OFS_DELTA, rawLength); + write(headerBuffer, 0, n); + + long offsetDiff = start - baseInPack.getOffset(); + n = headerBuffer.length - 1; + headerBuffer[n] = (byte) (offsetDiff & 0x7F); + while ((offsetDiff >>= 7) > 0) + headerBuffer[--n] = (byte) (0x80 | (--offsetDiff & 0x7F)); + write(headerBuffer, n, headerBuffer.length - n); + return; + } + } + + int n = encodeTypeSize(Constants.OBJ_REF_DELTA, rawLength); + otp.getDeltaBaseId().copyRawTo(headerBuffer, n); + write(headerBuffer, 0, n + Constants.OBJECT_ID_LENGTH); + } else { + int n = encodeTypeSize(otp.getType(), rawLength); + write(headerBuffer, 0, n); + } + } + + private int encodeTypeSize(int type, long rawLength) { + long nextLength = rawLength >>> 4; + headerBuffer[0] = (byte) ((nextLength > 0 ? 0x80 : 0x00) + | (type << 4) | (rawLength & 0x0F)); + rawLength = nextLength; + int n = 1; + while (rawLength > 0) { + nextLength >>>= 7; + headerBuffer[n++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (rawLength & 0x7F)); + rawLength = nextLength; + } + return n; + } + + /** @return a temporary buffer writers can use to copy data with. */ + public byte[] getCopyBuffer() { + if (copyBuffer == null) + copyBuffer = new byte[16 * 1024]; + return copyBuffer; + } + + void endObject() { + writeMonitor.update(1); + } + + /** @return total number of bytes written since stream start. */ + long length() { + return count; + } + + /** @return obtain the current CRC32 register. */ + int getCRC32() { + return (int) crc.getValue(); + } + + /** Reinitialize the CRC32 register for a new region. */ + void resetCRC32() { + crc.reset(); + } + + /** @return obtain the current SHA-1 digest. */ + byte[] getDigest() { + return md.digest(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java new file mode 100644 index 0000000000..38f00b5739 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -0,0 +1,1442 @@ +/* + * Copyright (C) 2008-2010, Google Inc. + * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> + * 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.pack; + +import static org.eclipse.jgit.storage.pack.StoredObjectRepresentation.PACK_DELTA; +import static org.eclipse.jgit.storage.pack.StoredObjectRepresentation.PACK_WHOLE; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.MessageDigest; +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.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.LargeObjectException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Config; +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.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.ThreadSafeProgressMonitor; +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; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.TemporaryBuffer; + +/** + * <p> + * PackWriter class is responsible for generating pack files from specified set + * of objects from repository. This implementation produce pack files in format + * version 2. + * </p> + * <p> + * Source of objects may be specified in two ways: + * <ul> + * <li>(usually) by providing sets of interesting and uninteresting objects in + * repository - all interesting objects and their ancestors except uninteresting + * objects and their ancestors will be included in pack, or</li> + * <li>by providing iterator of {@link RevObject} specifying exact list and + * order of objects in pack</li> + * </ul> + * Typical usage consists of creating instance intended for some pack, + * configuring options, preparing the list of objects by calling + * {@link #preparePack(Iterator)} or + * {@link #preparePack(ProgressMonitor, Collection, Collection)}, and finally + * producing the stream with {@link #writePack(ProgressMonitor, ProgressMonitor, OutputStream)}. + * </p> + * <p> + * Class provide set of configurable options and {@link ProgressMonitor} + * support, as operations may take a long time for big repositories. Deltas + * searching algorithm is <b>NOT IMPLEMENTED</b> yet - this implementation + * relies only on deltas and objects reuse. + * </p> + * <p> + * This class is not thread safe, it is intended to be used in one thread, with + * one instance per created pack. Subsequent calls to writePack result in + * undefined behavior. + * </p> + */ +public class PackWriter { + /** + * Title of {@link ProgressMonitor} task used during counting objects to + * pack. + * + * @see #preparePack(ProgressMonitor, Collection, Collection) + */ + public static final String COUNTING_OBJECTS_PROGRESS = JGitText.get().countingObjects; + + /** + * Title of {@link ProgressMonitor} task used during compression. + * + * @see #writePack(ProgressMonitor, ProgressMonitor, OutputStream) + */ + public static final String COMPRESSING_OBJECTS_PROGRESS = JGitText.get().compressingObjects; + + /** + * Title of {@link ProgressMonitor} task used during writing out pack + * (objects) + * + * @see #writePack(ProgressMonitor, ProgressMonitor, OutputStream) + */ + public static final String WRITING_OBJECTS_PROGRESS = JGitText.get().writingObjects; + + /** + * Default value of deltas reuse option. + * + * @see #setReuseDeltas(boolean) + */ + public static final boolean DEFAULT_REUSE_DELTAS = true; + + /** + * Default value of objects reuse option. + * + * @see #setReuseObjects(boolean) + */ + public static final boolean DEFAULT_REUSE_OBJECTS = true; + + /** + * Default value of delta base as offset option. + * + * @see #setDeltaBaseAsOffset(boolean) + */ + public static final boolean DEFAULT_DELTA_BASE_AS_OFFSET = false; + + /** + * Default value of maximum delta chain depth. + * + * @see #setMaxDeltaDepth(int) + */ + public static final int DEFAULT_MAX_DELTA_DEPTH = 50; + + /** + * Default window size during packing. + * + * @see #setDeltaSearchWindowSize(int) + */ + public static final int DEFAULT_DELTA_SEARCH_WINDOW_SIZE = 10; + + static final long DEFAULT_BIG_FILE_THRESHOLD = 50 * 1024 * 1024; + + static final long DEFAULT_DELTA_CACHE_SIZE = 50 * 1024 * 1024; + + static final int DEFAULT_DELTA_CACHE_LIMIT = 100; + + private static final int PACK_VERSION_GENERATED = 2; + + @SuppressWarnings("unchecked") + private final List<ObjectToPack> objectsLists[] = new List[Constants.OBJ_TAG + 1]; + { + objectsLists[0] = Collections.<ObjectToPack> emptyList(); + objectsLists[Constants.OBJ_COMMIT] = new ArrayList<ObjectToPack>(); + objectsLists[Constants.OBJ_TREE] = new ArrayList<ObjectToPack>(); + objectsLists[Constants.OBJ_BLOB] = new ArrayList<ObjectToPack>(); + objectsLists[Constants.OBJ_TAG] = new ArrayList<ObjectToPack>(); + } + + private final ObjectIdSubclassMap<ObjectToPack> objectsMap = new ObjectIdSubclassMap<ObjectToPack>(); + + // edge objects for thin packs + private final ObjectIdSubclassMap<ObjectToPack> edgeObjects = new ObjectIdSubclassMap<ObjectToPack>(); + + private int compressionLevel; + + private Deflater myDeflater; + + private final ObjectReader reader; + + /** {@link #reader} recast to the reuse interface, if it supports it. */ + private final ObjectReuseAsIs reuseSupport; + + private List<ObjectToPack> sortedByName; + + private byte packcsum[]; + + private boolean reuseDeltas = DEFAULT_REUSE_DELTAS; + + private boolean reuseObjects = DEFAULT_REUSE_OBJECTS; + + private boolean deltaBaseAsOffset = DEFAULT_DELTA_BASE_AS_OFFSET; + + private boolean deltaCompress = true; + + private int maxDeltaDepth = DEFAULT_MAX_DELTA_DEPTH; + + private int deltaSearchWindowSize = DEFAULT_DELTA_SEARCH_WINDOW_SIZE; + + private long deltaSearchMemoryLimit; + + private long deltaCacheSize = DEFAULT_DELTA_CACHE_SIZE; + + private int deltaCacheLimit = DEFAULT_DELTA_CACHE_LIMIT; + + private int indexVersion; + + private long bigFileThreshold = DEFAULT_BIG_FILE_THRESHOLD; + + private int threads = 1; + + private boolean thin; + + private boolean ignoreMissingUninteresting = true; + + /** + * Create writer for specified repository. + * <p> + * Objects for packing are specified in {@link #preparePack(Iterator)} or + * {@link #preparePack(ProgressMonitor, Collection, Collection)}. + * + * @param repo + * repository where objects are stored. + */ + public PackWriter(final Repository repo) { + this(repo, repo.newObjectReader()); + } + + /** + * Create a writer to load objects from the specified reader. + * <p> + * Objects for packing are specified in {@link #preparePack(Iterator)} or + * {@link #preparePack(ProgressMonitor, Collection, Collection)}. + * + * @param reader + * reader to read from the repository with. + */ + public PackWriter(final ObjectReader reader) { + this(null, reader); + } + + /** + * Create writer for specified repository. + * <p> + * Objects for packing are specified in {@link #preparePack(Iterator)} or + * {@link #preparePack(ProgressMonitor, Collection, Collection)}. + * + * @param repo + * repository where objects are stored. + * @param reader + * reader to read from the repository with. + */ + public PackWriter(final Repository repo, final ObjectReader reader) { + this.reader = reader; + if (reader instanceof ObjectReuseAsIs) + reuseSupport = ((ObjectReuseAsIs) reader); + else + reuseSupport = null; + + final PackConfig pc = configOf(repo).get(PackConfig.KEY); + deltaSearchWindowSize = pc.deltaWindow; + deltaSearchMemoryLimit = pc.deltaWindowMemory; + deltaCacheSize = pc.deltaCacheSize; + deltaCacheLimit = pc.deltaCacheLimit; + maxDeltaDepth = pc.deltaDepth; + compressionLevel = pc.compression; + indexVersion = pc.indexVersion; + bigFileThreshold = pc.bigFileThreshold; + threads = pc.threads; + } + + private static Config configOf(final Repository repo) { + if (repo == null) + return new Config(); + return repo.getConfig(); + } + + /** + * Check whether object is configured to reuse deltas existing in + * repository. + * <p> + * Default setting: {@value #DEFAULT_REUSE_DELTAS} + * </p> + * + * @return true if object is configured to reuse deltas; false otherwise. + */ + public boolean isReuseDeltas() { + return reuseDeltas; + } + + /** + * Set reuse deltas configuration option for this writer. When enabled, + * writer will search for delta representation of object in repository and + * use it if possible. Normally, only deltas with base to another object + * existing in set of objects to pack will be used. Exception is however + * thin-pack (see + * {@link #preparePack(ProgressMonitor, Collection, Collection)} and + * {@link #preparePack(Iterator)}) where base object must exist on other + * side machine. + * <p> + * When raw delta data is directly copied from a pack file, checksum is + * computed to verify data. + * </p> + * <p> + * Default setting: {@value #DEFAULT_REUSE_DELTAS} + * </p> + * + * @param reuseDeltas + * boolean indicating whether or not try to reuse deltas. + */ + public void setReuseDeltas(boolean reuseDeltas) { + this.reuseDeltas = reuseDeltas; + } + + /** + * Checks whether object is configured to reuse existing objects + * representation in repository. + * <p> + * Default setting: {@value #DEFAULT_REUSE_OBJECTS} + * </p> + * + * @return true if writer is configured to reuse objects representation from + * pack; false otherwise. + */ + public boolean isReuseObjects() { + return reuseObjects; + } + + /** + * Set reuse objects configuration option for this writer. If enabled, + * writer searches for representation in a pack file. If possible, + * compressed data is directly copied from such a pack file. Data checksum + * is verified. + * <p> + * Default setting: {@value #DEFAULT_REUSE_OBJECTS} + * </p> + * + * @param reuseObjects + * boolean indicating whether or not writer should reuse existing + * objects representation. + */ + public void setReuseObjects(boolean reuseObjects) { + this.reuseObjects = reuseObjects; + } + + /** + * Check whether writer can store delta base as an offset (new style + * reducing pack size) or should store it as an object id (legacy style, + * compatible with old readers). + * <p> + * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET} + * </p> + * + * @return true if delta base is stored as an offset; false if it is stored + * as an object id. + */ + public boolean isDeltaBaseAsOffset() { + return deltaBaseAsOffset; + } + + /** + * Set writer delta base format. Delta base can be written as an offset in a + * pack file (new approach reducing file size) or as an object id (legacy + * approach, compatible with old readers). + * <p> + * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET} + * </p> + * + * @param deltaBaseAsOffset + * boolean indicating whether delta base can be stored as an + * offset. + */ + public void setDeltaBaseAsOffset(boolean deltaBaseAsOffset) { + this.deltaBaseAsOffset = deltaBaseAsOffset; + } + + /** + * Check whether the writer will create new deltas on the fly. + * <p> + * Default setting: true + * </p> + * + * @return true if the writer will create a new delta when either + * {@link #isReuseDeltas()} is false, or no suitable delta is + * available for reuse. + */ + public boolean isDeltaCompress() { + return deltaCompress; + } + + /** + * Set whether or not the writer will create new deltas on the fly. + * + * @param deltaCompress + * true to create deltas when {@link #isReuseDeltas()} is false, + * or when a suitable delta isn't available for reuse. Set to + * false to write whole objects instead. + */ + public void setDeltaCompress(boolean deltaCompress) { + this.deltaCompress = deltaCompress; + } + + /** + * Get maximum depth of delta chain set up for this writer. Generated chains + * are not longer than this value. + * <p> + * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH} + * </p> + * + * @return maximum delta chain depth. + */ + public int getMaxDeltaDepth() { + return maxDeltaDepth; + } + + /** + * Set up maximum depth of delta chain for this writer. Generated chains are + * not longer than this value. Too low value causes low compression level, + * while too big makes unpacking (reading) longer. + * <p> + * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH} + * </p> + * + * @param maxDeltaDepth + * maximum delta chain depth. + */ + public void setMaxDeltaDepth(int maxDeltaDepth) { + this.maxDeltaDepth = maxDeltaDepth; + } + + /** + * Get the number of objects to try when looking for a delta base. + * <p> + * This limit is per thread, if 4 threads are used the actual memory + * used will be 4 times this value. + * + * @return the object count to be searched. + */ + public int getDeltaSearchWindowSize() { + return deltaSearchWindowSize; + } + + /** + * Set the number of objects considered when searching for a delta base. + * <p> + * Default setting: {@value #DEFAULT_DELTA_SEARCH_WINDOW_SIZE} + * </p> + * + * @param objectCount + * number of objects to search at once. Must be at least 2. + */ + public void setDeltaSearchWindowSize(int objectCount) { + if (objectCount <= 2) + setDeltaCompress(false); + else + deltaSearchWindowSize = objectCount; + } + + /** + * Get maximum number of bytes to put into the delta search window. + * <p> + * Default setting is 0, for an unlimited amount of memory usage. Actual + * memory used is the lower limit of either this setting, or the sum of + * space used by at most {@link #getDeltaSearchWindowSize()} objects. + * <p> + * This limit is per thread, if 4 threads are used the actual memory + * limit will be 4 times this value. + * + * @return the memory limit. + */ + public long getDeltaSearchMemoryLimit() { + return deltaSearchMemoryLimit; + } + + /** + * Set the maximum number of bytes to put into the delta search window. + * <p> + * Default setting is 0, for an unlimited amount of memory usage. If the + * memory limit is reached before {@link #getDeltaSearchWindowSize()} the + * window size is temporarily lowered. + * + * @param memoryLimit + * Maximum number of bytes to load at once, 0 for unlimited. + */ + public void setDeltaSearchMemoryLimit(long memoryLimit) { + deltaSearchMemoryLimit = memoryLimit; + } + + /** + * Get the size of the in-memory delta cache. + * <p> + * This limit is for the entire writer, even if multiple threads are used. + * + * @return maximum number of bytes worth of delta data to cache in memory. + * If 0 the cache is infinite in size (up to the JVM heap limit + * anyway). A very tiny size such as 1 indicates the cache is + * effectively disabled. + */ + public long getDeltaCacheSize() { + return deltaCacheSize; + } + + /** + * Set the maximum number of bytes of delta data to cache. + * <p> + * During delta search, up to this many bytes worth of small or hard to + * compute deltas will be stored in memory. This cache speeds up writing by + * allowing the cached entry to simply be dumped to the output stream. + * + * @param size + * number of bytes to cache. Set to 0 to enable an infinite + * cache, set to 1 (an impossible size for any delta) to disable + * the cache. + */ + public void setDeltaCacheSize(long size) { + deltaCacheSize = size; + } + + /** + * Maximum size in bytes of a delta to cache. + * + * @return maximum size (in bytes) of a delta that should be cached. + */ + public int getDeltaCacheLimit() { + return deltaCacheLimit; + } + + /** + * Set the maximum size of a delta that should be cached. + * <p> + * During delta search, any delta smaller than this size will be cached, up + * to the {@link #getDeltaCacheSize()} maximum limit. This speeds up writing + * by allowing these cached deltas to be output as-is. + * + * @param size + * maximum size (in bytes) of a delta to be cached. + */ + public void setDeltaCacheLimit(int size) { + deltaCacheLimit = size; + } + + /** + * Get the maximum file size that will be delta compressed. + * <p> + * Files bigger than this setting will not be delta compressed, as they are + * more than likely already highly compressed binary data files that do not + * delta compress well, such as MPEG videos. + * + * @return the configured big file threshold. + */ + public long getBigFileThreshold() { + return bigFileThreshold; + } + + /** + * Set the maximum file size that should be considered for deltas. + * + * @param bigFileThreshold + * the limit, in bytes. + */ + public void setBigFileThreshold(long bigFileThreshold) { + this.bigFileThreshold = bigFileThreshold; + } + + /** + * Get the compression level applied to objects in the pack. + * + * @return current compression level, see {@link java.util.zip.Deflater}. + */ + public int getCompressionLevel() { + return compressionLevel; + } + + /** + * Set the compression level applied to objects in the pack. + * + * @param level + * compression level, must be a valid level recognized by the + * {@link java.util.zip.Deflater} class. Typically this setting + * is {@link java.util.zip.Deflater#BEST_SPEED}. + */ + public void setCompressionLevel(int level) { + compressionLevel = level; + } + + /** @return number of threads used for delta compression. */ + public int getThreads() { + return threads; + } + + /** + * Set the number of threads to use for delta compression. + * <p> + * During delta compression, if there are enough objects to be considered + * the writer will start up concurrent threads and allow them to compress + * different sections of the repository concurrently. + * + * @param threads + * number of threads to use. If <= 0 the number of available + * processors for this JVM is used. + */ + public void setThread(int threads) { + this.threads = threads; + } + + /** @return true if this writer is producing a thin pack. */ + public boolean isThin() { + return thin; + } + + /** + * @param packthin + * a boolean indicating whether writer may pack objects with + * delta base object not within set of objects to pack, but + * belonging to party repository (uninteresting/boundary) as + * determined by set; this kind of pack is used only for + * transport; true - to produce thin pack, false - otherwise. + */ + public void setThin(final boolean packthin) { + thin = packthin; + } + + /** + * @return true to ignore objects that are uninteresting and also not found + * on local disk; false to throw a {@link MissingObjectException} + * out of {@link #preparePack(ProgressMonitor, Collection, Collection)} if an + * uninteresting object is not in the source repository. By default, + * true, permitting gracefully ignoring of uninteresting objects. + */ + public boolean isIgnoreMissingUninteresting() { + return ignoreMissingUninteresting; + } + + /** + * @param ignore + * true if writer should ignore non existing uninteresting + * objects during construction set of objects to pack; false + * otherwise - non existing uninteresting objects may cause + * {@link MissingObjectException} + */ + public void setIgnoreMissingUninteresting(final boolean ignore) { + ignoreMissingUninteresting = ignore; + } + + /** + * Set the pack index file format version this instance will create. + * + * @param version + * the version to write. The special version 0 designates the + * oldest (most compatible) format available for the objects. + * @see PackIndexWriter + */ + public void setIndexVersion(final int version) { + indexVersion = version; + } + + /** + * Returns objects number in a pack file that was created by this writer. + * + * @return number of objects in pack. + */ + public int getObjectsNumber() { + return objectsMap.size(); + } + + /** + * Prepare the list of objects to be written to the pack stream. + * <p> + * Iterator <b>exactly</b> determines which objects are included in a pack + * and order they appear in pack (except that objects order by type is not + * needed at input). This order should conform general rules of ordering + * objects in git - by recency and path (type and delta-base first is + * internally secured) and responsibility for guaranteeing this order is on + * a caller side. Iterator must return each id of object to write exactly + * once. + * </p> + * <p> + * When iterator returns object that has {@link RevFlag#UNINTERESTING} flag, + * this object won't be included in an output pack. Instead, it is recorded + * as edge-object (known to remote repository) for thin-pack. In such a case + * writer may pack objects with delta base object not within set of objects + * to pack, but belonging to party repository - those marked with + * {@link RevFlag#UNINTERESTING} flag. This type of pack is used only for + * transport. + * </p> + * + * @param objectsSource + * iterator of object to store in a pack; order of objects within + * each type is important, ordering by type is not needed; + * allowed types for objects are {@link Constants#OBJ_COMMIT}, + * {@link Constants#OBJ_TREE}, {@link Constants#OBJ_BLOB} and + * {@link Constants#OBJ_TAG}; objects returned by iterator may + * be later reused by caller as object id and type are internally + * copied in each iteration; if object returned by iterator has + * {@link RevFlag#UNINTERESTING} flag set, it won't be included + * in a pack, but is considered as edge-object for thin-pack. + * @throws IOException + * when some I/O problem occur during reading objects. + */ + public void preparePack(final Iterator<RevObject> objectsSource) + throws IOException { + while (objectsSource.hasNext()) { + addObject(objectsSource.next()); + } + } + + /** + * Prepare the list of objects to be written to the pack stream. + * <p> + * Basing on these 2 sets, another set of objects to put in a pack file is + * created: this set consists of all objects reachable (ancestors) from + * interesting objects, except uninteresting objects and their ancestors. + * This method uses class {@link ObjectWalk} extensively to find out that + * appropriate set of output objects and their optimal order in output pack. + * Order is consistent with general git in-pack rules: sort by object type, + * recency, path and delta-base first. + * </p> + * + * @param countingMonitor + * progress during object enumeration. + * @param interestingObjects + * collection of objects to be marked as interesting (start + * points of graph traversal). + * @param uninterestingObjects + * collection of objects to be marked as uninteresting (end + * points of graph traversal). + * @throws IOException + * when some I/O problem occur during reading objects. + */ + public void preparePack(ProgressMonitor countingMonitor, + final Collection<? extends ObjectId> interestingObjects, + final Collection<? extends ObjectId> uninterestingObjects) + throws IOException { + if (countingMonitor == null) + countingMonitor = NullProgressMonitor.INSTANCE; + ObjectWalk walker = setUpWalker(interestingObjects, + uninterestingObjects); + findObjectsToPack(countingMonitor, walker); + } + + /** + * Determine if the pack file will contain the requested object. + * + * @param id + * the object to test the existence of. + * @return true if the object will appear in the output pack file. + */ + public boolean willInclude(final AnyObjectId id) { + return objectsMap.get(id) != null; + } + + /** + * Computes SHA-1 of lexicographically sorted objects ids written in this + * pack, as used to name a pack file in repository. + * + * @return ObjectId representing SHA-1 name of a pack that was created. + */ + public ObjectId computeName() { + final byte[] buf = new byte[Constants.OBJECT_ID_LENGTH]; + final MessageDigest md = Constants.newMessageDigest(); + for (ObjectToPack otp : sortByName()) { + otp.copyRawTo(buf, 0); + md.update(buf, 0, Constants.OBJECT_ID_LENGTH); + } + return ObjectId.fromRaw(md.digest()); + } + + /** + * Create an index file to match the pack file just written. + * <p> + * This method can only be invoked after {@link #preparePack(Iterator)} or + * {@link #preparePack(ProgressMonitor, Collection, Collection)} has been + * invoked and completed successfully. Writing a corresponding index is an + * optional feature that not all pack users may require. + * + * @param indexStream + * output for the index data. Caller is responsible for closing + * this stream. + * @throws IOException + * the index data could not be written to the supplied stream. + */ + public void writeIndex(final OutputStream indexStream) throws IOException { + final List<ObjectToPack> list = sortByName(); + final PackIndexWriter iw; + if (indexVersion <= 0) + iw = PackIndexWriter.createOldestPossible(indexStream, list); + else + iw = PackIndexWriter.createVersion(indexStream, indexVersion); + iw.write(list, packcsum); + } + + private List<ObjectToPack> sortByName() { + if (sortedByName == null) { + sortedByName = new ArrayList<ObjectToPack>(objectsMap.size()); + for (List<ObjectToPack> list : objectsLists) { + for (ObjectToPack otp : list) + sortedByName.add(otp); + } + Collections.sort(sortedByName); + } + return sortedByName; + } + + /** + * Write the prepared pack to the supplied stream. + * <p> + * At first, this method collects and sorts objects to pack, then deltas + * search is performed if set up accordingly, finally pack stream is + * written. {@link ProgressMonitor} tasks {@value #COMPRESSING_OBJECTS_PROGRESS} + * (only if reuseDeltas or reuseObjects is enabled) and + * {@value #WRITING_OBJECTS_PROGRESS} are updated during packing. + * </p> + * <p> + * All reused objects data checksum (Adler32/CRC32) is computed and + * validated against existing checksum. + * </p> + * + * @param compressMonitor + * progress monitor to report object compression work. + * @param writeMonitor + * progress monitor to report the number of objects written. + * @param packStream + * output stream of pack data. The stream should be buffered by + * the caller. The caller is responsible for closing the stream. + * @throws IOException + * an error occurred reading a local object's data to include in + * the pack, or writing compressed object data to the output + * stream. + */ + public void writePack(ProgressMonitor compressMonitor, + ProgressMonitor writeMonitor, OutputStream packStream) + throws IOException { + if (compressMonitor == null) + compressMonitor = NullProgressMonitor.INSTANCE; + if (writeMonitor == null) + writeMonitor = NullProgressMonitor.INSTANCE; + + if ((reuseDeltas || reuseObjects) && reuseSupport != null) + searchForReuse(); + if (deltaCompress) + searchForDeltas(compressMonitor); + + final PackOutputStream out = new PackOutputStream(writeMonitor, + packStream, this); + + writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber()); + out.writeFileHeader(PACK_VERSION_GENERATED, getObjectsNumber()); + writeObjects(writeMonitor, out); + writeChecksum(out); + + reader.release(); + writeMonitor.endTask(); + } + + /** Release all resources used by this writer. */ + public void release() { + reader.release(); + if (myDeflater != null) { + myDeflater.end(); + myDeflater = null; + } + } + + private void searchForReuse() throws IOException { + for (List<ObjectToPack> list : objectsLists) { + for (ObjectToPack otp : list) + reuseSupport.selectObjectRepresentation(this, otp); + } + } + + private void searchForDeltas(ProgressMonitor monitor) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + // Commits and annotated tags tend to have too many differences to + // really benefit from delta compression. Consequently just don't + // bother examining those types here. + // + ObjectToPack[] list = new ObjectToPack[ + objectsLists[Constants.OBJ_TREE].size() + + objectsLists[Constants.OBJ_BLOB].size() + + edgeObjects.size()]; + int cnt = 0; + cnt = findObjectsNeedingDelta(list, cnt, Constants.OBJ_TREE); + cnt = findObjectsNeedingDelta(list, cnt, Constants.OBJ_BLOB); + if (cnt == 0) + return; + + // Queue up any edge objects that we might delta against. We won't + // be sending these as we assume the other side has them, but we need + // them in the search phase below. + // + for (ObjectToPack eo : edgeObjects) { + try { + if (loadSize(eo)) + list[cnt++] = eo; + } catch (IOException notAvailable) { + // Skip this object. Since we aren't going to write it out + // the only consequence of it being unavailable to us is we + // may produce a larger data stream than we could have. + // + if (!ignoreMissingUninteresting) + throw notAvailable; + } + } + + monitor.beginTask(COMPRESSING_OBJECTS_PROGRESS, cnt); + + // Sort the objects by path hash so like files are near each other, + // and then by size descending so that bigger files are first. This + // applies "Linus' Law" which states that newer files tend to be the + // bigger ones, because source files grow and hardly ever shrink. + // + Arrays.sort(list, 0, cnt, new Comparator<ObjectToPack>() { + public int compare(ObjectToPack a, ObjectToPack b) { + int cmp = a.getType() - b.getType(); + if (cmp == 0) + cmp = (a.getPathHash() >>> 1) - (b.getPathHash() >>> 1); + if (cmp == 0) + cmp = (a.getPathHash() & 1) - (b.getPathHash() & 1); + if (cmp == 0) + cmp = b.getWeight() - a.getWeight(); + return cmp; + } + }); + searchForDeltas(monitor, list, cnt); + monitor.endTask(); + } + + private int findObjectsNeedingDelta(ObjectToPack[] list, int cnt, int type) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + for (ObjectToPack otp : objectsLists[type]) { + if (otp.isDoNotDelta()) // delta is disabled for this path + continue; + if (otp.isDeltaRepresentation()) // already reusing a delta + continue; + if (loadSize(otp)) + list[cnt++] = otp; + } + return cnt; + } + + private boolean loadSize(ObjectToPack e) throws MissingObjectException, + IncorrectObjectTypeException, IOException { + long sz = reader.getObjectSize(e, e.getType()); + + // If its too big for us to handle, skip over it. + // + if (bigFileThreshold <= sz || Integer.MAX_VALUE <= sz) + return false; + + // If its too tiny for the delta compression to work, skip it. + // + if (sz <= DeltaIndex.BLKSZ) + return false; + + e.setWeight((int) sz); + return true; + } + + private void searchForDeltas(final ProgressMonitor monitor, + final ObjectToPack[] list, final int cnt) + throws MissingObjectException, IncorrectObjectTypeException, + LargeObjectException, IOException { + if (threads == 0) + threads = Runtime.getRuntime().availableProcessors(); + + if (threads <= 1 || cnt <= 2 * getDeltaSearchWindowSize()) { + DeltaCache dc = new DeltaCache(this); + DeltaWindow dw = new DeltaWindow(this, dc, reader); + dw.search(monitor, list, 0, cnt); + return; + } + + final List<Throwable> errors = Collections + .synchronizedList(new ArrayList<Throwable>()); + final DeltaCache dc = new ThreadSafeDeltaCache(this); + final ProgressMonitor pm = new ThreadSafeProgressMonitor(monitor); + final ExecutorService pool = Executors.newFixedThreadPool(threads); + + // Guess at the size of batch we want. Because we don't really + // have a way for a thread to steal work from another thread if + // it ends early, we over partition slightly so the work units + // are a bit smaller. + // + int estSize = cnt / (threads * 2); + if (estSize < 2 * getDeltaSearchWindowSize()) + estSize = 2 * getDeltaSearchWindowSize(); + + for (int i = 0; i < cnt;) { + final int start = i; + final int batchSize; + + if (cnt - i < estSize) { + // If we don't have enough to fill the remaining block, + // schedule what is left over as a single block. + // + batchSize = cnt - i; + } else { + // Try to split the block at the end of a path. + // + int end = start + estSize; + while (end < cnt) { + ObjectToPack a = list[end - 1]; + ObjectToPack b = list[end]; + if (a.getPathHash() == b.getPathHash()) + end++; + else + break; + } + batchSize = end - start; + } + i += batchSize; + + pool.submit(new Runnable() { + public void run() { + try { + final ObjectReader or = reader.newReader(); + try { + DeltaWindow dw; + dw = new DeltaWindow(PackWriter.this, dc, or); + dw.search(pm, list, start, batchSize); + } finally { + or.release(); + } + } catch (Throwable err) { + errors.add(err); + } + } + }); + } + + // Tell the pool to stop. + // + pool.shutdown(); + for (;;) { + try { + if (pool.awaitTermination(60, TimeUnit.SECONDS)) + break; + } catch (InterruptedException e) { + throw new IOException( + JGitText.get().packingCancelledDuringObjectsWriting); + } + } + + // If any thread threw an error, try to report it back as + // though we weren't using a threaded search algorithm. + // + if (!errors.isEmpty()) { + Throwable err = errors.get(0); + if (err instanceof Error) + throw (Error) err; + if (err instanceof RuntimeException) + throw (RuntimeException) err; + if (err instanceof IOException) + throw (IOException) err; + + IOException fail = new IOException(err.getMessage()); + fail.initCause(err); + throw fail; + } + } + + private void writeObjects(ProgressMonitor writeMonitor, PackOutputStream out) + throws IOException { + for (List<ObjectToPack> list : objectsLists) { + for (ObjectToPack otp : list) { + if (writeMonitor.isCancelled()) + throw new IOException( + JGitText.get().packingCancelledDuringObjectsWriting); + if (!otp.isWritten()) + writeObject(out, otp); + } + } + } + + private void writeObject(PackOutputStream out, final ObjectToPack otp) + throws IOException { + if (otp.isWritten()) + return; // We shouldn't be here. + + otp.markWantWrite(); + if (otp.isDeltaRepresentation()) + writeBaseFirst(out, otp); + + out.resetCRC32(); + otp.setOffset(out.length()); + + while (otp.isReuseAsIs()) { + try { + reuseSupport.copyObjectAsIs(out, otp); + out.endObject(); + otp.setCRC(out.getCRC32()); + return; + } catch (StoredObjectRepresentationNotAvailableException gone) { + if (otp.getOffset() == out.length()) { + redoSearchForReuse(otp); + continue; + } else { + // Object writing already started, we cannot recover. + // + CorruptObjectException coe; + coe = new CorruptObjectException(otp, ""); + coe.initCause(gone); + throw coe; + } + } + } + + // If we reached here, reuse wasn't possible. + // + if (otp.isDeltaRepresentation()) + writeDeltaObjectDeflate(out, otp); + else + writeWholeObjectDeflate(out, otp); + out.endObject(); + otp.setCRC(out.getCRC32()); + } + + private void writeBaseFirst(PackOutputStream out, final ObjectToPack otp) + throws IOException { + ObjectToPack baseInPack = otp.getDeltaBase(); + if (baseInPack != null) { + if (!baseInPack.isWritten()) { + if (baseInPack.wantWrite()) { + // There is a cycle. Our caller is trying to write the + // object we want as a base, and called us. Turn off + // delta reuse so we can find another form. + // + reuseDeltas = false; + redoSearchForReuse(otp); + reuseDeltas = true; + } else { + writeObject(out, baseInPack); + } + } + } else if (!thin) { + // This should never occur, the base isn't in the pack and + // the pack isn't allowed to reference base outside objects. + // Write the object as a whole form, even if that is slow. + // + otp.clearDeltaBase(); + otp.clearReuseAsIs(); + } + } + + private void redoSearchForReuse(final ObjectToPack otp) throws IOException, + MissingObjectException { + otp.clearDeltaBase(); + otp.clearReuseAsIs(); + reuseSupport.selectObjectRepresentation(this, otp); + } + + private void writeWholeObjectDeflate(PackOutputStream out, + final ObjectToPack otp) throws IOException { + final Deflater deflater = deflater(); + final ObjectLoader ldr = reader.open(otp, otp.getType()); + + out.writeHeader(otp, ldr.getSize()); + + deflater.reset(); + DeflaterOutputStream dst = new DeflaterOutputStream(out, deflater); + ldr.copyTo(dst); + dst.finish(); + } + + private void writeDeltaObjectDeflate(PackOutputStream out, + final ObjectToPack otp) throws IOException { + DeltaCache.Ref ref = otp.popCachedDelta(); + if (ref != null) { + byte[] zbuf = ref.get(); + if (zbuf != null) { + out.writeHeader(otp, otp.getCachedSize()); + out.write(zbuf); + return; + } + } + + TemporaryBuffer.Heap delta = delta(otp); + out.writeHeader(otp, delta.length()); + + Deflater deflater = deflater(); + deflater.reset(); + DeflaterOutputStream dst = new DeflaterOutputStream(out, deflater); + delta.writeTo(dst, null); + dst.finish(); + } + + private TemporaryBuffer.Heap delta(final ObjectToPack otp) + throws IOException { + DeltaIndex index = new DeltaIndex(buffer(reader, otp.getDeltaBaseId())); + byte[] res = buffer(reader, otp); + + // We never would have proposed this pair if the delta would be + // larger than the unpacked version of the object. So using it + // as our buffer limit is valid: we will never reach it. + // + TemporaryBuffer.Heap delta = new TemporaryBuffer.Heap(res.length); + index.encode(delta, res); + return delta; + } + + byte[] buffer(ObjectReader or, AnyObjectId objId) throws IOException { + ObjectLoader ldr = or.open(objId); + if (!ldr.isLarge()) + return ldr.getCachedBytes(); + + // PackWriter should have already pruned objects that + // are above the big file threshold, so our chances of + // the object being below it are very good. We really + // shouldn't be here, unless the implementation is odd. + + // If it really is too big to work with, abort out now. + // + long sz = ldr.getSize(); + if (getBigFileThreshold() <= sz || Integer.MAX_VALUE < sz) + throw new LargeObjectException(objId.copy()); + + // Its considered to be large by the loader, but we really + // want it in byte array format. Try to make it happen. + // + byte[] buf; + try { + buf = new byte[(int) sz]; + } catch (OutOfMemoryError noMemory) { + LargeObjectException e; + + e = new LargeObjectException(objId.copy()); + e.initCause(noMemory); + throw e; + } + InputStream in = ldr.openStream(); + try { + IO.readFully(in, buf, 0, buf.length); + } finally { + in.close(); + } + return buf; + } + + private Deflater deflater() { + if (myDeflater == null) + myDeflater = new Deflater(compressionLevel); + return myDeflater; + } + + private void writeChecksum(PackOutputStream out) throws IOException { + packcsum = out.getDigest(); + out.write(packcsum); + } + + private ObjectWalk setUpWalker( + final Collection<? extends ObjectId> interestingObjects, + final Collection<? extends ObjectId> uninterestingObjects) + throws MissingObjectException, IOException, + IncorrectObjectTypeException { + final ObjectWalk walker = new ObjectWalk(reader); + walker.setRetainBody(false); + walker.sort(RevSort.COMMIT_TIME_DESC); + if (thin) + walker.sort(RevSort.BOUNDARY, true); + + for (ObjectId id : interestingObjects) { + RevObject o = walker.parseAny(id); + walker.markStart(o); + } + if (uninterestingObjects != null) { + for (ObjectId id : uninterestingObjects) { + final RevObject o; + try { + o = walker.parseAny(id); + } catch (MissingObjectException x) { + if (ignoreMissingUninteresting) + continue; + throw x; + } + walker.markUninteresting(o); + } + } + return walker; + } + + private void findObjectsToPack(final ProgressMonitor countingMonitor, + final ObjectWalk walker) throws MissingObjectException, + IncorrectObjectTypeException, IOException { + countingMonitor.beginTask(COUNTING_OBJECTS_PROGRESS, + ProgressMonitor.UNKNOWN); + RevObject o; + + while ((o = walker.next()) != null) { + addObject(o, 0); + countingMonitor.update(1); + } + while ((o = walker.nextObject()) != null) { + addObject(o, walker.getPathHashCode()); + countingMonitor.update(1); + } + countingMonitor.endTask(); + } + + /** + * Include one object to the output file. + * <p> + * Objects are written in the order they are added. If the same object is + * added twice, it may be written twice, creating a larger than necessary + * file. + * + * @param object + * the object to add. + * @throws IncorrectObjectTypeException + * the object is an unsupported type. + */ + public void addObject(final RevObject object) + throws IncorrectObjectTypeException { + addObject(object, 0); + } + + private void addObject(final RevObject object, final int pathHashCode) + throws IncorrectObjectTypeException { + if (object.has(RevFlag.UNINTERESTING)) { + switch (object.getType()) { + case Constants.OBJ_TREE: + case Constants.OBJ_BLOB: + ObjectToPack otp = new ObjectToPack(object); + otp.setPathHash(pathHashCode); + otp.setDoNotDelta(true); + edgeObjects.add(otp); + thin = true; + break; + } + return; + } + + final ObjectToPack otp; + if (reuseSupport != null) + otp = reuseSupport.newObjectToPack(object); + else + otp = new ObjectToPack(object); + otp.setPathHash(pathHashCode); + + try { + objectsLists[object.getType()].add(otp); + } catch (ArrayIndexOutOfBoundsException x) { + throw new IncorrectObjectTypeException(object, + JGitText.get().incorrectObjectType_COMMITnorTREEnorBLOBnorTAG); + } catch (UnsupportedOperationException x) { + // index pointing to "dummy" empty list + throw new IncorrectObjectTypeException(object, + JGitText.get().incorrectObjectType_COMMITnorTREEnorBLOBnorTAG); + } + objectsMap.add(otp); + } + + /** + * Select an object representation for this writer. + * <p> + * An {@link ObjectReader} implementation should invoke this method once for + * each representation available for an object, to allow the writer to find + * the most suitable one for the output. + * + * @param otp + * the object being packed. + * @param next + * the next available representation from the repository. + */ + public void select(ObjectToPack otp, StoredObjectRepresentation next) { + int nFmt = next.getFormat(); + int nWeight; + if (otp.isReuseAsIs()) { + // We've already chosen to reuse a packed form, if next + // cannot beat that break out early. + // + if (PACK_WHOLE < nFmt) + return; // next isn't packed + else if (PACK_DELTA < nFmt && otp.isDeltaRepresentation()) + return; // next isn't a delta, but we are + + nWeight = next.getWeight(); + if (otp.getWeight() <= nWeight) + return; // next would be bigger + } else + nWeight = next.getWeight(); + + if (nFmt == PACK_DELTA && reuseDeltas) { + ObjectId baseId = next.getDeltaBase(); + ObjectToPack ptr = objectsMap.get(baseId); + if (ptr != null) { + otp.setDeltaBase(ptr); + otp.setReuseAsIs(); + otp.setWeight(nWeight); + } else if (thin && edgeObjects.contains(baseId)) { + otp.setDeltaBase(baseId); + otp.setReuseAsIs(); + otp.setWeight(nWeight); + } else { + otp.clearDeltaBase(); + otp.clearReuseAsIs(); + } + } else if (nFmt == PACK_WHOLE && reuseObjects) { + otp.clearDeltaBase(); + otp.setReuseAsIs(); + otp.setWeight(nWeight); + } else { + otp.clearDeltaBase(); + otp.clearReuseAsIs(); + } + + otp.select(next); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/StoredObjectRepresentation.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/StoredObjectRepresentation.java new file mode 100644 index 0000000000..334ea5ea19 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/StoredObjectRepresentation.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.pack; + +import org.eclipse.jgit.lib.ObjectId; + +/** + * An object representation {@link PackWriter} can consider for packing. + */ +public class StoredObjectRepresentation { + /** Special unknown value for {@link #getWeight()}. */ + public static final int WEIGHT_UNKNOWN = Integer.MAX_VALUE; + + /** Stored in pack format, as a delta to another object. */ + public static final int PACK_DELTA = 0; + + /** Stored in pack format, without delta. */ + public static final int PACK_WHOLE = 1; + + /** Only available after inflating to canonical format. */ + public static final int FORMAT_OTHER = 2; + + /** + * @return relative size of this object's packed form. The special value + * {@link #WEIGHT_UNKNOWN} can be returned to indicate the + * implementation doesn't know, or cannot supply the weight up + * front. + */ + public int getWeight() { + return WEIGHT_UNKNOWN; + } + + /** + * @return true if this is a delta against another object and this is stored + * in pack delta format. + */ + public int getFormat() { + return FORMAT_OTHER; + } + + /** + * @return identity of the object this delta applies to in order to recover + * the original object content. This method should only be called if + * {@link #getFormat()} returned {@link #PACK_DELTA}. + */ + public ObjectId getDeltaBase() { + return null; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ThreadSafeDeltaCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ThreadSafeDeltaCache.java new file mode 100644 index 0000000000..141289190a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ThreadSafeDeltaCache.java @@ -0,0 +1,86 @@ +/* + * 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.pack; + +import java.util.concurrent.locks.ReentrantLock; + +class ThreadSafeDeltaCache extends DeltaCache { + private final ReentrantLock lock; + + ThreadSafeDeltaCache(PackWriter pw) { + super(pw); + lock = new ReentrantLock(); + } + + @Override + boolean canCache(int length, ObjectToPack src, ObjectToPack res) { + lock.lock(); + try { + return super.canCache(length, src, res); + } finally { + lock.unlock(); + } + } + + @Override + void credit(int reservedSize) { + lock.lock(); + try { + super.credit(reservedSize); + } finally { + lock.unlock(); + } + } + + @Override + Ref cache(byte[] data, int actLen, int reservedSize) { + data = resize(data, actLen); + lock.lock(); + try { + return super.cache(data, actLen, reservedSize); + } finally { + lock.unlock(); + } + } +} 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..af18f18d8e 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; @@ -270,6 +270,12 @@ abstract class BasePackFetchConnection extends BasePackConnection implements } } + @Override + public void close() { + walk.release(); + super.close(); + } + private int maxTimeWanted(final Collection<Ref> wants) { int maxTime = 0; for (final Ref r : wants) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java index 44ccd2d6ad..297105d468 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java @@ -48,6 +48,7 @@ import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; import org.eclipse.jgit.JGitText; @@ -56,9 +57,9 @@ import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.PackWriter; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.storage.pack.PackWriter; import org.eclipse.jgit.transport.RemoteRefUpdate.Status; /** @@ -226,25 +227,29 @@ class BasePackPushConnection extends BasePackConnection implements private void writePack(final Map<String, RemoteRefUpdate> refUpdates, final ProgressMonitor monitor) throws IOException { - final PackWriter writer = new PackWriter(local, monitor); - final ArrayList<ObjectId> remoteObjects = new ArrayList<ObjectId>( - getRefs().size()); - final ArrayList<ObjectId> newObjects = new ArrayList<ObjectId>( - refUpdates.size()); - - for (final Ref r : getRefs()) - remoteObjects.add(r.getObjectId()); - remoteObjects.addAll(additionalHaves); - for (final RemoteRefUpdate r : refUpdates.values()) { - if (!ObjectId.zeroId().equals(r.getNewObjectId())) - newObjects.add(r.getNewObjectId()); - } + List<ObjectId> remoteObjects = new ArrayList<ObjectId>(getRefs().size()); + List<ObjectId> newObjects = new ArrayList<ObjectId>(refUpdates.size()); + + final long start; + final PackWriter writer = new PackWriter(local); + try { - writer.setThin(thinPack); - writer.setDeltaBaseAsOffset(capableOfsDelta); - writer.preparePack(newObjects, remoteObjects); - final long start = System.currentTimeMillis(); - writer.writePack(out); + for (final Ref r : getRefs()) + remoteObjects.add(r.getObjectId()); + remoteObjects.addAll(additionalHaves); + for (final RemoteRefUpdate r : refUpdates.values()) { + if (!ObjectId.zeroId().equals(r.getNewObjectId())) + newObjects.add(r.getNewObjectId()); + } + + writer.setThin(thinPack); + writer.setDeltaBaseAsOffset(capableOfsDelta); + writer.preparePack(monitor, newObjects, remoteObjects); + start = System.currentTimeMillis(); + writer.writePack(monitor, monitor, out); + } finally { + writer.release(); + } out.flush(); packTransferTime = System.currentTimeMillis() - start; } 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..126acab48d 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; @@ -213,57 +213,65 @@ class BundleFetchConnection extends BaseFetchConnection { return; final RevWalk rw = new RevWalk(transport.local); - final RevFlag PREREQ = rw.newFlag("PREREQ"); - final RevFlag SEEN = rw.newFlag("SEEN"); + try { + final RevFlag PREREQ = rw.newFlag("PREREQ"); + final RevFlag SEEN = rw.newFlag("SEEN"); - final Map<ObjectId, String> missing = new HashMap<ObjectId, String>(); - final List<RevObject> commits = new ArrayList<RevObject>(); - for (final Map.Entry<ObjectId, String> e : prereqs.entrySet()) { - ObjectId p = e.getKey(); - try { - final RevCommit c = rw.parseCommit(p); - if (!c.has(PREREQ)) { - c.add(PREREQ); - commits.add(c); + final Map<ObjectId, String> missing = new HashMap<ObjectId, String>(); + final List<RevObject> commits = new ArrayList<RevObject>(); + for (final Map.Entry<ObjectId, String> e : prereqs.entrySet()) { + ObjectId p = e.getKey(); + try { + final RevCommit c = rw.parseCommit(p); + if (!c.has(PREREQ)) { + c.add(PREREQ); + commits.add(c); + } + } catch (MissingObjectException notFound) { + missing.put(p, e.getValue()); + } catch (IOException err) { + throw new TransportException(transport.uri, MessageFormat + .format(JGitText.get().cannotReadCommit, p.name()), + err); } - } catch (MissingObjectException notFound) { - missing.put(p, e.getValue()); - } catch (IOException err) { - throw new TransportException(transport.uri - , MessageFormat.format(JGitText.get().cannotReadCommit, p.name()), err); } - } - if (!missing.isEmpty()) - throw new MissingBundlePrerequisiteException(transport.uri, missing); + if (!missing.isEmpty()) + throw new MissingBundlePrerequisiteException(transport.uri, + missing); - for (final Ref r : transport.local.getAllRefs().values()) { - try { - rw.markStart(rw.parseCommit(r.getObjectId())); - } catch (IOException readError) { - // If we cannot read the value of the ref skip it. + for (final Ref r : transport.local.getAllRefs().values()) { + try { + rw.markStart(rw.parseCommit(r.getObjectId())); + } catch (IOException readError) { + // If we cannot read the value of the ref skip it. + } } - } - int remaining = commits.size(); - try { - RevCommit c; - while ((c = rw.next()) != null) { - if (c.has(PREREQ)) { - c.add(SEEN); - if (--remaining == 0) - break; + int remaining = commits.size(); + try { + RevCommit c; + while ((c = rw.next()) != null) { + if (c.has(PREREQ)) { + c.add(SEEN); + if (--remaining == 0) + break; + } } + } catch (IOException err) { + throw new TransportException(transport.uri, + JGitText.get().cannotReadObject, err); } - } catch (IOException err) { - throw new TransportException(transport.uri, JGitText.get().cannotReadObject, err); - } - if (remaining > 0) { - for (final RevObject o : commits) { - if (!o.has(SEEN)) - missing.put(o, prereqs.get(o)); + if (remaining > 0) { + for (final RevObject o : commits) { + if (!o.has(SEEN)) + missing.put(o, prereqs.get(o)); + } + throw new MissingBundlePrerequisiteException(transport.uri, + missing); } - throw new MissingBundlePrerequisiteException(transport.uri, missing); + } finally { + rw.release(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java index 7e91557b09..79fa58c368 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java @@ -57,11 +57,11 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.PackWriter; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.pack.PackWriter; /** * Creates a Git bundle file, for sneaker-net transport to another system. @@ -92,11 +92,9 @@ public class BundleWriter { * * @param repo * repository where objects are stored. - * @param monitor - * operations progress monitor. */ - public BundleWriter(final Repository repo, final ProgressMonitor monitor) { - packWriter = new PackWriter(repo, monitor); + public BundleWriter(final Repository repo) { + packWriter = new PackWriter(repo); include = new TreeMap<String, ObjectId>(); assume = new HashSet<RevCommit>(); } @@ -155,6 +153,8 @@ public class BundleWriter { * <p> * This method can only be called once per BundleWriter instance. * + * @param monitor + * progress monitor to report bundle writing status to. * @param os * the stream the bundle is written to. The stream should be * buffered by the caller. The caller is responsible for closing @@ -164,38 +164,43 @@ public class BundleWriter { * the bundle, or writing compressed object data to the output * stream. */ - public void writeBundle(OutputStream os) throws IOException { - final HashSet<ObjectId> inc = new HashSet<ObjectId>(); - final HashSet<ObjectId> exc = new HashSet<ObjectId>(); - inc.addAll(include.values()); - for (final RevCommit r : assume) - exc.add(r.getId()); - packWriter.setThin(exc.size() > 0); - packWriter.preparePack(inc, exc); + public void writeBundle(ProgressMonitor monitor, OutputStream os) + throws IOException { + try { + final HashSet<ObjectId> inc = new HashSet<ObjectId>(); + final HashSet<ObjectId> exc = new HashSet<ObjectId>(); + inc.addAll(include.values()); + for (final RevCommit r : assume) + exc.add(r.getId()); + packWriter.setThin(exc.size() > 0); + packWriter.preparePack(monitor, inc, exc); - final Writer w = new OutputStreamWriter(os, Constants.CHARSET); - w.write(TransportBundle.V2_BUNDLE_SIGNATURE); - w.write('\n'); + final Writer w = new OutputStreamWriter(os, Constants.CHARSET); + w.write(TransportBundle.V2_BUNDLE_SIGNATURE); + w.write('\n'); - final char[] tmp = new char[Constants.OBJECT_ID_STRING_LENGTH]; - for (final RevCommit a : assume) { - w.write('-'); - a.copyTo(tmp, w); - if (a.getRawBuffer() != null) { + final char[] tmp = new char[Constants.OBJECT_ID_STRING_LENGTH]; + for (final RevCommit a : assume) { + w.write('-'); + a.copyTo(tmp, w); + if (a.getRawBuffer() != null) { + w.write(' '); + w.write(a.getShortMessage()); + } + w.write('\n'); + } + for (final Map.Entry<String, ObjectId> e : include.entrySet()) { + e.getValue().copyTo(tmp, w); w.write(' '); - w.write(a.getShortMessage()); + w.write(e.getKey()); + w.write('\n'); } + w.write('\n'); + w.flush(); + packWriter.writePack(monitor, monitor, os); + } finally { + packWriter.release(); } - for (final Map.Entry<String, ObjectId> e : include.entrySet()) { - e.getValue().copyTo(tmp, w); - w.write(' '); - w.write(e.getKey()); - w.write('\n'); - } - - w.write('\n'); - w.flush(); - packWriter.writePack(os); } } 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 fc203f69c8..ca68858059 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. */ @@ -176,16 +176,21 @@ class FetchProcess { } final RevWalk walk = new RevWalk(transport.local); - if (transport.isRemoveDeletedRefs()) - deleteStaleTrackingRefs(result, walk); - for (TrackingRefUpdate u : localUpdates) { - try { - u.update(walk); - result.add(u); - } catch (IOException err) { - throw new TransportException(MessageFormat.format( - JGitText.get().failureUpdatingTrackingRef, u.getLocalName(), err.getMessage()), err); + try { + if (transport.isRemoveDeletedRefs()) + deleteStaleTrackingRefs(result, walk); + for (TrackingRefUpdate u : localUpdates) { + try { + u.update(walk); + result.add(u); + } catch (IOException err) { + throw new TransportException(MessageFormat.format(JGitText + .get().failureUpdatingTrackingRef, + u.getLocalName(), err.getMessage()), err); + } } + } finally { + walk.release(); } if (!fetchHeadUpdates.isEmpty()) { @@ -271,8 +276,10 @@ class FetchProcess { } private void updateFETCH_HEAD(final FetchResult result) throws IOException { - final LockFile lock = new LockFile(new File(transport.local - .getDirectory(), "FETCH_HEAD")); + File meta = transport.local.getDirectory(); + if (meta == null) + return; + final LockFile lock = new LockFile(new File(meta, "FETCH_HEAD")); try { if (lock.lock()) { final Writer w = new OutputStreamWriter(lock.getOutputStream()); @@ -294,11 +301,15 @@ class FetchProcess { private boolean askForIsComplete() throws TransportException { try { final ObjectWalk ow = new ObjectWalk(transport.local); - for (final ObjectId want : askFor.keySet()) - ow.markStart(ow.parseAny(want)); - for (final Ref ref : transport.local.getAllRefs().values()) - ow.markUninteresting(ow.parseAny(ref.getObjectId())); - ow.checkConnectivity(); + try { + for (final ObjectId want : askFor.keySet()) + ow.markStart(ow.parseAny(want)); + for (final Ref ref : transport.local.getAllRefs().values()) + ow.markUninteresting(ow.parseAny(ref.getObjectId())); + ow.checkConnectivity(); + } finally { + ow.release(); + } return true; } catch (MissingObjectException e) { return false; 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 491227d091..25b499b32e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java @@ -65,8 +65,8 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AnyObjectId; -import org.eclipse.jgit.lib.BinaryDelta; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.CoreConfig; import org.eclipse.jgit.lib.InflaterCache; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectChecker; @@ -74,11 +74,12 @@ 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.WindowCursor; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.storage.file.PackIndexWriter; +import org.eclipse.jgit.storage.file.PackLock; +import org.eclipse.jgit.storage.pack.BinaryDelta; import org.eclipse.jgit.util.NB; /** Indexes Git pack files for local use. */ @@ -127,7 +128,8 @@ public class IndexPack { base = new File(objdir, n.substring(0, n.length() - suffix.length())); final IndexPack ip = new IndexPack(db, is, base); - ip.setIndexVersion(db.getConfig().getCore().getPackIndexVersion()); + ip.setIndexVersion(db.getConfig().get(CoreConfig.KEY) + .getPackIndexVersion()); return ip; } @@ -224,7 +226,7 @@ public class IndexPack { /** If {@link #fixThin} this is the last byte of the original checksum. */ private long originalEOF; - private WindowCursor readCurs; + private ObjectReader readCurs; /** * Create a new pack indexer utility. @@ -243,7 +245,7 @@ public class IndexPack { objectDatabase = db.getObjectDatabase().newCachedDatabase(); in = src; inflater = InflaterCache.get(); - readCurs = new WindowCursor(); + readCurs = objectDatabase.newReader(); buf = new byte[BUFFER_SIZE]; skipBuffer = new byte[512]; objectDigest = Constants.newMessageDigest(); @@ -436,12 +438,18 @@ public class IndexPack { } finally { try { + if (readCurs != null) + readCurs.release(); + } finally { + readCurs = null; + } + + try { InflaterCache.release(inflater); } finally { inflater = null; objectDatabase.close(); } - readCurs = WindowCursor.release(readCurs); progress.endTask(); if (packOut != null) @@ -598,8 +606,10 @@ public class IndexPack { continue; if (needBaseObjectIds) baseObjectIds.add(baseId); - final ObjectLoader ldr = repo.openObject(readCurs, baseId); - if (ldr == null) { + final ObjectLoader ldr; + try { + ldr = readCurs.open(baseId); + } catch (MissingObjectException notFound) { missing.add(baseId); continue; } @@ -858,12 +868,16 @@ public class IndexPack { } } - final ObjectLoader ldr = objectDatabase.openObject(readCurs, id); - if (ldr != null) { + try { + final ObjectLoader ldr = readCurs.open(id, type); final byte[] existingData = ldr.getCachedBytes(); - if (ldr.getType() != type || !Arrays.equals(data, existingData)) { + if (!Arrays.equals(data, existingData)) { throw new IOException(MessageFormat.format(JGitText.get().collisionOn, id.name())); } + } catch (MissingObjectException notLocal) { + // This is OK, we don't have a copy of the object locally + // but the API throws when we try to read it as usually its + // an error to read something that doesn't exist. } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java index 02497cb06f..6cd796a0dc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java @@ -122,32 +122,38 @@ class PushProcess { */ PushResult execute(final ProgressMonitor monitor) throws NotSupportedException, TransportException { - monitor.beginTask(PROGRESS_OPENING_CONNECTION, ProgressMonitor.UNKNOWN); - - final PushResult res = new PushResult(); - connection = transport.openPush(); try { - res.setAdvertisedRefs(transport.getURI(), connection.getRefsMap()); - res.setRemoteUpdates(toPush); - monitor.endTask(); - - final Map<String, RemoteRefUpdate> preprocessed = prepareRemoteUpdates(); - if (transport.isDryRun()) - modifyUpdatesForDryRun(); - else if (!preprocessed.isEmpty()) - connection.push(monitor, preprocessed); + monitor.beginTask(PROGRESS_OPENING_CONNECTION, + ProgressMonitor.UNKNOWN); + + final PushResult res = new PushResult(); + connection = transport.openPush(); + try { + res.setAdvertisedRefs(transport.getURI(), connection + .getRefsMap()); + res.setRemoteUpdates(toPush); + monitor.endTask(); + + final Map<String, RemoteRefUpdate> preprocessed = prepareRemoteUpdates(); + if (transport.isDryRun()) + modifyUpdatesForDryRun(); + else if (!preprocessed.isEmpty()) + connection.push(monitor, preprocessed); + } finally { + connection.close(); + res.addMessages(connection.getMessages()); + } + if (!transport.isDryRun()) + updateTrackingRefs(); + for (final RemoteRefUpdate rru : toPush.values()) { + final TrackingRefUpdate tru = rru.getTrackingRefUpdate(); + if (tru != null) + res.add(tru); + } + return res; } finally { - connection.close(); - res.addMessages(connection.getMessages()); - } - if (!transport.isDryRun()) - updateTrackingRefs(); - for (final RemoteRefUpdate rru : toPush.values()) { - final TrackingRefUpdate tru = rru.getTrackingRefUpdate(); - if (tru != null) - res.add(tru); + walker.release(); } - return res; } private Map<String, RemoteRefUpdate> prepareRemoteUpdates() 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 e42b7fe0c9..6b0a9b6cff 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; @@ -84,9 +83,9 @@ import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; 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.PackLock; import org.eclipse.jgit.transport.ReceiveCommand.Result; import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; import org.eclipse.jgit.util.io.InterruptTimer; @@ -575,6 +574,7 @@ public class ReceivePack { service(); } finally { + walk.release(); try { if (pckOut != null) pckOut.flush(); @@ -697,7 +697,7 @@ public class ReceivePack { adv.send(refs); if (head != null && !head.isSymbolic()) adv.advertiseHave(head.getObjectId()); - adv.includeAdditionalHaves(); + adv.includeAdditionalHaves(db); if (adv.isEmpty()) adv.advertiseId(ObjectId.zeroId(), "capabilities^{}"); adv.end(); @@ -818,8 +818,7 @@ public class ReceivePack { ow.markUninteresting(o); if (checkReferencedIsReachable && !baseObjects.isEmpty()) { - while (o instanceof RevTag) - o = ((RevTag) o).getObject(); + o = ow.peel(o); if (o instanceof RevCommit) o = ((RevCommit) o).getTree(); if (o instanceof RevTree) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java index 694a2e0f16..df0afe73fa 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java @@ -49,10 +49,9 @@ import java.util.Map; import java.util.Set; import java.util.SortedMap; -import org.eclipse.jgit.lib.AlternateRepositoryDatabase; 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.Ref; import org.eclipse.jgit.lib.RefComparator; import org.eclipse.jgit.lib.Repository; @@ -126,7 +125,7 @@ public abstract class RefAdvertiser { * <ul> * <li>{@link #send(Map)} * <li>{@link #advertiseHave(AnyObjectId)} - * <li>{@link #includeAdditionalHaves()} + * <li>{@link #includeAdditionalHaves(Repository)} * </ul> * * @param deref @@ -144,7 +143,7 @@ public abstract class RefAdvertiser { * <ul> * <li>{@link #send(Map)} * <li>{@link #advertiseHave(AnyObjectId)} - * <li>{@link #includeAdditionalHaves()} + * <li>{@link #includeAdditionalHaves(Repository)} * </ul> * * @param name @@ -212,24 +211,15 @@ public abstract class RefAdvertiser { /** * Include references of alternate repositories as {@code .have} lines. * + * @param src + * repository to get the additional reachable objects from. * @throws IOException * the underlying output stream failed to write out an * advertisement record. */ - public void includeAdditionalHaves() throws IOException { - additionalHaves(walk.getRepository().getObjectDatabase()); - } - - private void additionalHaves(final ObjectDatabase db) throws IOException { - if (db instanceof AlternateRepositoryDatabase) - additionalHaves(((AlternateRepositoryDatabase) db).getRepository()); - for (ObjectDatabase alt : db.getAlternates()) - additionalHaves(alt); - } - - private void additionalHaves(final Repository alt) throws IOException { - for (final Ref r : alt.getAllRefs().values()) - advertiseHave(r.getObjectId()); + public void includeAdditionalHaves(Repository src) throws IOException { + for (ObjectId id : src.getAdditionalHaves()) + advertiseHave(id); } /** @return true if no advertisements have been sent yet. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java index 1b17c9f0f4..37e03fd62a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java @@ -357,6 +357,6 @@ public class RemoteRefUpdate { + "..." + (newObjectId != null ? newObjectId.abbreviate(localDb).name() : "(null)") + (fastForward ? ", fastForward" : "") + ", srcRef=" + srcRef + (forceUpdate ? ", forceUpdate" : "") + ", message=" + (message != null ? "\"" - + message + "\"" : "null") + ", " + localDb.getDirectory() + "]"; + + message + "\"" : "null") + "]"; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java index e1988a6c85..a8e47afd38 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java @@ -566,7 +566,7 @@ public abstract class Transport { * URI passed to {@link #open(Repository, URIish)}. */ protected Transport(final Repository local, final URIish uri) { - final TransferConfig tc = local.getConfig().getTransfer(); + final TransferConfig tc = local.getConfig().get(TransferConfig.KEY); this.local = local; this.uri = uri; this.checkFetchedObjects = tc.isFsckObjects(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java index 56a5c9796d..79b88b6a73 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java @@ -126,23 +126,7 @@ public class TransportAmazonS3 extends HttpTransport implements WalkTransport { throws NotSupportedException { super(local, uri); - Properties props = null; - File propsFile = new File(local.getDirectory(), uri.getUser()); - if (!propsFile.isFile()) - propsFile = new File(local.getFS().userHome(), uri.getUser()); - if (propsFile.isFile()) { - try { - props = AmazonS3.properties(propsFile); - } catch (IOException e) { - throw new NotSupportedException(MessageFormat.format(JGitText.get().cannotReadFile, propsFile), e); - } - } else { - props = new Properties(); - props.setProperty("accesskey", uri.getUser()); - props.setProperty("secretkey", uri.getPass()); - } - - s3 = new AmazonS3(props); + s3 = new AmazonS3(loadProperties()); bucket = uri.getHost(); String p = uri.getPath(); @@ -153,6 +137,33 @@ public class TransportAmazonS3 extends HttpTransport implements WalkTransport { keyPrefix = p; } + private Properties loadProperties() throws NotSupportedException { + if (local.getDirectory() != null) { + File propsFile = new File(local.getDirectory(), uri.getUser()); + if (propsFile.isFile()) + return loadPropertiesFile(propsFile); + } + + File propsFile = new File(local.getFS().userHome(), uri.getUser()); + if (propsFile.isFile()) + return loadPropertiesFile(propsFile); + + Properties props = new Properties(); + props.setProperty("accesskey", uri.getUser()); + props.setProperty("secretkey", uri.getPass()); + return props; + } + + private static Properties loadPropertiesFile(File propsFile) + throws NotSupportedException { + try { + return AmazonS3.properties(propsFile); + } catch (IOException e) { + throw new NotSupportedException(MessageFormat.format( + JGitText.get().cannotReadFile, propsFile), e); + } + } + @Override public FetchConnection openFetch() throws TransportException { final DatabaseS3 c = new DatabaseS3(bucket, keyPrefix + "/objects"); 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 08fd8901d8..c9b18be1f8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java @@ -61,6 +61,7 @@ import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.Constants; 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; @@ -174,7 +175,7 @@ class TransportLocal extends Transport implements PackTransport { final Repository dst; try { - dst = new Repository(remoteGitDir); + dst = new FileRepository(remoteGitDir); } catch (IOException err) { throw new TransportException(uri, JGitText.get().notAGitDirectory); } @@ -314,7 +315,7 @@ class TransportLocal extends Transport implements PackTransport { final Repository dst; try { - dst = new Repository(remoteGitDir); + dst = new FileRepository(remoteGitDir); } catch (IOException err) { throw new TransportException(uri, JGitText.get().notAGitDirectory); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java index 77cc1a6f0a..02ce251bea 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -56,10 +56,10 @@ import java.util.Map; import java.util.Set; import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.PackWriter; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; @@ -69,6 +69,7 @@ import org.eclipse.jgit.revwalk.RevFlagSet; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.pack.PackWriter; import org.eclipse.jgit.transport.BasePackFetchConnection.MultiAck; import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; import org.eclipse.jgit.util.io.InterruptTimer; @@ -295,6 +296,7 @@ public class UploadPack { pckOut = new PacketLineOut(rawOut); service(); } finally { + walk.release(); if (timer != null) { try { timer.terminate(); @@ -393,11 +395,15 @@ public class UploadPack { } if (!o.has(ADVERTISED)) throw new PackProtocolException(MessageFormat.format(JGitText.get().notValid, id.name())); - want(o); + try { + want(o); + } catch (IOException e) { + throw new PackProtocolException(MessageFormat.format(JGitText.get().notValid, id.name()), e); + } } } - private void want(RevObject o) { + private void want(RevObject o) throws MissingObjectException, IOException { if (!o.has(WANT)) { o.add(WANT); wantAll.add(o); @@ -406,9 +412,7 @@ public class UploadPack { wantCommits.add((RevCommit) o); else if (o instanceof RevTag) { - do { - o = ((RevTag) o).getObject(); - } while (o instanceof RevTag); + o = walk.peel(o); if (o instanceof RevCommit) want(o); } @@ -564,27 +568,30 @@ public class UploadPack { SideBandOutputStream.CH_PROGRESS, bufsz, rawOut)); } - final PackWriter pw; - pw = new PackWriter(db, pm, NullProgressMonitor.INSTANCE); - pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA)); - pw.setThin(thin); - pw.preparePack(wantAll, commonBase); - if (options.contains(OPTION_INCLUDE_TAG)) { - for (final Ref r : refs.values()) { - final RevObject o; - try { - o = walk.parseAny(r.getObjectId()); - } catch (IOException e) { - continue; + final PackWriter pw = new PackWriter(db, walk.getObjectReader()); + try { + pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA)); + pw.setThin(thin); + pw.preparePack(pm, wantAll, commonBase); + if (options.contains(OPTION_INCLUDE_TAG)) { + for (final Ref r : refs.values()) { + final RevObject o; + try { + o = walk.parseAny(r.getObjectId()); + } catch (IOException e) { + continue; + } + if (o.has(WANT) || !(o instanceof RevTag)) + continue; + final RevTag t = (RevTag) o; + if (!pw.willInclude(t) && pw.willInclude(t.getObject())) + pw.addObject(t); } - if (o.has(WANT) || !(o instanceof RevTag)) - continue; - final RevTag t = (RevTag) o; - if (!pw.willInclude(t) && pw.willInclude(t.getObject())) - pw.addObject(t); } + pw.writePack(pm, NullProgressMonitor.INSTANCE, packOut); + } finally { + pw.release(); } - pw.writePack(packOut); packOut.flush(); if (sideband) 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 6254745476..237b4314c4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java @@ -48,7 +48,6 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; -import java.security.MessageDigest; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; @@ -63,7 +62,6 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.CompoundException; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.errors.ObjectWritingException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; @@ -71,12 +69,12 @@ import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.PackIndex; -import org.eclipse.jgit.lib.PackLock; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; 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 +82,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.UnpackedObject; import org.eclipse.jgit.treewalk.TreeWalk; /** @@ -165,8 +167,6 @@ class WalkFetchConnection extends BaseFetchConnection { private final MutableObjectId idBuffer = new MutableObjectId(); - private final MessageDigest objectDigest = Constants.newMessageDigest(); - /** * Errors received while trying to obtain an object. * <p> @@ -180,10 +180,18 @@ class WalkFetchConnection extends BaseFetchConnection { private final List<PackLock> packLocks; + /** Inserter to write objects onto {@link #local}. */ + private final ObjectInserter inserter; + + /** Inserter to read objects from {@link #local}. */ + private final ObjectReader reader; + WalkFetchConnection(final WalkTransport t, final WalkRemoteObjectDatabase w) { Transport wt = (Transport)t; local = wt.local; objCheck = wt.isCheckFetchedObjects() ? new ObjectChecker() : null; + inserter = local.newObjectInserter(); + reader = local.newObjectReader(); remotes = new ArrayList<WalkRemoteObjectDatabase>(); remotes.add(w); @@ -200,9 +208,9 @@ class WalkFetchConnection extends BaseFetchConnection { fetchErrors = new HashMap<ObjectId, List<Throwable>>(); packLocks = new ArrayList<PackLock>(4); - revWalk = new RevWalk(local); + revWalk = new RevWalk(reader); revWalk.setRetainBody(false); - treeWalk = new TreeWalk(local); + treeWalk = new TreeWalk(reader); COMPLETE = revWalk.newFlag("COMPLETE"); IN_WORK_QUEUE = revWalk.newFlag("IN_WORK_QUEUE"); LOCALLY_SEEN = revWalk.newFlag("LOCALLY_SEEN"); @@ -240,8 +248,12 @@ class WalkFetchConnection extends BaseFetchConnection { @Override public void close() { - for (final RemotePack p : unfetchedPacks) - p.tmpIdx.delete(); + inserter.release(); + reader.release(); + for (final RemotePack p : unfetchedPacks) { + if (p.tmpIdx != null) + p.tmpIdx.delete(); + } for (final WalkRemoteObjectDatabase r : remotes) r.close(); } @@ -309,10 +321,17 @@ class WalkFetchConnection extends BaseFetchConnection { } private void processBlob(final RevObject obj) throws TransportException { - if (!local.hasObject(obj)) - throw new TransportException(MessageFormat.format(JGitText.get().cannotReadBlob, obj.name()), - new MissingObjectException(obj, Constants.TYPE_BLOB)); - obj.add(COMPLETE); + try { + if (reader.has(obj, Constants.OBJ_BLOB)) + obj.add(COMPLETE); + else + throw new TransportException(MessageFormat.format(JGitText + .get().cannotReadBlob, obj.name()), + new MissingObjectException(obj, Constants.TYPE_BLOB)); + } catch (IOException error) { + throw new TransportException(MessageFormat.format( + JGitText.get().cannotReadBlob, obj.name()), error); + } } private void processTree(final RevObject obj) throws TransportException { @@ -369,7 +388,7 @@ class WalkFetchConnection extends BaseFetchConnection { private void downloadObject(final ProgressMonitor pm, final AnyObjectId id) throws TransportException { - if (local.hasObject(id)) + if (alreadyHave(id)) return; for (;;) { @@ -456,6 +475,15 @@ class WalkFetchConnection extends BaseFetchConnection { } } + private boolean alreadyHave(final AnyObjectId id) throws TransportException { + try { + return reader.has(id); + } catch (IOException error) { + throw new TransportException(MessageFormat.format( + JGitText.get().cannotReadObject, id.name()), error); + } + } + private boolean downloadPackedObject(final ProgressMonitor monitor, final AnyObjectId id) throws TransportException { // Search for the object in a remote pack whose index we have, @@ -512,11 +540,12 @@ class WalkFetchConnection extends BaseFetchConnection { // it failed the index and pack are unusable and we // shouldn't consult them again. // - pack.tmpIdx.delete(); + if (pack.tmpIdx != null) + pack.tmpIdx.delete(); packItr.remove(); } - if (!local.hasObject(id)) { + if (!alreadyHave(id)) { // What the hell? This pack claimed to have // the object, but after indexing we didn't // actually find it in the pack. @@ -555,8 +584,7 @@ class WalkFetchConnection extends BaseFetchConnection { throws TransportException { try { final byte[] compressed = remote.open(looseName).toArray(); - verifyLooseObject(id, compressed); - saveLooseObject(id, compressed); + verifyAndInsertLooseObject(id, compressed); return true; } catch (FileNotFoundException e) { // Not available in a loose format from this alternate? @@ -569,11 +597,11 @@ class WalkFetchConnection extends BaseFetchConnection { } } - private void verifyLooseObject(final AnyObjectId id, final byte[] compressed) - throws IOException { - final UnpackedObjectLoader uol; + private void verifyAndInsertLooseObject(final AnyObjectId id, + final byte[] compressed) throws IOException { + final ObjectLoader uol; try { - uol = new UnpackedObjectLoader(compressed); + uol = UnpackedObject.parse(compressed, id); } catch (CorruptObjectException parsingError) { // Some HTTP servers send back a "200 OK" status with an HTML // page that explains the requested file could not be found. @@ -592,62 +620,23 @@ class WalkFetchConnection extends BaseFetchConnection { throw e; } - objectDigest.reset(); - objectDigest.update(Constants.encodedTypeString(uol.getType())); - objectDigest.update((byte) ' '); - objectDigest.update(Constants.encodeASCII(uol.getSize())); - objectDigest.update((byte) 0); - objectDigest.update(uol.getCachedBytes()); - idBuffer.fromRaw(objectDigest.digest(), 0); - - if (!AnyObjectId.equals(id, idBuffer)) { - throw new TransportException(MessageFormat.format(JGitText.get().incorrectHashFor - , id.name(), idBuffer.name(), Constants.typeString(uol.getType()), compressed.length)); - } + final int type = uol.getType(); + final byte[] raw = uol.getCachedBytes(); if (objCheck != null) { try { - objCheck.check(uol.getType(), uol.getCachedBytes()); + objCheck.check(type, raw); } catch (CorruptObjectException e) { throw new TransportException(MessageFormat.format(JGitText.get().transportExceptionInvalid - , Constants.typeString(uol.getType()), id.name(), e.getMessage())); + , Constants.typeString(type), id.name(), e.getMessage())); } } - } - - private void saveLooseObject(final AnyObjectId id, final byte[] compressed) - throws IOException, ObjectWritingException { - final File tmp; - tmp = File.createTempFile("noz", null, local.getObjectsDirectory()); - try { - final FileOutputStream out = new FileOutputStream(tmp); - try { - out.write(compressed); - } finally { - out.close(); - } - tmp.setReadOnly(); - } catch (IOException e) { - tmp.delete(); - throw e; + ObjectId act = inserter.insert(type, raw); + if (!AnyObjectId.equals(id, act)) { + throw new TransportException(MessageFormat.format(JGitText.get().incorrectHashFor + , id.name(), act.name(), Constants.typeString(type), compressed.length)); } - - final File o = local.toFile(id); - if (tmp.renameTo(o)) - return; - - // Maybe the directory doesn't exist yet as the object - // directories are always lazily created. Note that we - // try the rename first as the directory likely does exist. - // - o.getParentFile().mkdir(); - if (tmp.renameTo(o)) - return; - - tmp.delete(); - if (local.hasObject(id)) - return; - throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToStore, id.name())); + inserter.flush(); } private Collection<WalkRemoteObjectDatabase> expandOneAlternate( @@ -788,12 +777,11 @@ class WalkFetchConnection extends BaseFetchConnection { final String idxName; - final File tmpIdx; + File tmpIdx; PackIndex index; RemotePack(final WalkRemoteObjectDatabase c, final String pn) { - final File objdir = local.getObjectsDirectory(); connection = c; packName = pn; idxName = packName.substring(0, packName.length() - 5) + ".idx"; @@ -803,13 +791,19 @@ class WalkFetchConnection extends BaseFetchConnection { tn = tn.substring(5); if (tn.endsWith(".idx")) tn = tn.substring(0, tn.length() - 4); - tmpIdx = new File(objdir, "walk-" + tn + ".walkidx"); + + if (local.getObjectDatabase() instanceof ObjectDirectory) { + tmpIdx = new File(((ObjectDirectory) local.getObjectDatabase()) + .getDirectory(), "walk-" + tn + ".walkidx"); + } } void openIndex(final ProgressMonitor pm) throws IOException { if (index != null) return; - if (tmpIdx.isFile()) { + if (tmpIdx == null) + tmpIdx = File.createTempFile("jgit-walk-", ".idx"); + else if (tmpIdx.isFile()) { try { index = PackIndex.open(tmpIdx); return; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java index 0edf9678e4..bbc918f256 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java @@ -61,12 +61,12 @@ import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; -import org.eclipse.jgit.lib.PackWriter; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefWriter; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Ref.Storage; +import org.eclipse.jgit.storage.pack.PackWriter; import org.eclipse.jgit.transport.RemoteRefUpdate.Status; /** @@ -209,8 +209,8 @@ class WalkPushConnection extends BaseConnection implements PushConnection { String pathPack = null; String pathIdx = null; + final PackWriter pw = new PackWriter(local); try { - final PackWriter pw = new PackWriter(local, monitor); final List<ObjectId> need = new ArrayList<ObjectId>(); final List<ObjectId> have = new ArrayList<ObjectId>(); for (final RemoteRefUpdate r : updates) @@ -220,7 +220,7 @@ class WalkPushConnection extends BaseConnection implements PushConnection { if (r.getPeeledObjectId() != null) have.add(r.getPeeledObjectId()); } - pw.preparePack(need, have); + pw.preparePack(monitor, need, have); // We don't have to continue further if the pack will // be an empty pack, as the remote has all objects it @@ -254,7 +254,7 @@ class WalkPushConnection extends BaseConnection implements PushConnection { OutputStream os = dest.writeFile(pathPack, monitor, wt + "..pack"); try { os = new BufferedOutputStream(os); - pw.writePack(os); + pw.writePack(monitor, monitor, os); } finally { os.close(); } @@ -281,6 +281,8 @@ class WalkPushConnection extends BaseConnection implements PushConnection { safeDelete(pathPack); throw new TransportException(uri, JGitText.get().cannotStoreObjects, err); + } finally { + pw.release(); } } 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/treewalk/AbstractTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java index 3b68abca99..a54b3e9cfa 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java @@ -55,8 +55,7 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.WindowCursor; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.treewalk.filter.TreeFilter; /** @@ -405,6 +404,24 @@ public abstract class AbstractTreeIterator { } /** + * Get the current entry's path hash code. + * <p> + * This method computes a hash code on the fly for this path, the hash is + * suitable to cluster objects that may have similar paths together. + * + * @return path hash code; any integer may be returned. + */ + public int getEntryPathHashCode() { + int hash = 0; + for (int i = Math.max(0, pathLen - 16); i < pathLen; i++) { + byte c = path[i]; + if (c != ' ') + hash = (hash >>> 2) + (c << 24); + } + return hash; + } + + /** * Get the byte array buffer object IDs must be copied out of. * <p> * The id buffer contains the bytes necessary to construct an ObjectId for @@ -433,8 +450,8 @@ public abstract class AbstractTreeIterator { * otherwise the caller would not be able to exit out of the subtree * iterator correctly and return to continue walking <code>this</code>. * - * @param repo - * repository to load the tree data from. + * @param reader + * reader to load the tree data from. * @return a new parser that walks over the current subtree. * @throws IncorrectObjectTypeException * the current entry is not actually a tree and cannot be parsed @@ -442,8 +459,9 @@ public abstract class AbstractTreeIterator { * @throws IOException * a loose object or pack file could not be read. */ - public abstract AbstractTreeIterator createSubtreeIterator(Repository repo) - throws IncorrectObjectTypeException, IOException; + public abstract AbstractTreeIterator createSubtreeIterator( + ObjectReader reader) throws IncorrectObjectTypeException, + IOException; /** * Create a new iterator as though the current entry were a subtree. @@ -461,12 +479,10 @@ public abstract class AbstractTreeIterator { * the caller would not be able to exit out of the subtree iterator * correctly and return to continue walking <code>this</code>. * - * @param repo - * repository to load the tree data from. + * @param reader + * reader to load the tree data from. * @param idBuffer * temporary ObjectId buffer for use by this method. - * @param curs - * window cursor to use during repository access. * @return a new parser that walks over the current subtree. * @throws IncorrectObjectTypeException * the current entry is not actually a tree and cannot be parsed @@ -474,10 +490,10 @@ public abstract class AbstractTreeIterator { * @throws IOException * a loose object or pack file could not be read. */ - public AbstractTreeIterator createSubtreeIterator(final Repository repo, - final MutableObjectId idBuffer, final WindowCursor curs) + public AbstractTreeIterator createSubtreeIterator( + final ObjectReader reader, final MutableObjectId idBuffer) throws IncorrectObjectTypeException, IOException { - return createSubtreeIterator(repo); + return createSubtreeIterator(reader); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java index 0b9dc00446..8e4094a055 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java @@ -54,9 +54,7 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectLoader; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.WindowCursor; +import org.eclipse.jgit.lib.ObjectReader; /** Parses raw Git trees from the canonical semi-text/semi-binary format. */ public class CanonicalTreeParser extends AbstractTreeIterator { @@ -86,13 +84,11 @@ public class CanonicalTreeParser extends AbstractTreeIterator { * may be null or the empty array to indicate the prefix is the * root of the repository. A trailing slash ('/') is * automatically appended if the prefix does not end in '/'. - * @param repo - * repository to load the tree data from. + * @param reader + * reader to load the tree data from. * @param treeId * identity of the tree being parsed; used only in exception * messages if data corruption is found. - * @param curs - * a window cursor to use during data access from the repository. * @throws MissingObjectException * the object supplied is not available from the repository. * @throws IncorrectObjectTypeException @@ -101,11 +97,11 @@ public class CanonicalTreeParser extends AbstractTreeIterator { * @throws IOException * a loose object or pack file could not be read. */ - public CanonicalTreeParser(final byte[] prefix, final Repository repo, - final AnyObjectId treeId, final WindowCursor curs) - throws IncorrectObjectTypeException, IOException { + public CanonicalTreeParser(final byte[] prefix, final ObjectReader reader, + final AnyObjectId treeId) throws IncorrectObjectTypeException, + IOException { super(prefix); - reset(repo, treeId, curs); + reset(reader, treeId); } private CanonicalTreeParser(final CanonicalTreeParser p) { @@ -131,13 +127,11 @@ public class CanonicalTreeParser extends AbstractTreeIterator { /** * Reset this parser to walk through the given tree. * - * @param repo - * repository to load the tree data from. + * @param reader + * reader to use during repository access. * @param id * identity of the tree being parsed; used only in exception * messages if data corruption is found. - * @param curs - * window cursor to use during repository access. * @return the root level parser. * @throws MissingObjectException * the object supplied is not available from the repository. @@ -147,13 +141,13 @@ public class CanonicalTreeParser extends AbstractTreeIterator { * @throws IOException * a loose object or pack file could not be read. */ - public CanonicalTreeParser resetRoot(final Repository repo, - final AnyObjectId id, final WindowCursor curs) - throws IncorrectObjectTypeException, IOException { + public CanonicalTreeParser resetRoot(final ObjectReader reader, + final AnyObjectId id) throws IncorrectObjectTypeException, + IOException { CanonicalTreeParser p = this; while (p.parent != null) p = (CanonicalTreeParser) p.parent; - p.reset(repo, id, curs); + p.reset(reader, id); return p; } @@ -181,13 +175,11 @@ public class CanonicalTreeParser extends AbstractTreeIterator { /** * Reset this parser to walk through the given tree. * - * @param repo - * repository to load the tree data from. + * @param reader + * reader to use during repository access. * @param id * identity of the tree being parsed; used only in exception * messages if data corruption is found. - * @param curs - * window cursor to use during repository access. * @throws MissingObjectException * the object supplied is not available from the repository. * @throws IncorrectObjectTypeException @@ -196,32 +188,21 @@ public class CanonicalTreeParser extends AbstractTreeIterator { * @throws IOException * a loose object or pack file could not be read. */ - public void reset(final Repository repo, final AnyObjectId id, - final WindowCursor curs) + public void reset(final ObjectReader reader, final AnyObjectId id) throws IncorrectObjectTypeException, IOException { - final ObjectLoader ldr = repo.openObject(curs, id); - if (ldr == null) { - final ObjectId me = id.toObjectId(); - throw new MissingObjectException(me, Constants.TYPE_TREE); - } - final byte[] subtreeData = ldr.getCachedBytes(); - if (ldr.getType() != Constants.OBJ_TREE) { - final ObjectId me = id.toObjectId(); - throw new IncorrectObjectTypeException(me, Constants.TYPE_TREE); - } - reset(subtreeData); + reset(reader.open(id, Constants.OBJ_TREE).getCachedBytes()); } @Override - public CanonicalTreeParser createSubtreeIterator(final Repository repo, - final MutableObjectId idBuffer, final WindowCursor curs) + public CanonicalTreeParser createSubtreeIterator(final ObjectReader reader, + final MutableObjectId idBuffer) throws IncorrectObjectTypeException, IOException { idBuffer.fromRaw(idBuffer(), idOffset()); if (!FileMode.TREE.equals(mode)) { final ObjectId me = idBuffer.toObjectId(); throw new IncorrectObjectTypeException(me, Constants.TYPE_TREE); } - return createSubtreeIterator0(repo, idBuffer, curs); + return createSubtreeIterator0(reader, idBuffer); } /** @@ -231,32 +212,25 @@ public class CanonicalTreeParser extends AbstractTreeIterator { * called only once the current entry has been identified as a tree and its * identity has been converted into an ObjectId. * - * @param repo - * repository to load the tree data from. + * @param reader + * reader to load the tree data from. * @param id * ObjectId of the tree to open. - * @param curs - * window cursor to use during repository access. * @return a new parser that walks over the current subtree. * @throws IOException * a loose object or pack file could not be read. */ public final CanonicalTreeParser createSubtreeIterator0( - final Repository repo, final AnyObjectId id, final WindowCursor curs) + final ObjectReader reader, final AnyObjectId id) throws IOException { final CanonicalTreeParser p = new CanonicalTreeParser(this); - p.reset(repo, id, curs); + p.reset(reader, id); return p; } - public CanonicalTreeParser createSubtreeIterator(final Repository repo) + public CanonicalTreeParser createSubtreeIterator(final ObjectReader reader) throws IncorrectObjectTypeException, IOException { - final WindowCursor curs = new WindowCursor(); - try { - return createSubtreeIterator(repo, new MutableObjectId(), curs); - } finally { - curs.release(); - } + return createSubtreeIterator(reader, new MutableObjectId()); } @Override diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java index 1776b50887..7d4ee6d2bd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java @@ -50,7 +50,7 @@ import java.io.IOException; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.ObjectReader; /** Iterator over an empty tree (a directory with no files). */ public class EmptyTreeIterator extends AbstractTreeIterator { @@ -87,7 +87,7 @@ public class EmptyTreeIterator extends AbstractTreeIterator { } @Override - public AbstractTreeIterator createSubtreeIterator(final Repository repo) + public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader) throws IncorrectObjectTypeException, IOException { return new EmptyTreeIterator(this); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java index 603dcf2baa..2d032ab835 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java @@ -54,6 +54,7 @@ import java.io.InputStream; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.FS; @@ -83,7 +84,7 @@ public class FileTreeIterator extends WorkingTreeIterator { * the repository whose working tree will be scanned. */ public FileTreeIterator(Repository repo) { - this(repo.getWorkDir(), repo.getFS()); + this(repo.getWorkTree(), repo.getFS()); initRootIterator(repo); } @@ -123,7 +124,7 @@ public class FileTreeIterator extends WorkingTreeIterator { } @Override - public AbstractTreeIterator createSubtreeIterator(final Repository repo) + public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader) throws IncorrectObjectTypeException, IOException { return new FileTreeIterator(this, ((FileEntry) current()).file, fs); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java index b569174bdb..99126e8615 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java @@ -46,6 +46,7 @@ package org.eclipse.jgit.treewalk; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; /** @@ -93,7 +94,17 @@ public class NameConflictTreeWalk extends TreeWalk { * the repository the walker will obtain data from. */ public NameConflictTreeWalk(final Repository repo) { - super(repo); + this(repo.newObjectReader()); + } + + /** + * Create a new tree walker for a given repository. + * + * @param or + * the reader the walker will obtain tree data from. + */ + public NameConflictTreeWalk(final ObjectReader or) { + super(or); } @Override diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java index 68965c1903..39e09a330c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java @@ -57,7 +57,7 @@ import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.WindowCursor; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; import org.eclipse.jgit.treewalk.filter.TreeFilter; @@ -113,11 +113,15 @@ public class TreeWalk { final AnyObjectId... trees) throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException { final TreeWalk r = new TreeWalk(db); - r.setFilter(PathFilterGroup.createFromStrings(Collections - .singleton(path))); - r.setRecursive(r.getFilter().shouldBeRecursive()); - r.reset(trees); - return r.next() ? r : null; + try { + r.setFilter(PathFilterGroup.createFromStrings(Collections + .singleton(path))); + r.setRecursive(r.getFilter().shouldBeRecursive()); + r.reset(trees); + return r.next() ? r : null; + } finally { + r.release(); + } } /** @@ -151,12 +155,10 @@ public class TreeWalk { return forPath(db, path, new ObjectId[] { tree }); } - private final Repository db; + private final ObjectReader reader; private final MutableObjectId idBuffer = new MutableObjectId(); - private final WindowCursor curs = new WindowCursor(); - private TreeFilter filter; AbstractTreeIterator[] trees; @@ -180,18 +182,34 @@ public class TreeWalk { * the repository the walker will obtain data from. */ public TreeWalk(final Repository repo) { - db = repo; + this(repo.newObjectReader()); + } + + /** + * Create a new tree walker for a given repository. + * + * @param or + * the reader the walker will obtain tree data from. + */ + public TreeWalk(final ObjectReader or) { + reader = or; filter = TreeFilter.ALL; trees = new AbstractTreeIterator[] { new EmptyTreeIterator() }; } + /** @return the reader this walker is using to load objects. */ + public ObjectReader getObjectReader() { + return reader; + } + /** - * Get the repository this tree walker is reading from. - * - * @return the repository configured when the walker was created. + * Release any resources used by this walker's reader. + * <p> + * A walker that has been released can be used again, but may need to be + * released after the subsequent usage. */ - public Repository getRepository() { - return db; + public void release() { + reader.release(); } /** @@ -319,7 +337,7 @@ public class TreeWalk { if (o instanceof CanonicalTreeParser) { o.matches = null; o.matchShift = 0; - ((CanonicalTreeParser) o).reset(db, id, curs); + ((CanonicalTreeParser) o).reset(reader, id); trees[0] = o; } else { trees[0] = parserFor(id); @@ -366,7 +384,7 @@ public class TreeWalk { if (o instanceof CanonicalTreeParser && o.pathOffset == 0) { o.matches = null; o.matchShift = 0; - ((CanonicalTreeParser) o).reset(db, ids[i], curs); + ((CanonicalTreeParser) o).reset(reader, ids[i]); r[i] = o; continue; } @@ -836,7 +854,7 @@ public class TreeWalk { final AbstractTreeIterator t = trees[i]; final AbstractTreeIterator n; if (t.matches == ch && !t.eof() && FileMode.TREE.equals(t.mode)) - n = t.createSubtreeIterator(db, idBuffer, curs); + n = t.createSubtreeIterator(reader, idBuffer); else n = t.createEmptyTreeIterator(); tmp[i] = n; @@ -911,7 +929,7 @@ public class TreeWalk { private CanonicalTreeParser parserFor(final AnyObjectId id) throws IncorrectObjectTypeException, IOException { final CanonicalTreeParser p = new CanonicalTreeParser(); - p.reset(db, id, curs); + p.reset(reader, id); return p; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java index 6c146f79f0..2ffcbed9f1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java @@ -42,13 +42,12 @@ */ package org.eclipse.jgit.util; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.regex.Pattern; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectWriter; import org.eclipse.jgit.lib.PersonIdent; /** @@ -113,12 +112,8 @@ public class ChangeIdUtil { b.append(committer.toExternalString()); b.append("\n\n"); b.append(cleanMessage); - ObjectWriter w = new ObjectWriter(null); - byte[] bytes = b.toString().getBytes(Constants.CHARACTER_ENCODING); - ByteArrayInputStream is = new ByteArrayInputStream(bytes); - ObjectId sha1 = w.computeObjectSha1(Constants.OBJ_COMMIT, bytes.length, - is); - return sha1; + return new ObjectInserter.Formatter().idFor(Constants.OBJ_COMMIT, // + b.toString().getBytes(Constants.CHARACTER_ENCODING)); } private static final Pattern issuePattern = Pattern diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java index 26608bb2a1..96b311dfbf 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java @@ -83,6 +83,20 @@ public class LongList { return entries[i]; } + /** + * Determine if an entry appears in this collection. + * + * @param value + * the value to search for. + * @return true of {@code value} appears in this list. + */ + public boolean contains(final long value) { + for (int i = 0; i < count; i++) + if (entries[i] == value) + return true; + return false; + } + /** Empty this list */ public void clear() { count = 0; 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. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java index 6c421c5f50..baa45c5c61 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java @@ -126,7 +126,7 @@ public abstract class TemporaryBuffer extends OutputStream { blocks.add(s); } - final int n = Math.min(Block.SZ - s.count, len); + final int n = Math.min(s.buffer.length - s.count, len); System.arraycopy(b, off, s.buffer, s.count, n); s.count += n; len -= n; @@ -139,6 +139,19 @@ public abstract class TemporaryBuffer extends OutputStream { } /** + * Dumps the entire buffer into the overflow stream, and flushes it. + * + * @throws IOException + * the overflow stream cannot be started, or the buffer contents + * cannot be written to it, or it failed to flush. + */ + protected void doFlush() throws IOException { + if (overflow == null) + switchToOverflow(); + overflow.flush(); + } + + /** * Copy all bytes remaining on the input stream into this buffer. * * @param in @@ -158,7 +171,7 @@ public abstract class TemporaryBuffer extends OutputStream { blocks.add(s); } - final int n = in.read(s.buffer, s.count, Block.SZ - s.count); + int n = in.read(s.buffer, s.count, s.buffer.length - s.count); if (n < 1) return; s.count += n; @@ -179,8 +192,12 @@ public abstract class TemporaryBuffer extends OutputStream { * @return total length of the buffer, in bytes. */ public long length() { + return inCoreLength(); + } + + private long inCoreLength() { final Block last = last(); - return ((long) blocks.size()) * Block.SZ - (Block.SZ - last.count); + return ((long) blocks.size() - 1) * Block.SZ + last.count; } /** @@ -238,8 +255,13 @@ public abstract class TemporaryBuffer extends OutputStream { if (overflow != null) { destroy(); } - blocks = new ArrayList<Block>(inCoreLimit / Block.SZ); - blocks.add(new Block()); + if (inCoreLimit < Block.SZ) { + blocks = new ArrayList<Block>(1); + blocks.add(new Block(inCoreLimit)); + } else { + blocks = new ArrayList<Block>(inCoreLimit / Block.SZ); + blocks.add(new Block()); + } } /** @@ -257,9 +279,14 @@ public abstract class TemporaryBuffer extends OutputStream { } private boolean reachedInCoreLimit() throws IOException { - if (blocks.size() * Block.SZ < inCoreLimit) + if (inCoreLength() < inCoreLimit) return false; + switchToOverflow(); + return true; + } + + private void switchToOverflow() throws IOException { overflow = overflow(); final Block last = blocks.remove(blocks.size() - 1); @@ -269,7 +296,6 @@ public abstract class TemporaryBuffer extends OutputStream { overflow = new BufferedOutputStream(overflow, Block.SZ); overflow.write(last.buffer, 0, last.count); - return true; } public void close() throws IOException { @@ -427,12 +453,20 @@ public abstract class TemporaryBuffer extends OutputStream { static class Block { static final int SZ = 8 * 1024; - final byte[] buffer = new byte[SZ]; + final byte[] buffer; int count; + Block() { + buffer = new byte[SZ]; + } + + Block(int sz) { + buffer = new byte[sz]; + } + boolean isFull() { - return count == SZ; + return count == buffer.length; } } } |