diff options
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; } } } |