From dc10dd6fc85e1c6c69c74ff64db8f4c8c7c4c50b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 23 Jun 2010 10:07:44 -0700 Subject: [PATCH 001/103] Remove test of the unsupported core.legacyHeaders variable Long ago we stopped supporting the core.legacyHeaders variable, as JGit (like C Git) stopped creating the new pack-style loose objects, rendering this variable pointless. The test is still valid, it proves we write the standard loose object format for a commit, but the variable assignment has no impact on the test so drop it from the code. Change-Id: I051336ada23033c05e86bbff73ae5d78a37b1640 Signed-off-by: Shawn O. Pearce --- .../tst/org/eclipse/jgit/lib/T0003_Basic.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java index ce8a79ef9..80d14ced0 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java @@ -345,11 +345,7 @@ public void test008_FailOnWrongVersion() throws IOException { } } - 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"); From 8e396bcddc2c945a2a90f6842c1a481a16c7be52 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 23 Jun 2010 10:09:27 -0700 Subject: [PATCH 002/103] Use higher level Config types when possible We don't have to assume/depend on RepositoryConfig here, these two tests can use higher level versions of the class and still come up with the same test. That frees us up to do some changes to the RepositoryConfig API. Change-Id: Ia7b263c8c5efa3fae1054416d39c546867288132 Signed-off-by: Shawn O. Pearce --- .../tst/org/eclipse/jgit/lib/T0003_Basic.java | 4 ++-- .../tst/org/eclipse/jgit/transport/TransportTest.java | 4 ++-- org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java index 80d14ced0..d96857498 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java @@ -283,7 +283,7 @@ public void test003_WriteShouldBeEmptyTree() throws IOException { } 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 +294,8 @@ public void test005_ReadSimpleConfig() { 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" 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 e3518251f..a6bdd8886 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.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/src/org/eclipse/jgit/lib/GitIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java index 5da33fd6b..42c16cb10 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java @@ -352,7 +352,7 @@ private boolean config_filemode() { // 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(); } From e1b312b5f7e1010c3633df2582f6a20373becdf3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 23 Jun 2010 10:11:34 -0700 Subject: [PATCH 003/103] Use CoreConfig, UserConfig and TransferConfig directly Rather than relying on the helpers in RepositoryConfig to get these objects, obtain them directly through the Config API. Its only slightly more verbose, but permits us to work with the base Config class, which is more flexible than the highly file specific RepositoryConfig. This is what I really meant to do when I added the section parser and caching support to Config, we just failed to finish updating all of the call sites. Change-Id: I481cb365aa00bfa8c21e5ad0cd367ddd9c6c0edd Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/pgm/IndexPack.java | 4 +++- .../tst/org/eclipse/jgit/lib/ReflogConfigTest.java | 4 ++-- org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java | 6 ++++-- org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java | 2 +- org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java | 2 +- .../src/org/eclipse/jgit/transport/IndexPack.java | 4 +++- .../src/org/eclipse/jgit/transport/Transport.java | 2 +- 7 files changed, 15 insertions(+), 9 deletions(-) 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 35fd2a597..640c8ef34 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 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.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java index 88bcf7671..d78892b89 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 void testlogAllRefUpdates() throws Exception { // 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 void testlogAllRefUpdates() throws Exception { // 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/src/org/eclipse/jgit/lib/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java index 48f41a558..75cc4662e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java @@ -238,8 +238,10 @@ public PackWriter(final Repository repo, final ProgressMonitor imonitor, 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(); + + final CoreConfig coreConfig = db.getConfig().get(CoreConfig.KEY); + this.deflater = new Deflater(coreConfig.getCompression()); + outputVersion = coreConfig.getPackIndexVersion(); } /** 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 522f8477b..0406684ea 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/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java index 302b63b48..e306baabb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java @@ -590,7 +590,7 @@ else if (log.isFile()) } private boolean isLogAllRefUpdates() { - return parent.getConfig().getCore().isLogAllRefUpdates(); + return parent.getConfig().get(CoreConfig.KEY).isLogAllRefUpdates(); } private boolean shouldAutoCreateLog(final String refName) { 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 2a5b4344f..f6e04107f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java @@ -67,6 +67,7 @@ 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; @@ -127,7 +128,8 @@ public static IndexPack create(final Repository db, final InputStream is) 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; } 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 e1988a6c8..a8e47afd3 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 @@ private static String findTrackingRefName(final String remoteName, * 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(); From 599c0ce745ab0322fc110a374bacf3c5a142da7b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 14 Jun 2010 18:51:09 -0700 Subject: [PATCH 004/103] Use RevTag/RevCommit to sort in a PlotWalk We already have these objects parsed and cached in our object pool. We shouldn't be looking them up via the legacy mapObject API, but instead can use the pool and the faster parsing routines available through the RevWalk that we extend. While we are here fixing the code, lets also correct the tag date sorting to accept tags that have no tagger identity, because they were created before Git knew how to store that field. Change-Id: Id49a11f6d9c050c82b876e5e11058840c894b2d7 Signed-off-by: Shawn O. Pearce --- .../org/eclipse/jgit/revplot/PlotWalk.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) 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 6b4ed80e1..476592f5d 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 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 @@ protected Ref[] getTags(final AnyObjectId commitId) { class PlotRefComparator implements Comparator { 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 int compare(Ref o1, Ref o2) { 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; } } From 47c07e1a0dec79cb87fc5ccd6ee9b5e64992efb5 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 23 Jun 2010 17:26:44 -0700 Subject: [PATCH 005/103] Replace manual peel loops with RevWalk.peel Instead of peeling things by hand in application level code, defer the peeling logic into RevWalk's new peel utility method. Change-Id: Idabd10dc41502e782f6a2eeb56f09566b97775a8 Signed-off-by: Shawn O. Pearce --- .../org/eclipse/jgit/lib/RefDirectory.java | 6 +--- .../src/org/eclipse/jgit/revwalk/RevWalk.java | 35 +++++++++++++------ .../eclipse/jgit/transport/ReceivePack.java | 4 +-- .../eclipse/jgit/transport/UploadPack.java | 13 ++++--- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java index e306baabb..b6fd10d01 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java @@ -409,12 +409,8 @@ public Ref peel(final Ref ref) throws IOException { 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()); + .getName(), leaf.getObjectId(), rw.peel(obj).copy()); } else { newLeaf = new ObjectIdRef.PeeledNonTag(leaf.getStorage(), leaf .getName(), leaf.getObjectId()); 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 94e11752c..e42554811 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java @@ -659,11 +659,7 @@ public RevObject lookupAny(final AnyObjectId id, final int type) { 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); @@ -690,11 +686,7 @@ public RevCommit parseCommit(final AnyObjectId id) 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) @@ -802,6 +794,29 @@ public void parseBody(final RevObject obj) obj.parseBody(this); } + /** + * 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. *

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 e42b7fe0c..91105cc8a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java @@ -84,7 +84,6 @@ 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.transport.ReceiveCommand.Result; @@ -818,8 +817,7 @@ private void checkConnectivity() throws IOException { 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/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java index 77cc1a6f0..712ad3ff7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -56,6 +56,7 @@ 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; @@ -393,11 +394,15 @@ private void recvWants() throws IOException { } 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 +411,7 @@ private void want(RevObject o) { 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); } From b6ba9739d5e10e18ecff13095204d71e95837392 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 23 Jun 2010 17:26:43 -0700 Subject: [PATCH 006/103] Rewrite resolve in terms of RevWalk We want to eventually get rid of the mapCommit, mapTree APIs on Repository and force everyone into the faster parsers that exist in RevWalk. Rewriting resolve in terms of the faster parsers is a good first step. It actually simplifies the code a bit, as we no longer need to keep track of an ObjectId and an Object (the parsed form), since all RevObjects implicitly have their ObjectId readily available. Change-Id: I4d234630195616e2c263e7e70038b55a1be4e7a3 Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/lib/Repository.java | 202 +++++++----------- 1 file changed, 83 insertions(+), 119 deletions(-) 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 350d11f43..e0f0a6c89 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -67,6 +67,10 @@ import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.RevisionSyntaxException; +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.util.FS; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; @@ -745,36 +749,37 @@ public RefRename renameRef(final String fromRef, final String toRef) throws IOEx * * Currently supported is combinations of these. *

    - *
  • SHA-1 - a SHA-1
  • - *
  • refs/... - a ref name
  • - *
  • ref^n - nth parent reference
  • - *
  • ref~n - distance via parent reference
  • - *
  • ref@{n} - nth version of ref
  • - *
  • ref^{tree} - tree references by ref
  • - *
  • ref^{commit} - commit references by ref
  • + *
  • SHA-1 - a SHA-1
  • + *
  • refs/... - a ref name
  • + *
  • ref^n - nth parent reference
  • + *
  • ref~n - distance via parent reference
  • + *
  • ref@{n} - nth version of ref
  • + *
  • ref^{tree} - tree references by ref
  • + *
  • ref^{commit} - commit references by ref
  • *
* - * Not supported is + * Not supported is: *
    *
  • timestamps in reflogs, ref@{full or relative timestamp}
  • *
  • abbreviated SHA-1's
  • *
* - * @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 { char[] rev = revstr.toCharArray(); - Object ref = null; - ObjectId refId = null; + RevObject ref = null; + RevWalk rw = new RevWalk(this); 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) { @@ -790,19 +795,12 @@ public ObjectId resolve(final String revstr) throws IOException { 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 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 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; @@ -951,30 +910,35 @@ else if (item.equals("")) { case '@': int m; String time = null; - for (m=i+2; m Date: Mon, 14 Jun 2010 12:49:41 -0700 Subject: [PATCH 007/103] UploadPack: Permit flushing progress messages under smart HTTP If UploadPack invokes flush() on the output stream we pass it, its most likely the progress messages coming down the side band stream. As pack generation can take a while, we want to push that down at the client as early as we can, to keep the connection alive, and to let the user know we are still working on their behalf. Ensure we dump the temporary buffer whenever flush() is invoked, otherwise the messages don't get sent in a timely fashion to the user agent (in this case, git fetch). We specifically don't implement flush() for ReceivePack right now, as that protocol currently does not provide progress messages to the user, but it does invoke flush several times, as the different streams include '0000' type flush-pkts to denote various end points. Change-Id: I797c90a2c562a416223dc0704785f61ac64e0220 Signed-off-by: Shawn O. Pearce --- .../jgit/http/server/UploadPackServlet.java | 7 ++++++- .../eclipse/jgit/util/TemporaryBuffer.java | 19 ++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) 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 92d41a0ca..6d0d64fc6 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 @@ -107,7 +107,12 @@ public void doPost(final HttpServletRequest req, 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/src/org/eclipse/jgit/util/TemporaryBuffer.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java index 6c421c5f5..101b6056b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java @@ -138,6 +138,19 @@ public void write(final byte[] b, int off, int len) throws IOException { overflow.write(b, off, len); } + /** + * 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. * @@ -260,6 +273,11 @@ private boolean reachedInCoreLimit() throws IOException { if (blocks.size() * Block.SZ < inCoreLimit) return false; + switchToOverflow(); + return true; + } + + private void switchToOverflow() throws IOException { overflow = overflow(); final Block last = blocks.remove(blocks.size() - 1); @@ -269,7 +287,6 @@ private boolean reachedInCoreLimit() throws IOException { overflow = new BufferedOutputStream(overflow, Block.SZ); overflow.write(last.buffer, 0, last.count); - return true; } public void close() throws IOException { From 60aae90d4d15d6b35d19941867dad966ddcbe161 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 14 Jun 2010 18:31:14 -0700 Subject: [PATCH 008/103] Disable topological sorting in PackWriter Its not strictly required that we sort topologically in order to produce a valid pack file. This was just something that Linus thought would be a good idea to do. In practice its not that important for most repositories. Local file IO quickly falls out of the pattern that topological sorting provides any sort of benefit for, so expending extra resources to enforce it when we make a pack isn't really worth it. I'm removing this sort in the pipeline because later changes would support really efficient COMMIT_TIME_DESC sorting on a non-file storage system, but TOPO sorting would be a bit more ugly to run, due to the in-memory delays it imposes. Change-Id: I0121453461c2140c6917cb10c6df584eb47e5795 Signed-off-by: Shawn O. Pearce --- org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java index 75cc4662e..e8e5fc21e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java @@ -821,8 +821,7 @@ private ObjectWalk setUpWalker( IncorrectObjectTypeException { final ObjectWalk walker = new ObjectWalk(db); walker.setRetainBody(false); - walker.sort(RevSort.TOPO); - walker.sort(RevSort.COMMIT_TIME_DESC, true); + walker.sort(RevSort.COMMIT_TIME_DESC); if (thin) walker.sort(RevSort.BOUNDARY, true); From 553c2e5a42061d216637aca882b6c216e21aaf1a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 25 Jun 2010 17:32:12 -0700 Subject: [PATCH 009/103] DirCache must use getIndexFile When reading or locking the index of a repository, we need to use the index file specified by the repository, to ensure we correctly honor what the repository was configured with. Change-Id: I5be366ce32d7923b888dc01d19335912b01b7c4c Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/dircache/DirCache.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 42fea4852..99d5d2eda 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java @@ -168,6 +168,8 @@ public static DirCache read(final File indexLocation) * 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 IllegalStateException + * if the repository is bare (lacks a working directory). * @throws IOException * the index file is present but could not be read. * @throws CorruptObjectException @@ -176,7 +178,7 @@ public static DirCache read(final File indexLocation) */ public static DirCache read(final Repository db) throws CorruptObjectException, IOException { - return read(new File(db.getDirectory(), "index")); + return read(db.getIndexFile()); } /** @@ -231,6 +233,8 @@ public static DirCache lock(final File indexLocation) * 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 IllegalStateException + * if the repository is bare (lacks a working directory). * @throws IOException * the index file is present but could not be read, or the lock * could not be obtained. @@ -240,7 +244,7 @@ public static DirCache lock(final File indexLocation) */ public static DirCache lock(final Repository db) throws CorruptObjectException, IOException { - return lock(new File(db.getDirectory(), "index")); + return lock(db.getIndexFile()); } /** Location of the current version of the index file. */ From 2370ad9514587a354e3a8767d7b10e83587ab9cf Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Jun 2010 11:39:20 -0700 Subject: [PATCH 010/103] Use FileKey to resolve Git repository arguments upload-pack and receive-pack take a git repository as an argument, but its a lenient path format. Locate the repository and open it. Change-Id: I4b377e57b28ba3b1717c13d9ab51a602de1ad257 Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/pgm/ReceivePack.java | 18 ++++++++----- .../src/org/eclipse/jgit/pgm/UploadPack.java | 26 +++++++++++-------- 2 files changed, 26 insertions(+), 18 deletions(-) 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 09a9f2b58..7a2761722 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 @@ 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 @@ protected final boolean requiresRepository() { 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/UploadPack.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/UploadPack.java index 52d2488f7..d4e2bcec7 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 @@ 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 @@ protected final boolean requiresRepository() { @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); } } From f39c9fc7415c9b74c2c4fc12978d865b03cc330d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Jun 2010 11:30:19 -0700 Subject: [PATCH 011/103] Download pack-*.idx to /tmp if not on local filesystem If the destination repository doesn't use an ObjectDirectory to store its objects, we can't download to the object directory. Instead pull the pack-*.idx files down to temporary files in the JVM's default temporary directory. Change-Id: Ied16bc89be624d87110ba42ba52d698a6ea7d982 Signed-off-by: Shawn O. Pearce --- .../jgit/transport/WalkFetchConnection.java | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) 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 625474547..0469c1ac0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java @@ -70,6 +70,7 @@ import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectChecker; +import org.eclipse.jgit.lib.ObjectDirectory; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PackIndex; import org.eclipse.jgit.lib.PackLock; @@ -240,8 +241,10 @@ public void setPackLockMessage(final String message) { @Override public void close() { - for (final RemotePack p : unfetchedPacks) - p.tmpIdx.delete(); + for (final RemotePack p : unfetchedPacks) { + if (p.tmpIdx != null) + p.tmpIdx.delete(); + } for (final WalkRemoteObjectDatabase r : remotes) r.close(); } @@ -512,7 +515,8 @@ private boolean downloadPackedObject(final ProgressMonitor monitor, // 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(); } @@ -788,12 +792,11 @@ private class RemotePack { 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 +806,19 @@ private class RemotePack { 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; From 479fcf9e32fd5a802c5a959d0e697f85a6eee369 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 25 Jun 2010 17:42:01 -0700 Subject: [PATCH 012/103] Refactor amazon-s3:// property file loading to support no directory In the future getDirectory() can return null. Avoid an NPE here by refactoring the code to support conditionally skipping a check for the properties file in the repository directory, falling to only the user's ~/ file location. Change-Id: I76f5503d4063fdd9d24b7c1b58e1b09ddf1a5670 Signed-off-by: Shawn O. Pearce --- .../jgit/transport/TransportAmazonS3.java | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) 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 56a5c9796..79b88b6a7 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 @@ static boolean canHandle(final URIish uri) { 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 @@ static boolean canHandle(final URIish uri) { 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"); From 530924471320f5e2a53e90c48821d9f09e91e071 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 18 Jun 2010 20:23:06 -0700 Subject: [PATCH 013/103] Move additional have enumeration to Repository This permits the repository implementation to know what its alternates concept means, and avoids needing to expose finer details about the ObjectDatabase to network code like the RefAdvertiser. Change-Id: Ic6d173f300cb72de34519c7607cf7b0ff3ea6882 Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/lib/Repository.java | 25 +++++++++++++++++++ .../eclipse/jgit/transport/RefAdvertiser.java | 19 +++----------- 2 files changed, 28 insertions(+), 16 deletions(-) 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 e0f0a6c89..d482c21fb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -1021,6 +1021,31 @@ public String getBranch() throws IOException { return name; } + /** + * Objects known to exist but not expressed by {@link #getAllRefs()}. + *

+ * When a repository borrows objects from another repository, it can + * advertise that it safely has that other repository's references, without + * exposing any other details about the other repository. This may help + * a client trying to push changes avoid pushing more than it needs to. + * + * @return unmodifiable collection of other known objects. + */ + public Set getAdditionalHaves() { + HashSet r = new HashSet(); + for (ObjectDatabase d : objectDatabase.getAlternates()) { + if (d instanceof AlternateRepositoryDatabase) { + Repository repo; + + repo = ((AlternateRepositoryDatabase) d).getRepository(); + for (Ref ref : repo.getAllRefs().values()) + r.add(ref.getObjectId()); + r.addAll(repo.getAdditionalHaves()); + } + } + return r; + } + /** * Get a ref by name. * 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 694a2e0f1..532cc11a7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java @@ -49,13 +49,11 @@ 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; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; @@ -217,19 +215,8 @@ public void advertiseHave(AnyObjectId id) throws IOException { * 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()); + for (ObjectId id : walk.getRepository().getAdditionalHaves()) + advertiseHave(id); } /** @return true if no advertisements have been sent yet. */ From f6c26dabd221d6820d0968a8bec556cf1bcc036f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Jun 2010 15:19:05 -0700 Subject: [PATCH 014/103] Cleanup Repository.create() This method doesn't need to be synchronized, as its only a proxy to create(boolean), which is the real worker. While we are touching it try to improve the Javadoc and whitespace nearby. Change-Id: Ibdddec6e518ca6d7439cfad90fedfcdc2d6b7a2e Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/lib/Repository.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 d482c21fb..375a2fc69 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -327,15 +327,16 @@ private void loadConfig() throws IOException { } } - /** - * 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. + *

+ * 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); } From bd8b06427fa4d4d804a886d6b74bae40613d4df1 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Jun 2010 15:18:14 -0700 Subject: [PATCH 015/103] Delegate repository access to refs, objects Instead of using the internal field directly to access references or objects, use the getter method to obtain the proper type of database, and follow down from there. This permits us to later do a refactoring that makes those methods abstract and strips the field out of the Repository class, moving it into a concrete base class that is more storage implementation specific. Change-Id: Ic21dd48800e68a04ce372965ad233485b2a84bef Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/lib/Repository.java | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) 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 375a2fc69..e605928da 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -453,8 +453,8 @@ public File toFile(final AnyObjectId 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); + public boolean hasObject(AnyObjectId objectId) { + return getObjectDatabase().hasObject(objectId); } /** @@ -485,9 +485,9 @@ public ObjectLoader openObject(final AnyObjectId id) * object, or null if the object does not exist. * @throws IOException */ - public ObjectLoader openObject(final WindowCursor curs, final AnyObjectId id) + public ObjectLoader openObject(WindowCursor curs, AnyObjectId id) throws IOException { - return objectDatabase.openObject(curs, id); + return getObjectDatabase().openObject(curs, id); } /** @@ -726,7 +726,7 @@ public RefUpdate updateRef(final String ref) throws IOException { * 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); } /** @@ -742,7 +742,7 @@ public RefUpdate updateRef(final String ref, final boolean detach) throws IOExce * */ public RefRename renameRef(final String fromRef, final String toRef) throws IOException { - return refs.newRename(fromRef, toRef); + return getRefDatabase().newRename(fromRef, toRef); } /** @@ -948,16 +948,23 @@ public void incrementOpen() { 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()}. + *

+ * 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. * @@ -1058,7 +1065,7 @@ public Set getAdditionalHaves() { * @throws IOException */ public Ref getRef(final String name) throws IOException { - return refs.getRef(name); + return getRefDatabase().getRef(name); } /** @@ -1066,7 +1073,7 @@ public Ref getRef(final String name) throws IOException { */ public Map getAllRefs() { try { - return refs.getRefs(RefDatabase.ALL); + return getRefDatabase().getRefs(RefDatabase.ALL); } catch (IOException e) { return new HashMap(); } @@ -1079,7 +1086,7 @@ public Map getAllRefs() { */ public Map getTags() { try { - return refs.getRefs(Constants.R_TAGS); + return getRefDatabase().getRefs(Constants.R_TAGS); } catch (IOException e) { return new HashMap(); } @@ -1100,7 +1107,7 @@ public Map getTags() { */ 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 From 6a822f0ebfd8b37bffbb680fa05b8783701ae15e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 23 Jun 2010 10:13:55 -0700 Subject: [PATCH 016/103] Remove RepositoryConfig and use FileBasedConfig instead Change the Repository API to use straight-up FileBasedConfig. This lets us remove the subclass RepositoryConfig and stop having a specialized configuration type for repository, letting us instead focus the config type heirarchy on type-of-storage rather than use. Change-Id: I7236800e8090624453a89cb0c7a9a632702691c6 Signed-off-by: Shawn O. Pearce --- .../jgit/http/test/AdvertiseErrorTest.java | 4 +- .../jgit/http/test/HookMessageTest.java | 4 +- .../http/test/SmartClientSmartServerTest.java | 4 +- .../org/eclipse/jgit/lib/ObjectWriter.java | 2 +- .../src/org/eclipse/jgit/lib/Repository.java | 8 +- .../eclipse/jgit/lib/RepositoryConfig.java | 135 ------------------ 6 files changed, 11 insertions(+), 146 deletions(-) delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryConfig.java 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 47d7806a1..d8268bf93 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java @@ -59,10 +59,10 @@ import org.eclipse.jgit.http.test.util.HttpTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileBasedConfig; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.RepositoryConfig; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.ReceivePack; @@ -114,7 +114,7 @@ public ReceivePack create(HttpServletRequest req, Repository db) 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 224ea05c1..b98171ebd 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java @@ -61,10 +61,10 @@ import org.eclipse.jgit.http.test.util.HttpTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileBasedConfig; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.RepositoryConfig; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.PreReceiveHook; @@ -124,7 +124,7 @@ public void onPreReceive(ReceivePack rp, remoteRepository = src.getRepository(); remoteURI = toURIish(app, srcName); - RepositoryConfig cfg = remoteRepository.getConfig(); + FileBasedConfig cfg = remoteRepository.getConfig(); cfg.setBoolean("http", null, "receivepack", true); cfg.save(); } diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java index f7b3bdb20..74dd8af8e 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java @@ -76,12 +76,12 @@ import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRng; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileBasedConfig; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.ReflogReader; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.RepositoryConfig; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.FetchConnection; @@ -547,7 +547,7 @@ public void testPush_ChunkedEncoding() throws Exception { } 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/src/org/eclipse/jgit/lib/ObjectWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java index 20147ed6c..3ba67476e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java @@ -336,7 +336,7 @@ ObjectId writeObject(final int type, long len, final InputStream is, md.reset(); if (store) { - def = new Deflater(r.getConfig().getCore().getCompression()); + def = new Deflater(r.getConfig().get(CoreConfig.KEY).getCompression()); deflateStream = new DeflaterOutputStream(fileStream, def); } else deflateStream = null; 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 e605928da..78fe6065a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -110,7 +110,7 @@ public class Repository { private final FileBasedConfig userConfig; - private final RepositoryConfig config; + private final FileBasedConfig config; private final RefDatabase refs; @@ -244,7 +244,7 @@ public Repository(final File d, final File workTree, final File objectDir, this.fs = fs; userConfig = SystemReader.getInstance().openUserConfig(fs); - config = new RepositoryConfig(userConfig, fs.resolve(gitDir, "config")); + config = new FileBasedConfig(userConfig, fs.resolve(gitDir, "config")); loadUserConfig(); loadConfig(); @@ -351,7 +351,7 @@ public void create() throws IOException { * in case of IO problem */ public void create(boolean bare) throws IOException { - final RepositoryConfig cfg = getConfig(); + final FileBasedConfig cfg = getConfig(); if (cfg.getFile().exists()) { throw new IllegalStateException(MessageFormat.format( JGitText.get().repositoryAlreadyExists, gitDir)); @@ -409,7 +409,7 @@ public RefDatabase getRefDatabase() { /** * @return the configuration of this repository */ - public RepositoryConfig getConfig() { + public FileBasedConfig getConfig() { if (userConfig.isOutdated()) { try { loadUserConfig(); 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 805975a8d..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryConfig.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2009, Constantine Plotnikov - * Copyright (C) 2007, Dave Watson - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2009, JetBrains s.r.o. - * Copyright (C) 2007-2008, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * Copyright (C) 2008, Thad Hughes - * Copyright (C) 2009, Yann Simon - * 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(); - } -} From c9c57d34debe1a522c715d416e68e3d1a5b18a1a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Jun 2010 18:18:46 -0700 Subject: [PATCH 017/103] Rename Repository 'config' as 'repoConfig' This better matches with the other configuration variable, 'userConfig', and helps to make it clear what config object we are dealing with. Change-Id: I2c585649aecc805e8e66db2f094828cd2649e549 Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/lib/Repository.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 78fe6065a..4a4969841 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -110,7 +110,7 @@ public class Repository { private final FileBasedConfig userConfig; - private final FileBasedConfig config; + private final FileBasedConfig repoConfig; private final RefDatabase refs; @@ -244,10 +244,10 @@ public Repository(final File d, final File workTree, final File objectDir, this.fs = fs; userConfig = SystemReader.getInstance().openUserConfig(fs); - config = new FileBasedConfig(userConfig, fs.resolve(gitDir, "config")); + repoConfig = new FileBasedConfig(userConfig, fs.resolve(gitDir, "config")); loadUserConfig(); - loadConfig(); + loadRepoConfig(); if (workDir == null) { // if the working directory was not provided explicitly, @@ -317,9 +317,9 @@ private void loadUserConfig() throws IOException { } } - private void loadConfig() throws IOException { + private void loadRepoConfig() throws IOException { try { - config.load(); + repoConfig.load(); } catch (ConfigInvalidException e1) { IOException e2 = new IOException(JGitText.get().unknownRepositoryFormat); e2.initCause(e1); @@ -417,14 +417,14 @@ public FileBasedConfig getConfig() { throw new RuntimeException(e); } } - if (config.isOutdated()) { + if (repoConfig.isOutdated()) { try { - loadConfig(); + loadRepoConfig(); } catch (IOException e) { throw new RuntimeException(e); } } - return config; + return repoConfig; } /** From a63494edeead4670ac2d838d346ed0fcdfa2b181 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Jun 2010 12:57:02 -0700 Subject: [PATCH 018/103] Add RepositoryState.BARE A bare repository cannot be checked out, committed to, etc. as it doesn't have a working directory. Define this as a state since the state enumeration exists only to describe how a working directory can be modified. Change-Id: I0a299013c6e42fef6cae3f6a9446f8f6c8e0514a Signed-off-by: Shawn O. Pearce --- org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java | 3 +++ .../src/org/eclipse/jgit/lib/RepositoryState.java | 8 ++++++++ 2 files changed, 11 insertions(+) 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 4a4969841..0ee0ca9d3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -1188,6 +1188,9 @@ static byte[] gitInternalSlash(byte[] bytes) { * @return an important state */ public RepositoryState getRepositoryState() { + if (isBare()) + return RepositoryState.BARE; + // Pre Git-1.6 logic if (new File(getWorkDir(), ".dotest").exists()) return RepositoryState.REBASING; 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 901d1b516..862b7ea0c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java @@ -54,6 +54,14 @@ * 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 * */ From f18b853044b57f1ffd1a3698f05b19d51e9b9c1b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Jun 2010 18:41:21 -0700 Subject: [PATCH 019/103] Consistently use getDirectory() for work tree state This permits us to leave the implementation of these methods here in the Repository class, but later refactor how the directory is accessed into a subclass. Change-Id: I5785b2009c5b7cca0fb070a968e50814ce847076 Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/lib/Repository.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 0ee0ca9d3..433423489 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -1194,7 +1194,7 @@ public RepositoryState getRepositoryState() { // Pre Git-1.6 logic if (new File(getWorkDir(), ".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 @@ -1211,7 +1211,7 @@ public RepositoryState getRepositoryState() { 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()) { @@ -1227,7 +1227,7 @@ public RepositoryState getRepositoryState() { return RepositoryState.MERGING; } - if (new File(gitDir,"BISECT_LOG").exists()) + if (new File(getDirectory(), "BISECT_LOG").exists()) return RepositoryState.BISECTING; return RepositoryState.SAFE; @@ -1455,7 +1455,7 @@ public ReflogReader getReflogReader(String refName) throws IOException { * @throws IOException */ public String readMergeCommitMsg() throws IOException { - File mergeMsgFile = new File(gitDir, Constants.MERGE_MSG); + File mergeMsgFile = new File(getDirectory(), Constants.MERGE_MSG); try { return new String(IO.readFully(mergeMsgFile)); } catch (FileNotFoundException e) { @@ -1476,7 +1476,7 @@ public String readMergeCommitMsg() throws IOException { * @throws IOException */ public List readMergeHeads() throws IOException { - File mergeHeadFile = new File(gitDir, Constants.MERGE_HEAD); + File mergeHeadFile = new File(getDirectory(), Constants.MERGE_HEAD); byte[] raw; try { raw = IO.readFully(mergeHeadFile); From 77b39df5ecdcff4d0ef68e7441526426499914e3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Jun 2010 18:45:57 -0700 Subject: [PATCH 020/103] Consistently fail work tree methods on bare repositories If the working tree isn't available, it doesn't make any sense to obtain the merge heads, or the buffered commit message. The repository shouldn't have a partial merge state to read. Throw back the same exception we do when invoking getWorkDir() on a bare repository instance. Change-Id: I762c55890b7fe272a183da583f910671d1cadf71 Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/lib/Repository.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 433423489..b4af15a60 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -1453,8 +1453,14 @@ public ReflogReader getReflogReader(String refName) throws IOException { * @return a String containing the content of the MERGE_MSG file or * {@code null} if this file doesn't exist * @throws IOException + * @throws IllegalStateException + * if the repository is "bare" */ public String readMergeCommitMsg() throws IOException { + if (isBare()) + throw new IllegalStateException( + JGitText.get().bareRepositoryNoWorkdirAndIndex); + File mergeMsgFile = new File(getDirectory(), Constants.MERGE_MSG); try { return new String(IO.readFully(mergeMsgFile)); @@ -1474,8 +1480,14 @@ public String readMergeCommitMsg() throws IOException { * 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 IllegalStateException + * if the repository is "bare" */ public List readMergeHeads() throws IOException { + if (isBare()) + throw new IllegalStateException( + JGitText.get().bareRepositoryNoWorkdirAndIndex); + File mergeHeadFile = new File(getDirectory(), Constants.MERGE_HEAD); byte[] raw; try { From 4c14b7623dd2ff943350eb0f80d899b00450794f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Jun 2010 15:16:32 -0700 Subject: [PATCH 021/103] Make lib.Repository abstract and lib.FileRepository its implementation To support other storage models other than just the local filesystem, we split the Repository class into a nearly abstract interface and then create a concrete subclass called FileRepository with the file based IO implementation. We are using an abstract class for Repository rather than the much more generic interface, as implementers will want to inherit a large array of utility functions, such as resolve(String). Having these in a base class makes it easy to inherit them. This isn't the final home for lib.FileRepository. Future changes will rename it into storage.file.FileRepository, but to do that we need to also move a number of other related class, which we aren't quite ready to do. Change-Id: I1bd54ea0500337799a8e792874c272eb14d555f7 Signed-off-by: Shawn O. Pearce --- .../junit/LocalDiskRepositoryTestCase.java | 3 +- .../src/org/eclipse/jgit/pgm/Clone.java | 4 +- .../src/org/eclipse/jgit/pgm/Init.java | 4 +- .../src/org/eclipse/jgit/pgm/Main.java | 4 +- .../org/eclipse/jgit/lib/RefUpdateTest.java | 2 +- .../jgit/lib/RepositorySetupWorkDirTest.java | 30 +- .../tst/org/eclipse/jgit/lib/T0003_Basic.java | 30 +- .../org/eclipse/jgit/lib/FileRepository.java | 492 ++++++++++++++++++ .../org/eclipse/jgit/lib/RefDirectory.java | 4 +- .../src/org/eclipse/jgit/lib/Repository.java | 364 ++----------- .../org/eclipse/jgit/lib/RepositoryCache.java | 2 +- .../jgit/transport/TransportLocal.java | 5 +- 12 files changed, 569 insertions(+), 375 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java 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 001deb262..100b632ae 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 @@ -63,6 +63,7 @@ import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileBasedConfig; +import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryCache; @@ -288,7 +289,7 @@ private Repository 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); + Repository db = new FileRepository(gitdir); assertFalse(gitdir.exists()); db.create(); 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 b0f51ec58..260b8b866 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java @@ -57,11 +57,11 @@ import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.Commit; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.GitIndex; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefComparator; 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; @@ -103,7 +103,7 @@ protected void run() throws Exception { if (gitdir == null) gitdir = new File(localName, Constants.DOT_GIT); - db = new Repository(gitdir); + db = new FileRepository(gitdir); db.create(); db.getConfig().setBoolean("core", null, "bare", false); db.getConfig().save(); 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 d8c7bdfb4..b2a9fbde0 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 org.kohsuke.args4j.Option; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.FileRepository; @Command(common = true, usage = "usage_CreateAnEmptyGitRepository") class Init extends TextBuiltin { @@ -66,7 +66,7 @@ protected final boolean requiresRepository() { 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 306ac816d..b0da55a43 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 @@ -60,7 +60,7 @@ 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.FileRepository; import org.eclipse.jgit.pgm.opt.CmdLineParser; import org.eclipse.jgit.pgm.opt.SubcommandHandler; import org.eclipse.jgit.util.CachedAuthenticator; @@ -212,7 +212,7 @@ private void execute(final String[] argv) throws Exception { writer.flush(); System.exit(1); } - cmd.init(new Repository(gitdir, gitworktree, objectdir, altobjectdirs, indexfile), gitdir); + cmd.init(new FileRepository(gitdir, gitworktree, objectdir, altobjectdirs, indexfile), gitdir); } else { cmd.init(null, gitdir); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java index 8a9bb5263..161bd3eff 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java @@ -664,7 +664,7 @@ public void testRenameBranchAlsoInPack() throws IOException { // 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")); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java index 6e5e0054b..e3617305f 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java @@ -57,12 +57,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 +71,14 @@ public void testNotBare_CreateRepositoryFromDotGitGitDir() throws Exception { public void testWorkdirIsParentDir_CreateRepositoryFromDotGitGitDir() throws Exception { File gitDir = getFile("workdir", Constants.DOT_GIT); - Repository repo = new Repository(gitDir); + Repository repo = new FileRepository(gitDir); String workdir = repo.getWorkDir().getName(); assertEquals(workdir, "workdir"); } public void testNotBare_CreateRepositoryFromWorkDirOnly() throws Exception { File workdir = getFile("workdir", "repo"); - Repository repo = new Repository(null, workdir); + Repository repo = new FileRepository(null, workdir); assertFalse(repo.isBare()); assertWorkdirPath(repo, "workdir", "repo"); assertGitdirPath(repo, "workdir", "repo", Constants.DOT_GIT); @@ -87,7 +87,7 @@ public void testNotBare_CreateRepositoryFromWorkDirOnly() throws Exception { public void testWorkdirIsDotGit_CreateRepositoryFromWorkDirOnly() throws Exception { File workdir = getFile("workdir", "repo"); - Repository repo = new Repository(null, workdir); + Repository repo = new FileRepository(null, workdir); assertGitdirPath(repo, "workdir", "repo", Constants.DOT_GIT); } @@ -96,7 +96,7 @@ public void testNotBare_CreateRepositoryFromGitDirOnlyWithWorktreeConfig() File gitDir = getFile("workdir", "repoWithConfig"); File workTree = getFile("workdir", "treeRoot"); setWorkTree(gitDir, workTree); - Repository repo = new Repository(gitDir, null); + Repository repo = new FileRepository(gitDir, null); assertFalse(repo.isBare()); assertWorkdirPath(repo, "workdir", "treeRoot"); assertGitdirPath(repo, "workdir", "repoWithConfig"); @@ -106,7 +106,7 @@ public void testBare_CreateRepositoryFromGitDirOnlyWithBareConfigTrue() throws Exception { File gitDir = getFile("workdir", "repoWithConfig"); setBare(gitDir, true); - Repository repo = new Repository(gitDir, null); + Repository repo = new FileRepository(gitDir, null); assertTrue(repo.isBare()); } @@ -114,7 +114,7 @@ public void testWorkdirIsParent_CreateRepositoryFromGitDirOnlyWithBareConfigFals throws Exception { File gitDir = getFile("workdir", "repoWithBareConfigTrue", "child"); setBare(gitDir, false); - Repository repo = new Repository(gitDir, null); + Repository repo = new FileRepository(gitDir, null); assertWorkdirPath(repo, "workdir", "repoWithBareConfigTrue"); } @@ -122,7 +122,7 @@ public void testNotBare_CreateRepositoryFromGitDirOnlyWithBareConfigFalse() throws Exception { File gitDir = getFile("workdir", "repoWithBareConfigFalse", "child"); setBare(gitDir, false); - Repository repo = new Repository(gitDir, null); + Repository repo = new FileRepository(gitDir, null); assertFalse(repo.isBare()); assertWorkdirPath(repo, "workdir", "repoWithBareConfigFalse"); assertGitdirPath(repo, "workdir", "repoWithBareConfigFalse", "child"); @@ -130,7 +130,7 @@ public void testNotBare_CreateRepositoryFromGitDirOnlyWithBareConfigFalse() public void testNotBare_MakeBareUnbareBySetWorkdir() throws Exception { File gitDir = getFile("gitDir"); - Repository repo = new Repository(gitDir); + Repository repo = new FileRepository(gitDir); repo.setWorkDir(getFile("workingDir")); assertFalse(repo.isBare()); assertWorkdirPath(repo, "workingDir"); @@ -140,7 +140,7 @@ public void testNotBare_MakeBareUnbareBySetWorkdir() throws Exception { public void testExceptionThrown_BareRepoGetWorkDir() throws Exception { File gitDir = getFile("workdir"); try { - new Repository(gitDir).getWorkDir(); + new FileRepository(gitDir).getWorkDir(); fail("Expected IllegalStateException missing"); } catch (IllegalStateException e) { // expected @@ -150,7 +150,7 @@ public void testExceptionThrown_BareRepoGetWorkDir() throws Exception { public void testExceptionThrown_BareRepoGetIndex() throws Exception { File gitDir = getFile("workdir"); try { - new Repository(gitDir).getIndex(); + new FileRepository(gitDir).getIndex(); fail("Expected IllegalStateException missing"); } catch (IllegalStateException e) { // expected @@ -160,7 +160,7 @@ public void testExceptionThrown_BareRepoGetIndex() throws Exception { public void testExceptionThrown_BareRepoGetIndexFile() throws Exception { File gitDir = getFile("workdir"); try { - new Repository(gitDir).getIndexFile(); + new FileRepository(gitDir).getIndexFile(); fail("Expected Exception missing"); } catch (IllegalStateException e) { // expected @@ -177,14 +177,14 @@ private File getFile(String... pathComponents) { } private void setBare(File gitDir, boolean bare) throws IOException { - Repository repo = new Repository(gitDir, null); + Repository repo = new FileRepository(gitDir, null); repo.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_BARE, bare); repo.getConfig().save(); } private void setWorkTree(File gitDir, File workTree) throws IOException { - Repository repo = new Repository(gitDir, null); + Repository repo = new FileRepository(gitDir, null); repo.getConfig() .setString(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_WORKTREE, diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java index d96857498..b44b9038e 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java @@ -80,7 +80,7 @@ public void test001_Initalize() { public void test000_openRepoBadArgs() throws IOException { try { - new Repository(null, null); + new FileRepository(null, null); fail("Must pass either GIT_DIR or GIT_WORK_TREE"); } catch (IllegalArgumentException e) { assertEquals( @@ -97,12 +97,12 @@ public void test000_openRepoBadArgs() throws IOException { */ 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); + Repository r = new FileRepository(theDir, null); assertEqualsPath(theDir, r.getDirectory()); assertEqualsPath(repo1Parent, r.getWorkDir()); assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); @@ -117,12 +117,12 @@ public void test000_openrepo_default_gitDirSet() throws IOException { */ 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()); + Repository r = new FileRepository(theDir, repo1Parent.getParentFile()); assertEqualsPath(theDir, r.getDirectory()); assertEqualsPath(repo1Parent.getParentFile(), r.getWorkDir()); assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); @@ -137,12 +137,12 @@ public void test000_openrepo_default_gitDirAndWorkTreeSet() throws IOException { */ 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); + Repository r = new FileRepository(null, repo1Parent); assertEqualsPath(theDir, r.getDirectory()); assertEqualsPath(repo1Parent, r.getWorkDir()); assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); @@ -159,7 +159,7 @@ public void test000_openrepo_default_absolute_workdirconfig() 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)); + Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); repo1initial.create(); repo1initial.getConfig().setString("core", null, "worktree", workdir.getAbsolutePath()); @@ -167,7 +167,7 @@ public void test000_openrepo_default_absolute_workdirconfig() repo1initial.close(); File theDir = new File(repo1Parent, Constants.DOT_GIT); - Repository r = new Repository(theDir, null); + Repository r = new FileRepository(theDir, null); assertEqualsPath(theDir, r.getDirectory()); assertEqualsPath(workdir, r.getWorkDir()); assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); @@ -184,7 +184,7 @@ public void test000_openrepo_default_relative_workdirconfig() 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)); + Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); repo1initial.create(); repo1initial.getConfig() .setString("core", null, "worktree", "../../rw"); @@ -192,7 +192,7 @@ public void test000_openrepo_default_relative_workdirconfig() repo1initial.close(); File theDir = new File(repo1Parent, Constants.DOT_GIT); - Repository r = new Repository(theDir, null); + Repository r = new FileRepository(theDir, null); assertEqualsPath(theDir, r.getDirectory()); assertEqualsPath(workdir, r.getWorkDir()); assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); @@ -211,12 +211,12 @@ public void test000_openrepo_alternate_index_file_and_objdirs() 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)); + 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, + Repository r = new FileRepository(theDir, null, objDir, altObjDirs, indexFile); assertEqualsPath(theDir, r.getDirectory()); assertEqualsPath(theDir.getParentFile(), r.getWorkDir()); @@ -321,7 +321,7 @@ public void test006_ReadUglyConfig() throws IOException, } public void test007_Open() throws IOException { - final Repository db2 = new Repository(db.getDirectory()); + final Repository db2 = new FileRepository(db.getDirectory()); assertEquals(db.getDirectory(), db2.getDirectory()); assertEquals(db.getObjectsDirectory(), db2.getObjectsDirectory()); assertNotSame(db.getConfig(), db2.getConfig()); @@ -337,7 +337,7 @@ public void test008_FailOnWrongVersion() throws IOException { 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); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java new file mode 100644 index 000000000..f5b1bf848 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java @@ -0,0 +1,492 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2008-2010, Google Inc. + * Copyright (C) 2006-2010, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.SystemReader; + +/** + * Represents a Git repository. A repository holds all objects and refs used for + * managing source code (could by any type of file, but source code is what + * SCM's are typically used for). + * + * In Git terms all data is stored in GIT_DIR, typically a directory called + * .git. A work tree is maintained unless the repository is a bare repository. + * Typically the .git directory is located at the root of the work dir. + * + *

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

+ * This class is thread-safe. + *

+ * This implementation only handles a subtly undocumented subset of git features. + * + */ +public class FileRepository extends Repository { + private final FileBasedConfig userConfig; + + private final FileBasedConfig repoConfig; + + private final RefDatabase refs; + + private final ObjectDirectory objectDatabase; + + /** + * Construct a representation of a Git repository. + * + * The work tree, object directory, alternate object directories and index + * file locations are deduced from the given git directory and the default + * rules. + * + * @param d + * GIT_DIR (the location of the repository metadata). + * @throws IOException + * the repository appears to already exist but cannot be + * accessed. + */ + public FileRepository(final File d) throws IOException { + this(d, null, null, null, null); // go figure it out + } + + /** + * 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 FileRepository(final File d, final File workTree) throws IOException { + this(d, workTree, null, null, null); // go figure it out + } + + /** + * 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 FileRepository(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. + * + * @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. + */ + public FileRepository(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); + repoConfig = new FileBasedConfig(userConfig, fs.resolve(gitDir, "config")); + + loadUserConfig(); + loadRepoConfig(); + + 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)); + } + } + } + + 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, 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(); + } + + /** + * @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; + } + + /** + * 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); + } + + /** + * Open object in all packs containing specified object. + * + * @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 + * @throws IOException + */ + public Collection openObjectInAllPacks( + final AnyObjectId objectId, final WindowCursor curs) + throws IOException { + Collection result = new LinkedList(); + openObjectInAllPacks(objectId, result, curs); + return result; + } + + /** + * Open object in all packs containing specified object. + * + * @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 resultLoaders, + final WindowCursor curs) throws IOException { + objectDatabase.openObjectInAllPacks(resultLoaders, curs, objectId); + } + + /** + * Objects known to exist but not expressed by {@link #getAllRefs()}. + *

+ * When a repository borrows objects from another repository, it can + * advertise that it safely has that other repository's references, without + * exposing any other details about the other repository. This may help + * a client trying to push changes avoid pushing more than it needs to. + * + * @return unmodifiable collection of other known objects. + */ + public Set getAdditionalHaves() { + HashSet r = new HashSet(); + for (ObjectDatabase d : getObjectDatabase().getAlternates()) { + if (d instanceof AlternateRepositoryDatabase) { + Repository repo; + + repo = ((AlternateRepositoryDatabase) d).getRepository(); + 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/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java index b6fd10d01..0cbcf2b5c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java @@ -109,7 +109,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 +150,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(); 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 b4af15a60..bd8fc1583 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -49,7 +49,6 @@ 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; @@ -64,7 +63,6 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.dircache.DirCache; -import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.RevisionSyntaxException; import org.eclipse.jgit.revwalk.RevBlob; @@ -74,257 +72,38 @@ 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. - * - *

    - *
  • GIT_DIR - *
      - *
    • objects/ - objects
    • - *
    • refs/ - tags and heads
    • - *
    • config - configuration
    • - *
    • info/ - more configurations
    • - *
    - *
  • - *
+ * Represents a Git repository. + *

+ * 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). *

* This class is thread-safe. - *

- * This implementation only handles a subtly undocumented subset of git features. - * */ -public class Repository { +public abstract class Repository { private final AtomicInteger useCnt = new AtomicInteger(1); - private final File gitDir; + /** Metadata directory holding the repository's critical files. */ + protected File gitDir; - private final FS fs; - - private final FileBasedConfig userConfig; - - private final FileBasedConfig repoConfig; - - private final RefDatabase refs; - - private final ObjectDirectory objectDatabase; + /** File abstraction used to resolve paths. */ + protected FS fs; private GitIndex index; private final List listeners = new Vector(); // thread safe static private final List allListeners = new Vector(); // thread safe - private File workDir; + /** If not bare, the top level directory of the working files. */ + protected File workDir; - private File indexFile; + /** If not bare, the index file caching the working file states. */ + protected 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. - * - * @param d - * GIT_DIR (the location of the repository metadata). - * @throws IOException - * the repository appears to already exist but cannot be - * accessed. - */ - public Repository(final File d) throws IOException { - this(d, null, null, null, null); // go figure it out - } - - /** - * 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 - } - - /** - * 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. - * - * @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. - */ - 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); - repoConfig = new FileBasedConfig(userConfig, fs.resolve(gitDir, "config")); - - loadUserConfig(); - loadRepoConfig(); - - 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)); - } - } - } - - 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; - } + /** Initialize a new repository instance. */ + protected Repository() { + // Empty constructor, defined protected to require subclassing. } /** @@ -350,35 +129,7 @@ public void create() throws IOException { * @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, 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 @@ -390,42 +141,20 @@ public File getDirectory() { /** * @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 the reference database which stores the reference namespace. */ - public RefDatabase getRefDatabase() { - return refs; - } + public abstract RefDatabase getRefDatabase(); /** * @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; - } + public abstract FileBasedConfig getConfig(); /** * @return the used file system abstraction @@ -444,9 +173,7 @@ public FS getFS() { * @param objectId * @return suggested file name */ - public File toFile(final AnyObjectId objectId) { - return objectDatabase.fileFor(objectId); - } + public abstract File toFile(AnyObjectId objectId); /** * @param objectId @@ -501,13 +228,9 @@ public ObjectLoader openObject(WindowCursor curs, AnyObjectId id) * this object * @throws IOException */ - public Collection openObjectInAllPacks( - final AnyObjectId objectId, final WindowCursor curs) - throws IOException { - Collection result = new LinkedList(); - openObjectInAllPacks(objectId, result, curs); - return result; - } + public abstract Collection openObjectInAllPacks( + AnyObjectId objectId, WindowCursor curs) + throws IOException; /** * Open object in all packs containing specified object. @@ -521,11 +244,9 @@ public Collection openObjectInAllPacks( * temporary working space associated with the calling thread. * @throws IOException */ - void openObjectInAllPacks(final AnyObjectId objectId, + abstract void openObjectInAllPacks(final AnyObjectId objectId, final Collection resultLoaders, - final WindowCursor curs) throws IOException { - objectDatabase.openObjectInAllPacks(resultLoaders, curs, objectId); - } + final WindowCursor curs) throws IOException; /** * @param id @@ -976,9 +697,7 @@ protected void doClose() { * 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() + "]"; @@ -1040,18 +759,7 @@ public String getBranch() throws IOException { * @return unmodifiable collection of other known objects. */ public Set getAdditionalHaves() { - HashSet r = new HashSet(); - for (ObjectDatabase d : objectDatabase.getAlternates()) { - if (d instanceof AlternateRepositoryDatabase) { - Repository repo; - - repo = ((AlternateRepositoryDatabase) d).getRepository(); - for (Ref ref : repo.getAllRefs().values()) - r.add(ref.getObjectId()); - r.addAll(repo.getAdditionalHaves()); - } - } - return r; + return Collections.emptySet(); } /** @@ -1411,11 +1119,7 @@ void fireIndexChanged() { * * @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 @@ -1438,12 +1142,8 @@ public String shortenRefName(String refName) { * 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 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 0b0260a4c..2bea8fecf 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java @@ -313,7 +313,7 @@ public final File getFile() { 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/transport/TransportLocal.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java index 08fd8901d..ae54a155a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java @@ -60,6 +60,7 @@ import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.io.MessageWriter; @@ -174,7 +175,7 @@ class InternalLocalFetchConnection extends BasePackFetchConnection { 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 InternalLocalPushConnection extends BasePackPushConnection { final Repository dst; try { - dst = new Repository(remoteGitDir); + dst = new FileRepository(remoteGitDir); } catch (IOException err) { throw new TransportException(uri, JGitText.get().notAGitDirectory); } From 89d4a7377fc578524dd87289afc367287fd9e653 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Jun 2010 15:45:51 -0700 Subject: [PATCH 022/103] Use FileRepository where we assume other file semantics When the surrounding code is already heavily based upon the assumption that we have a FileRepository (e.g. because it created that type of repository) keep the type around and use it directly. This permits us to continue to do things like save the configuration file. Change-Id: Ib783f0f6a11acd6aa305c16d61ccc368b46beecc Signed-off-by: Shawn O. Pearce --- .../jgit/http/test/AdvertiseErrorTest.java | 5 +- .../jgit/http/test/HookMessageTest.java | 5 +- .../jgit/http/test/HttpClientTests.java | 17 +-- .../http/test/SmartClientSmartServerTest.java | 9 +- .../jgit/http/test/util/HttpTestCase.java | 6 +- .../junit/LocalDiskRepositoryTestCase.java | 8 +- .../eclipse/jgit/junit/TestRepository.java | 100 ++++++++++-------- .../src/org/eclipse/jgit/pgm/Clone.java | 17 +-- .../jgit/lib/RepositorySetupWorkDirTest.java | 4 +- .../eclipse/jgit/lib/RepositoryTestCase.java | 2 +- .../tst/org/eclipse/jgit/lib/T0003_Basic.java | 6 +- .../eclipse/jgit/revwalk/RevWalkTestCase.java | 5 +- .../transport/ReceivePackRefFilterTest.java | 6 +- 13 files changed, 104 insertions(+), 86 deletions(-) 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 d8268bf93..0f6299cd5 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java @@ -60,6 +60,7 @@ import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileBasedConfig; +import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; @@ -71,14 +72,14 @@ import org.eclipse.jgit.transport.URIish; public class AdvertiseErrorTest extends HttpTestCase { - private Repository remoteRepository; + private FileRepository remoteRepository; private URIish remoteURI; protected void setUp() throws Exception { super.setUp(); - final TestRepository src = createTestRepository(); + final TestRepository src = createTestRepository(); final String srcName = src.getRepository().getDirectory().getName(); ServletContextHandler app = server.addContext("/git"); diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java index b98171ebd..a2292e6c0 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java @@ -62,6 +62,7 @@ import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileBasedConfig; +import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; @@ -76,14 +77,14 @@ import org.eclipse.jgit.transport.URIish; public class HookMessageTest extends HttpTestCase { - private Repository remoteRepository; + private FileRepository remoteRepository; private URIish remoteURI; protected void setUp() throws Exception { super.setUp(); - final TestRepository src = createTestRepository(); + final TestRepository src = createTestRepository(); final String srcName = src.getRepository().getDirectory().getName(); ServletContextHandler app = server.addContext("/git"); diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java index 729466df3..daf169eb1 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java @@ -62,6 +62,7 @@ import org.eclipse.jgit.http.test.util.HttpTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; @@ -71,7 +72,7 @@ import org.eclipse.jgit.transport.URIish; public class HttpClientTests extends HttpTestCase { - private TestRepository remoteRepository; + private TestRepository remoteRepository; private URIish dumbAuthNoneURI; @@ -95,7 +96,7 @@ protected void setUp() throws Exception { 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 @@ private ServletContextHandler smart(final String path) { 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 Repository open(HttpServletRequest req, String name) 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 void testListRemote_Dumb_DetachedHEAD() throws Exception { } 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 void testListRemote_Smart_UploadPackNeedsAuth() throws Exception { } 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 74dd8af8e..867af2bf6 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java @@ -77,6 +77,7 @@ import org.eclipse.jgit.junit.TestRng; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileBasedConfig; +import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; @@ -94,7 +95,7 @@ public class SmartClientSmartServerTest extends HttpTestCase { private static final String HDR_TRANSFER_ENCODING = "Transfer-Encoding"; - private Repository remoteRepository; + private FileRepository remoteRepository; private URIish remoteURI; @@ -107,7 +108,7 @@ public class SmartClientSmartServerTest extends HttpTestCase { protected void setUp() throws Exception { super.setUp(); - final TestRepository src = createTestRepository(); + final TestRepository src = createTestRepository(); final String srcName = src.getRepository().getDirectory().getName(); ServletContextHandler app = server.addContext("/git"); @@ -489,10 +490,10 @@ public void testPush_CreateBranch() throws Exception { } public void testPush_ChunkedEncoding() throws Exception { - final TestRepository src = createTestRepository(); + final TestRepository src = createTestRepository(); final RevBlob Q_bin = src.blob(new TestRng("Q").nextBytes(128 * 1024)); final RevCommit Q = src.commit().add("Q", Q_bin).create(); - final Repository db = src.getRepository(); + final FileRepository db = src.getRepository(); final String dstName = Constants.R_HEADS + "new.branch"; Transport t; diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java index e25975761..a25ca5678 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java @@ -57,6 +57,7 @@ import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; @@ -82,8 +83,9 @@ protected void tearDown() throws Exception { super.tearDown(); } - protected TestRepository createTestRepository() throws Exception { - return new TestRepository(createBareRepository()); + protected TestRepository createTestRepository() + throws IOException { + return new TestRepository(createBareRepository()); } protected URIish toURIish(String path) throws URISyntaxException { diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java index 100b632ae..6920e9188 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java @@ -260,7 +260,7 @@ private static void reportDeleteFailure(final String testName, * @throws IOException * the repository could not be created in the temporary area */ - protected Repository createBareRepository() throws IOException { + protected FileRepository createBareRepository() throws IOException { return createRepository(true /* bare */); } @@ -271,7 +271,7 @@ protected Repository createBareRepository() throws IOException { * @throws IOException * the repository could not be created in the temporary area */ - protected Repository createWorkRepository() throws IOException { + protected FileRepository createWorkRepository() throws IOException { return createRepository(false /* not bare */); } @@ -285,11 +285,11 @@ protected Repository createWorkRepository() throws IOException { * @throws IOException * the repository could not be created in the temporary area */ - private Repository createRepository(boolean bare) throws IOException { + private FileRepository createRepository(boolean bare) throws IOException { String uniqueId = System.currentTimeMillis() + "_" + (testCount++); String gitdirName = "test" + uniqueId + (bare ? "" : "/") + Constants.DOT_GIT; File gitdir = new File(trash, gitdirName).getCanonicalFile(); - Repository db = new FileRepository(gitdir); + FileRepository db = new FileRepository(gitdir); assertFalse(gitdir.exists()); db.create(); diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java index 59504aa78..279762ac6 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java @@ -73,10 +73,10 @@ import org.eclipse.jgit.lib.Commit; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.LockFile; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectChecker; -import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectDirectory; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectWriter; @@ -99,8 +99,13 @@ import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; -/** Wrapper to make creating test data easier. */ -public class TestRepository { +/** + * Wrapper to make creating test data easier. + * + * @param + * type of Repository the test data is stored on. + */ +public class TestRepository { private static final PersonIdent author; private static final PersonIdent committer; @@ -119,7 +124,7 @@ public class TestRepository { committer = new PersonIdent(cn, ce, now, tz); } - private final Repository db; + private final R db; private final RevWalk pool; @@ -132,9 +137,9 @@ public class TestRepository { * * @param db * the test repository to write into. - * @throws Exception + * @throws IOException */ - public TestRepository(Repository db) throws Exception { + public TestRepository(R db) throws IOException { this(db, new RevWalk(db)); } @@ -145,9 +150,9 @@ public TestRepository(Repository db) throws Exception { * the test repository to write into. * @param rw * the RevObject pool to use for object lookup. - * @throws Exception + * @throws IOException */ - public TestRepository(Repository db, RevWalk rw) throws Exception { + public TestRepository(R db, RevWalk rw) throws IOException { this.db = db; this.pool = rw; this.writer = new ObjectWriter(db); @@ -155,7 +160,7 @@ public TestRepository(Repository db, RevWalk rw) throws Exception { } /** @return the repository this helper class operates against. */ - public Repository getRepository() { + public R getRepository() { return db; } @@ -443,25 +448,27 @@ public T update(String ref, T obj) throws Exception { * @throws Exception */ public void updateServerInfo() throws Exception { - final ObjectDatabase odb = db.getObjectDatabase(); - if (odb instanceof ObjectDirectory) { - RefWriter rw = new RefWriter(db.getAllRefs().values()) { + if (db instanceof FileRepository) { + final FileRepository fr = (FileRepository) db; + RefWriter rw = new RefWriter(fr.getAllRefs().values()) { @Override protected void writeFile(final String name, final byte[] bin) throws IOException { - TestRepository.this.writeFile(name, bin); + File path = new File(fr.getDirectory(), name); + TestRepository.this.writeFile(path, bin); } }; rw.writePackedRefs(); rw.writeInfoRefs(); final StringBuilder w = new StringBuilder(); - for (PackFile p : ((ObjectDirectory) odb).getPacks()) { + for (PackFile p : fr.getObjectDatabase().getPacks()) { w.append("P "); w.append(p.getPackFile().getName()); w.append('\n'); } - writeFile("objects/info/packs", Constants.encodeASCII(w.toString())); + writeFile(new File(new File(fr.getObjectDatabase().getDirectory(), + "info"), "packs"), Constants.encodeASCII(w.toString())); } } @@ -563,38 +570,40 @@ private static void assertHash(RevObject id, byte[] bin) { * @throws Exception */ public void packAndPrune() throws Exception { - final ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase(); - final PackWriter pw = new PackWriter(db, NullProgressMonitor.INSTANCE); + if (db.getObjectDatabase() instanceof ObjectDirectory) { + ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase(); + PackWriter pw = new PackWriter(db, NullProgressMonitor.INSTANCE); - Set all = new HashSet(); - for (Ref r : db.getAllRefs().values()) - all.add(r.getObjectId()); - pw.preparePack(all, Collections. emptySet()); + Set all = new HashSet(); + for (Ref r : db.getAllRefs().values()) + all.add(r.getObjectId()); + pw.preparePack(all, Collections. emptySet()); - final ObjectId name = pw.computeName(); - OutputStream out; + final 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(); + final File pack = nameFor(odb, name, ".pack"); + out = new BufferedOutputStream(new FileOutputStream(pack)); + try { + pw.writePack(out); + } finally { + out.close(); + } + pack.setReadOnly(); + + final File idx = nameFor(odb, name, ".idx"); + out = new BufferedOutputStream(new FileOutputStream(idx)); + try { + pw.writeIndex(out); + } finally { + out.close(); + } + idx.setReadOnly(); + + odb.openPack(pack, idx); + updateServerInfo(); + prunePacked(odb); } - pack.setReadOnly(); - - final File idx = nameFor(odb, name, ".idx"); - out = new BufferedOutputStream(new FileOutputStream(idx)); - try { - pw.writeIndex(out); - } finally { - out.close(); - } - idx.setReadOnly(); - - odb.openPack(pack, idx); - updateServerInfo(); - prunePacked(odb); } private void prunePacked(ObjectDirectory odb) { @@ -609,9 +618,8 @@ private static File nameFor(ObjectDirectory odb, ObjectId name, String t) { return new File(packdir, "pack-" + name.name() + t); } - private void writeFile(final String name, final byte[] bin) - throws IOException, ObjectWritingException { - final File p = new File(db.getDirectory(), name); + private void writeFile(final File p, final byte[] bin) throws IOException, + ObjectWritingException { final LockFile lck = new LockFile(p); if (!lck.lock()) throw new ObjectWritingException("Can't write " + p); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java index 260b8b866..60663438f 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java @@ -82,6 +82,8 @@ class Clone extends AbstractFetchCommand { @Argument(index = 1, metaVar = "metaVar_directory") private String localName; + private FileRepository dst; + @Override protected final boolean requiresRepository() { return false; @@ -103,10 +105,11 @@ protected void run() throws Exception { if (gitdir == null) gitdir = new File(localName, Constants.DOT_GIT); - db = new FileRepository(gitdir); - db.create(); - db.getConfig().setBoolean("core", null, "bare", false); - db.getConfig().save(); + dst = new FileRepository(gitdir); + dst.create(); + dst.getConfig().setBoolean("core", null, "bare", false); + dst.getConfig().save(); + db = dst; out.format(CLIText.get().initializedEmptyGitRepositoryIn, gitdir.getAbsolutePath()); out.println(); @@ -120,13 +123,13 @@ protected void run() throws Exception { private void saveRemote(final URIish uri) throws URISyntaxException, IOException { - final RemoteConfig rc = new RemoteConfig(db.getConfig(), remoteName); + final RemoteConfig rc = new RemoteConfig(dst.getConfig(), remoteName); rc.addURI(uri); rc.addFetchRefSpec(new RefSpec().setForceUpdate(true) .setSourceDestination(Constants.R_HEADS + "*", Constants.R_REMOTES + remoteName + "/*")); - rc.update(db.getConfig()); - db.getConfig().save(); + rc.update(dst.getConfig()); + dst.getConfig().save(); } private FetchResult runFetch() throws NotSupportedException, diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java index e3617305f..070713d7b 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java @@ -177,14 +177,14 @@ private File getFile(String... pathComponents) { } private void setBare(File gitDir, boolean bare) throws IOException { - Repository repo = new FileRepository(gitDir, null); + FileRepository repo = new FileRepository(gitDir, null); repo.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_BARE, bare); repo.getConfig().save(); } private void setWorkTree(File gitDir, File workTree) throws IOException { - Repository repo = new FileRepository(gitDir, null); + FileRepository repo = new FileRepository(gitDir, null); repo.getConfig() .setString(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_WORKTREE, diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java index c5c6d998b..64df65b4c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java @@ -102,7 +102,7 @@ protected static void checkFile(File f, final String checkData) } /** Test repository, initialized for this test case. */ - protected Repository db; + protected FileRepository db; /** Working directory of {@link #db}. */ protected File trash; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java index b44b9038e..4e87fd624 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java @@ -159,7 +159,7 @@ public void test000_openrepo_default_absolute_workdirconfig() File repo1Parent = new File(trash.getParentFile(), "r1"); File workdir = new File(trash.getParentFile(), "rw"); workdir.mkdir(); - Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); + FileRepository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); repo1initial.create(); repo1initial.getConfig().setString("core", null, "worktree", workdir.getAbsolutePath()); @@ -184,7 +184,7 @@ public void test000_openrepo_default_relative_workdirconfig() File repo1Parent = new File(trash.getParentFile(), "r1"); File workdir = new File(trash.getParentFile(), "rw"); workdir.mkdir(); - Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); + FileRepository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); repo1initial.create(); repo1initial.getConfig() .setString("core", null, "worktree", "../../rw"); @@ -321,7 +321,7 @@ public void test006_ReadUglyConfig() throws IOException, } public void test007_Open() throws IOException { - final Repository db2 = new FileRepository(db.getDirectory()); + final FileRepository db2 = new FileRepository(db.getDirectory()); assertEquals(db.getDirectory(), db2.getDirectory()); assertEquals(db.getObjectsDirectory(), db2.getObjectsDirectory()); assertNotSame(db.getConfig(), db2.getConfig()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java index 64052323f..9473fe631 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 org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryTestCase; /** Support for tests of the {@link RevWalk} class. */ public abstract class RevWalkTestCase extends RepositoryTestCase { - private TestRepository util; + private TestRepository util; protected RevWalk rw; @Override public void setUp() throws Exception { super.setUp(); - util = new TestRepository(db, createRevWalk()); + util = new TestRepository(db, createRevWalk()); rw = util.getRevWalk(); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java index 40c719f69..b87cc7a99 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackRefFilterTest.java @@ -292,7 +292,7 @@ public void testUsingHiddenDeltaBaseFails() throws Exception { public void testUsingHiddenCommonBlobFails() throws Exception { // Try to use the 'b' blob that is hidden. // - TestRepository s = new TestRepository(src); + TestRepository s = new TestRepository(src); RevCommit N = s.commit().parent(B).add("q", s.blob("b")).create(); // But don't include it in the pack. @@ -333,7 +333,7 @@ public void testUsingHiddenCommonBlobFails() throws Exception { public void testUsingUnknownBlobFails() throws Exception { // Try to use the 'n' blob that is not on the server. // - TestRepository s = new TestRepository(src); + TestRepository s = new TestRepository(src); RevBlob n = s.blob("n"); RevCommit N = s.commit().parent(B).add("q", n).create(); @@ -373,7 +373,7 @@ public void testUsingUnknownBlobFails() throws Exception { } public void testUsingUnknownTreeFails() throws Exception { - TestRepository s = new TestRepository(src); + TestRepository s = new TestRepository(src); RevCommit N = s.commit().parent(B).add("q", s.blob("a")).create(); RevTree t = s.parseBody(N).getTree(); From 3e3a50db5e9c396769c24778f7f496c79068412d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Jun 2010 15:46:07 -0700 Subject: [PATCH 023/103] Change Repository.getConfig() to return non-file Configs A repository implementation might support storing configurations on a non-file storage system, so widen the return value to be any type of configuration. Change-Id: If9a0928f4b3ef29a24d270b0ce585a6e77f6fac6 Signed-off-by: Shawn O. Pearce --- org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 bd8fc1583..201c7a3fd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -154,7 +154,7 @@ public File getDirectory() { /** * @return the configuration of this repository */ - public abstract FileBasedConfig getConfig(); + public abstract Config getConfig(); /** * @return the used file system abstraction From cad10e6640258fd6bc6bc3183e4dbc61e83bf544 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 14 Jun 2010 15:48:53 -0700 Subject: [PATCH 024/103] Refactor object writing responsiblities to ObjectDatabase The ObjectInserter API permits ObjectDatabase implementations to control their own object insertion behavior, rather than forcing it to always be a new loose file created in the local filesystem. Inserted objects can also be queued and written asynchronously to the main application, such as by appending into a pack file that is later closed and added to the repository. This change also starts to open the door to non-file based object storage, such as an in-memory HashMap for unit testing, or a more complex system built on top of a distributed hash table. To help existing application code port to the newer interface we are keeping ObjectWriter as a delegation wrapper to the new API. Each ObjectWriter instances holds a reference to an ObjectInserter for the Repository's top-level ObjectDatabase, and it flushes and releases that instance on each object processed. Change-Id: I413224fb95563e7330c82748deb0aada4e0d6ace Signed-off-by: Shawn O. Pearce --- .../org/eclipse/jgit/lib/ReadTreeTest.java | 6 +- .../jgit/lib/AlternateRepositoryDatabase.java | 5 + .../jgit/lib/CachedObjectDatabase.java | 5 + .../src/org/eclipse/jgit/lib/CoreConfig.java | 1 - .../org/eclipse/jgit/lib/FileRepository.java | 17 +- .../org/eclipse/jgit/lib/ObjectDatabase.java | 11 + .../org/eclipse/jgit/lib/ObjectDirectory.java | 14 +- .../jgit/lib/ObjectDirectoryInserter.java | 177 ++++++++ .../org/eclipse/jgit/lib/ObjectInserter.java | 395 ++++++++++++++++++ .../org/eclipse/jgit/lib/ObjectWriter.java | 331 ++++----------- .../src/org/eclipse/jgit/lib/Repository.java | 5 + .../org/eclipse/jgit/util/ChangeIdUtil.java | 11 +- 12 files changed, 695 insertions(+), 283 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java 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 feef66f9a..e2a356466 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 @@ -46,10 +46,8 @@ package org.eclipse.jgit.lib; -import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; @@ -125,11 +123,9 @@ private Tree buildTree(HashMap headEntries) throws IOException { } ObjectId genSha1(String data) { - InputStream is = new ByteArrayInputStream(data.getBytes()); ObjectWriter objectWriter = new ObjectWriter(db); try { - return objectWriter.writeObject(Constants.OBJ_BLOB, data - .getBytes().length, is, true); + return objectWriter.writeBlob(data.getBytes()); } catch (IOException e) { fail(e.toString()); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java index 40f110684..64b1254cc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java @@ -85,6 +85,11 @@ public void create() throws IOException { repository.create(); } + @Override + public ObjectInserter newInserter() { + return odb.newInserter(); + } + @Override public boolean exists() { return odb.exists(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java index 3dcea1636..f593bfca4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java @@ -129,4 +129,9 @@ public ObjectDatabase newCachedDatabase() { // The situation might become even more tricky if we will consider alternates. return wrapped.newCachedDatabase(); } + + @Override + public ObjectInserter newInserter() { + return wrapped.newInserter(); + } } 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 9080ec139..ab024c790 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java @@ -74,7 +74,6 @@ private CoreConfig(final Config rc) { } /** - * @see ObjectWriter * @return The compression level to use when storing loose objects */ public int getCompression() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java index f5b1bf848..0ad558b94 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java @@ -249,12 +249,17 @@ public FileRepository(final File d, final File workTree, final File objectDir, } 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 (objectDir != null) { + objectDatabase = new ObjectDirectory(repoConfig, // + fs.resolve(objectDir, ""), // + alternateObjectDir, // + fs); + } else { + objectDatabase = new ObjectDirectory(repoConfig, // + fs.resolve(gitDir, "objects"), // + alternateObjectDir, // + fs); + } if (indexFile != null) this.indexFile = indexFile; 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 7eac79fb7..df52ae02f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java @@ -91,6 +91,17 @@ public void create() throws IOException { // Assume no action is required. } + /** + * Create a new {@code ObjectInserter} to insert new objects. + *

+ * 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 abstract ObjectInserter newInserter(); + /** * Close any resources held by this database and its active alternates. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java index 9a5bcdb68..ac3c7bf27 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java @@ -76,6 +76,8 @@ public class ObjectDirectory extends ObjectDatabase { 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; @@ -93,6 +95,8 @@ public class ObjectDirectory extends ObjectDatabase { /** * Initialize a reference to an on-disk object directory. * + * @param cfg + * configuration this directory consults for write settings. * @param dir * the location of the objects directory. * @param alternateObjectDir @@ -101,7 +105,8 @@ public class ObjectDirectory extends ObjectDatabase { * the file system abstraction which will be necessary to * perform certain file system operations. */ - public ObjectDirectory(final File dir, File[] alternateObjectDir, FS fs) { + public ObjectDirectory(final Config cfg, final File dir, File[] alternateObjectDir, FS fs) { + config = cfg; objects = dir; this.alternateObjectDir = alternateObjectDir; infoDirectory = new File(objects, "info"); @@ -130,6 +135,11 @@ public void create() throws IOException { packDirectory.mkdir(); } + @Override + public ObjectInserter newInserter() { + return new ObjectDirectoryInserter(this, config); + } + @Override public void closeSelf() { final PackList packs = packList.get(); @@ -501,7 +511,7 @@ private ObjectDatabase openAlternate(File objdir) throws IOException { final Repository db = RepositoryCache.open(FileKey.exact(parent, fs)); return new AlternateRepositoryDatabase(db); } - return new ObjectDirectory(objdir, null, fs); + return new ObjectDirectory(config, objdir, null, fs); } private static final class PackList { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java new file mode 100644 index 000000000..146d2d625 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * Copyright (C) 2009, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import java.io.EOFException; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.DigestOutputStream; +import java.security.MessageDigest; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; + +import org.eclipse.jgit.errors.ObjectWritingException; + +/** Creates loose objects in a {@link ObjectDirectory}. */ +class ObjectDirectoryInserter extends ObjectInserter { + private final ObjectDirectory db; + + private final Config config; + + private Deflater deflate; + + ObjectDirectoryInserter(final ObjectDirectory dest, final Config cfg) { + db = dest; + config = cfg; + } + + @Override + public ObjectId insert(final int type, long len, final InputStream is) + throws IOException { + final MessageDigest md = digest(); + final File tmp = toTemp(md, type, len, is); + final ObjectId id = ObjectId.fromRaw(md.digest()); + if (db.hasObject(id)) { + // Object is already in the repository, remove temporary file. + // + tmp.delete(); + return id; + } + + final File dst = db.fileFor(id); + if (tmp.renameTo(dst)) + return id; + + // Maybe the directory doesn't exist yet as the object + // directories are always lazily created. Note that we + // try the rename first as the directory likely does exist. + // + dst.getParentFile().mkdir(); + if (tmp.renameTo(dst)) + return id; + + if (db.hasObject(id)) { + tmp.delete(); + return id; + } + + // The object failed to be renamed into its proper + // location and it doesn't exist in the repository + // either. We really don't know what went wrong, so + // fail. + // + tmp.delete(); + throw new ObjectWritingException("Unable to create new object: " + dst); + } + + @Override + public void flush() throws IOException { + // Do nothing. Objects are immediately visible. + } + + @Override + public void release() { + if (deflate != null) { + try { + deflate.end(); + } finally { + deflate = null; + } + } + } + + private File toTemp(final MessageDigest md, final int type, long len, + final InputStream is) throws IOException, FileNotFoundException, + Error { + boolean delete = true; + File tmp = File.createTempFile("noz", null, db.getDirectory()); + try { + DigestOutputStream dOut = new DigestOutputStream( + compress(new FileOutputStream(tmp)), md); + try { + dOut.write(Constants.encodedTypeString(type)); + dOut.write((byte) ' '); + dOut.write(Constants.encodeASCII(len)); + dOut.write((byte) 0); + + final byte[] buf = buffer(); + while (len > 0) { + int n = is.read(buf, 0, (int) Math.min(len, buf.length)); + if (n <= 0) + throw shortInput(len); + dOut.write(buf, 0, n); + len -= n; + } + } finally { + dOut.close(); + } + + tmp.setReadOnly(); + delete = false; + return tmp; + } finally { + if (delete) + tmp.delete(); + } + } + + private DeflaterOutputStream compress(final OutputStream out) { + if (deflate == null) + deflate = new Deflater(config.get(CoreConfig.KEY).getCompression()); + else + deflate.reset(); + return new DeflaterOutputStream(out, deflate); + } + + private static EOFException shortInput(long missing) { + return new EOFException("Input did not match supplied length. " + + missing + " bytes are missing."); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java new file mode 100644 index 000000000..fd99d39e7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * Copyright (C) 2009, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import java.io.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}. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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/ObjectWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java index 3ba67476e..ce91efb8e 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 * Copyright (C) 2006-2008, Shawn O. Pearce + * Copyright (C) 2009, Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -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 ObjectId writeBlob(final File f) throws IOException { */ 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 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/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index 201c7a3fd..334581415 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -148,6 +148,11 @@ public File getDirectory() { */ 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 abstract RefDatabase getRefDatabase(); 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 6c146f79f..2ffcbed9f 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 static ObjectId computeChangeId(final ObjectId treeId, 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 From 88530a179e2ddfa81de5cc441a27d66521334608 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 14 Jun 2010 16:53:13 -0700 Subject: [PATCH 025/103] Start using ObjectInserter instead of ObjectWriter Some newer style APIs are updated to use the newer ObjectInserter interface instead of the now deprecated ObjectWriter. In many of the unit tests we don't bother to release the inserter, these are typically using the file backend which doesn't need a release, but in the future should use an in-memory HashMap based store, which really wouldn't need it either. Change-Id: I91a15e1dc42da68e6715397814e30fbd87fa2e73 Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/junit/TestRepository.java | 54 +++++++++-- .../org/eclipse/jgit/lib/ReadTreeTest.java | 8 +- .../eclipse/jgit/merge/CherryPickTest.java | 20 ++-- .../eclipse/jgit/merge/SimpleMergeTest.java | 33 ++++--- .../filter/PathSuffixFilterTestCase.java | 26 +++--- .../org/eclipse/jgit/api/CommitCommand.java | 93 ++++++++++--------- .../org/eclipse/jgit/dircache/DirCache.java | 8 +- .../eclipse/jgit/dircache/DirCacheTree.java | 8 +- .../src/org/eclipse/jgit/lib/Commit.java | 9 +- .../src/org/eclipse/jgit/lib/Tag.java | 11 ++- .../src/org/eclipse/jgit/merge/Merger.java | 12 +-- .../merge/StrategySimpleTwoWayInCore.java | 12 ++- 12 files changed, 187 insertions(+), 107 deletions(-) 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 279762ac6..dbf4eaf4c 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 @@ -79,7 +79,7 @@ import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectDirectory; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectWriter; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PackFile; import org.eclipse.jgit.lib.PackWriter; import org.eclipse.jgit.lib.PersonIdent; @@ -128,7 +128,7 @@ public class TestRepository { private final RevWalk pool; - private final ObjectWriter writer; + private final ObjectInserter inserter; private long now; @@ -155,7 +155,7 @@ public TestRepository(R db) throws IOException { 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; } @@ -205,7 +205,14 @@ public RevBlob blob(final String content) throws Exception { * @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); } /** @@ -241,7 +248,14 @@ public RevTree tree(final DirCacheEntry... entries) throws Exception { 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); } /** @@ -351,7 +365,14 @@ public RevCommit commit(final int secDelta, final RevTree tree, 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. */ @@ -382,7 +403,14 @@ public RevTag tag(final String name, final RevObject dst) throws Exception { 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); } /** @@ -777,13 +805,21 @@ public RevCommit create() throws Exception { 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.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java index e2a356466..e57d0f32f 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 @@ -123,11 +123,15 @@ private Tree buildTree(HashMap headEntries) throws IOException { } ObjectId genSha1(String data) { - ObjectWriter objectWriter = new ObjectWriter(db); + ObjectInserter w = db.newObjectInserter(); try { - return objectWriter.writeBlob(data.getBytes()); + ObjectId id = w.insert(Constants.OBJ_BLOB, data.getBytes()); + w.flush(); + return id; } catch (IOException e) { fail(e.toString()); + } finally { + w.release(); } return null; } 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 42e653be3..1b4a11db5 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.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; @@ -93,7 +94,7 @@ public void testPick() throws Exception { 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 @@ private void assertCorrectId(final DirCache treeT, final TreeWalk tw) { .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 @@ private DirCacheEntry makeEntry(final String path, final FileMode mode, 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 690b166cb..a4ef2cd64 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.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; @@ -126,7 +128,7 @@ public void testTrivialTwoWay_validSubtreeSort() throws Exception { 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 }); @@ -177,7 +179,7 @@ public void testTrivialTwoWay_concurrentSubtreeChange() throws Exception { 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 }); @@ -224,7 +226,7 @@ public void testTrivialTwoWay_conflictSubtreeChange() throws Exception { 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 }); @@ -256,7 +258,7 @@ public void testTrivialTwoWay_leftDFconflict1() throws Exception { 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 }); @@ -288,7 +290,7 @@ public void testTrivialTwoWay_rightDFconflict1() throws Exception { 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 }); @@ -318,7 +320,7 @@ public void testTrivialTwoWay_leftDFconflict2() throws Exception { 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 }); @@ -348,7 +350,7 @@ public void testTrivialTwoWay_rightDFconflict2() throws Exception { 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 @@ private void assertCorrectId(final DirCache treeT, final TreeWalk tw) { .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 @@ private DirCacheEntry makeEntry(final String path, final FileMode mode, 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/treewalk/filter/PathSuffixFilterTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTestCase.java index 1aaefc415..ad51ac2dd 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,16 +54,16 @@ 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 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 = DirCache.read(db); final DirCacheBuilder builder = dc.builder(); final DirCacheEntry aSthEntry = new DirCacheEntry("a.sth"); @@ -73,7 +75,8 @@ public void testNonRecursiveFiltering() throws IOException { 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,11 +95,11 @@ public void testNonRecursiveFiltering() throws IOException { } 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 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 = DirCache.read(db); final DirCacheBuilder builder = dc.builder(); final DirCacheEntry aSthEntry = new DirCacheEntry("a.sth"); @@ -116,7 +119,8 @@ public void testRecursiveFiltering() throws IOException { 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/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java index eef952e7c..f12c94b33 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.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; @@ -141,52 +141,59 @@ public RevCommit call() throws NoHeadException, NoMessageException, // lock the index DirCache index = DirCache.lock(repo); try { - ObjectWriter repoWriter = new ObjectWriter(repo); + 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); - // 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); + // 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 = odi.insert(Constants.OBJ_COMMIT, odi + .format(commit)); + odi.flush(); - commit.setParentIds(parents.toArray(new ObjectId[]{})); - commit.setTreeId(indexTreeId); - ObjectId commitId = repoWriter.writeCommit(commit); + RevCommit revCommit = new RevWalk(repo) + .parseCommit(commitId); + RefUpdate ru = repo.updateRef(Constants.HEAD); + ru.setNewObjectId(commitId); + ru.setRefLogMessage("commit : " + + revCommit.getShortMessage(), false); - RevCommit revCommit = new RevWalk(repo).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); + 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(); + } + 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)); } - 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/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java index 99d5d2eda..9ced005a4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java @@ -66,7 +66,7 @@ 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.ObjectInserter; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.MutableInteger; @@ -772,7 +772,9 @@ public DirCacheTree getCacheTree(final boolean build) { * 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), @@ -783,7 +785,7 @@ public DirCacheTree getCacheTree(final boolean build) { * @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/DirCacheTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java index 144b1a6cf..e04b797ab 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.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 String getPathString() { * 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 @@ ObjectId writeTree(final DirCacheEntry[] cache, int cIdx, 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/lib/Commit.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Commit.java index 66dd89120..be4818aa5 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 void setMessage(final String m) { 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.getObjectDatabase().newInserter(); + 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/Tag.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tag.java index 5b3531eb1..25a06c9c6 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 void tag() throws IOException { 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/merge/Merger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java index 38af20fb8..59b826438 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java @@ -51,7 +51,7 @@ 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.revwalk.RevCommit; @@ -73,7 +73,7 @@ public abstract class Merger { /** 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; @@ -105,10 +105,10 @@ public Repository getRepository() { /** * @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; } /** 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 6cd244599..aa2321b4a 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.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; @@ -152,7 +153,16 @@ else if (tw.isSubtree()) { if (hasConflict) return false; try { - resultTree = cache.writeTree(getObjectWriter()); + ObjectInserter odi = getObjectInserter(); + try { + resultTree = cache.writeTree(odi); + odi.flush(); + } finally { + // We don't know if our caller will release the + // inserter, so make sure we do it ourselves. + // + odi.release(); + } return true; } catch (UnmergedPathException upe) { resultTree = null; From 133c987f4db0788043b79e33582d0c0d4d6ce09a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 18 Jun 2010 20:23:13 -0700 Subject: [PATCH 026/103] Refactor alternate object databases below ObjectDirectory Not every object storage system will have the concept of alternate object databases to search, and even if they do, they may not have the notion of fast-access / slow-access split like we do within the ObjectDirectory code for pack files and loose objects. Push all of that down below the generic API so that it is a hidden detail of the ObjectDirectory and its related supporting classes. Change-Id: I54bc1ca5ff2ac94dfffad1f9a9dad7af202b9523 Signed-off-by: Shawn O. Pearce --- .../jgit/lib/AlternateRepositoryDatabase.java | 147 --------- .../jgit/lib/CachedObjectDatabase.java | 137 --------- .../jgit/lib/CachedObjectDirectory.java | 111 +++++-- .../eclipse/jgit/lib/FileObjectDatabase.java | 199 +++++++++++++ .../org/eclipse/jgit/lib/FileRepository.java | 8 +- .../org/eclipse/jgit/lib/ObjectDatabase.java | 278 +----------------- .../org/eclipse/jgit/lib/ObjectDirectory.java | 125 +++++--- 7 files changed, 387 insertions(+), 618 deletions(-) delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java 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 64b1254cc..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2010, Constantine Plotnikov - * 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}. - *

- * 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 ObjectInserter newInserter() { - return odb.newInserter(); - } - - @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 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/CachedObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java deleted file mode 100644 index f593bfca4..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2010, Constantine Plotnikov - * Copyright (C) 2010, JetBrains s.r.o. - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -import java.io.IOException; -import java.util.Collection; - -/** - * {@link ObjectDatabase} wrapper providing temporary lookup caching. - *

- * 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. - *

- * 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 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(); - } - - @Override - public ObjectInserter newInserter() { - return wrapped.newInserter(); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java index 3724f8446..37a96d74f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java @@ -46,6 +46,7 @@ import java.io.File; import java.io.IOException; +import java.util.Collection; /** * The cached instance of an {@link ObjectDirectory}. @@ -53,21 +54,26 @@ * 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 unpackedObjects = new ObjectIdSubclassMap(); + 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 +97,89 @@ public CachedObjectDirectory(ObjectDirectory wrapped) { } @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 + void openObjectInAllPacks(Collection out, + WindowCursor curs, AnyObjectId objectId) throws IOException { + wrapped.openObjectInAllPacks(out, curs, objectId); + } + + @Override + File getDirectory() { + return wrapped.getDirectory(); + } + + @Override + AlternateHandle[] myAlternates() { + if (alts == null) { + AlternateHandle[] src = wrapped.myAlternates(); + alts = new AlternateHandle[src.length]; + for (int i = 0; i < alts.length; i++) { + FileObjectDatabase s = src[i].db; + alts[i] = new AlternateHandle(s.newCachedFileObjectDatabase()); + } + } + return alts; + } + + @Override + boolean tryAgain1() { + return wrapped.tryAgain1(); + } + + @Override + public boolean hasObject(final AnyObjectId objectId) { + return hasObjectImpl1(objectId); + } + + @Override + boolean hasObject1(AnyObjectId objectId) { + return unpackedObjects.contains(objectId) + || wrapped.hasObject1(objectId); + } + + @Override + public 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); - } - - @Override - protected boolean hasObject1(AnyObjectId objectId) { - if (unpackedObjects.get(objectId) != null) - return true; // known to be loose - return super.hasObject1(objectId); - } - - @Override - protected boolean hasObject2(String name) { - return false; // loose objects were tested by hasObject1 + // This method should never be invoked. + throw new UnsupportedOperationException(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java new file mode 100644 index 000000000..a7bf60370 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import java.io.File; +import java.io.IOException; + +abstract class FileObjectDatabase extends ObjectDatabase { + /** + * Does the requested object exist in this database? + *

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

+ * Alternates (if present) are searched automatically. + * + * @param curs + * temporary working space associated with the calling thread. + * @param objectId + * identity of the object to open. + * @return a {@link ObjectLoader} for accessing the data of the named + * object, or null if the object does not exist. + * @throws IOException + */ + public ObjectLoader openObject(final WindowCursor curs, + final AnyObjectId objectId) throws IOException { + ObjectLoader ldr; + + ldr = openObjectImpl1(curs, objectId); + if (ldr != null) + return ldr; + + ldr = openObjectImpl2(curs, objectId.name(), objectId); + if (ldr != null) + return ldr; + + return null; + } + + final ObjectLoader openObjectImpl1(final WindowCursor curs, + final AnyObjectId objectId) throws IOException { + ObjectLoader ldr; + + ldr = openObject1(curs, objectId); + if (ldr != null) + return ldr; + + for (final AlternateHandle alt : myAlternates()) { + ldr = alt.db.openObjectImpl1(curs, objectId); + if (ldr != null) + return ldr; + } + + if (tryAgain1()) { + ldr = openObject1(curs, objectId); + if (ldr != null) + return ldr; + } + + return null; + } + + final ObjectLoader openObjectImpl2(final WindowCursor curs, + final String objectName, final AnyObjectId objectId) + throws IOException { + ObjectLoader ldr; + + ldr = openObject2(curs, objectName, objectId); + if (ldr != null) + return ldr; + + for (final AlternateHandle alt : myAlternates()) { + ldr = alt.db.openObjectImpl2(curs, objectName, objectId); + if (ldr != null) + return ldr; + } + + return null; + } + + abstract File getDirectory(); + + abstract AlternateHandle[] myAlternates(); + + abstract boolean tryAgain1(); + + abstract boolean hasObject1(AnyObjectId objectId); + + abstract boolean hasObject2(String objectId); + + abstract ObjectLoader openObject1(WindowCursor curs, AnyObjectId objectId) + throws IOException; + + abstract ObjectLoader openObject2(WindowCursor curs, String objectName, + AnyObjectId objectId) throws IOException; + + abstract FileObjectDatabase newCachedFileObjectDatabase(); + + static class AlternateHandle { + final FileObjectDatabase db; + + AlternateHandle(FileObjectDatabase db) { + this.db = db; + } + + void close() { + db.close(); + } + } + + static class AlternateRepository extends AlternateHandle { + final FileRepository repository; + + AlternateRepository(FileRepository r) { + super(r.getObjectDatabase()); + repository = r; + } + + void close() { + repository.close(); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java index 0ad558b94..12248fb42 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java @@ -56,6 +56,8 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.FileObjectDatabase.AlternateHandle; +import org.eclipse.jgit.lib.FileObjectDatabase.AlternateRepository; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.SystemReader; @@ -443,11 +445,11 @@ void openObjectInAllPacks(final AnyObjectId objectId, */ public Set getAdditionalHaves() { HashSet r = new HashSet(); - for (ObjectDatabase d : getObjectDatabase().getAlternates()) { - if (d instanceof AlternateRepositoryDatabase) { + for (AlternateHandle d : objectDatabase. myAlternates()) { + if (d instanceof AlternateRepository) { Repository repo; - repo = ((AlternateRepositoryDatabase) d).getRepository(); + repo = ((AlternateRepository) d).repository; for (Ref ref : repo.getAllRefs().values()) r.add(ref.getObjectId()); r.addAll(repo.getAdditionalHaves()); 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 df52ae02f..c44a7ac6f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java @@ -45,30 +45,17 @@ import java.io.IOException; import java.util.Collection; -import java.util.concurrent.atomic.AtomicReference; /** * Abstraction of arbitrary object storage. *

* 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. - *

- * 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 alternates; - /** Initialize a new database instance for access. */ protected ObjectDatabase() { - alternates = new AtomicReference(); + // Protected to force extension. } /** @@ -103,95 +90,21 @@ public void create() throws IOException { public abstract ObjectInserter newInserter(); /** - * Close any resources held by this database and its active alternates. + * Close any resources held by this database. */ - public final void close() { - closeSelf(); - closeAlternates(); - } - - /** - * Close any resources held by this database only; ignoring alternates. - *

- * To fully close this database and its referenced alternates, the caller - * should instead invoke {@link #close()}. - */ - 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 void close(); /** * Does the requested object exist in this database? - *

- * Alternates (if present) are searched automatically. - * - * @param objectId - * identity of the object to test for existence of. - * @return true if the specified object is stored in this database, or any - * of the alternate databases. - */ - public 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; - } - - /** - * 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. */ - protected abstract boolean hasObject1(AnyObjectId objectId); - - /** - * Slow half of {@link #hasObject(AnyObjectId)}. - * - * @param objectName - * 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; - } + public abstract boolean hasObject(AnyObjectId objectId); /** * Open an object from this database. - *

- * Alternates (if present) are searched automatically. * * @param curs * temporary working space associated with the calling thread. @@ -201,100 +114,11 @@ protected boolean hasObject2(String objectName) { * object, or null if the object does not exist. * @throws IOException */ - 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) - 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; - } - - /** - * 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, + public abstract ObjectLoader openObject(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. - *

- * If any alternates are present, their packs are also considered. * * @param out * result collection of loaders for this object, filled with @@ -305,92 +129,9 @@ protected ObjectLoader openObject2(WindowCursor curs, String objectName, * id of object to search for * @throws IOException */ - final void openObjectInAllPacks(final Collection out, + abstract void openObjectInAllPacks(final Collection 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. - * - * @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 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. - *

- * 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. - *

- * If the alternate array is empty, implementors should consider using the - * constant {@link #NO_ALTERNATES}. - * - * @return the alternate list for this database. - * @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(); - } - } + throws IOException; /** * Create a new cached database instance over this database. This instance might @@ -398,9 +139,8 @@ protected void closeAlternates(ObjectDatabase[] alt) { * 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/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java index ac3c7bf27..c605df9b8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java @@ -72,8 +72,18 @@ * where objects are stored loose by hashing them into directories by their * {@link ObjectId}, or are stored in compressed containers known as * {@link PackFile}s. + *

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

+ * Databases are divided into two halves: a half that is considered to be fast + * to search (the {@code PackFile}s), and a half that is considered to be slow + * to search (loose objects). When alternates are present the fast half is fully + * searched (recursively through all alternates) before the slow half is + * considered. */ -public class ObjectDirectory extends ObjectDatabase { +public class ObjectDirectory extends FileObjectDatabase { private static final PackList NO_PACKS = new PackList(-1, -1, new PackFile[0]); private final Config config; @@ -88,10 +98,10 @@ public class ObjectDirectory extends ObjectDatabase { private final AtomicReference packList; - private final File[] alternateObjectDir; - private final FS fs; + private final AtomicReference alternates; + /** * Initialize a reference to an on-disk object directory. * @@ -99,21 +109,33 @@ public class ObjectDirectory extends ObjectDatabase { * configuration this directory consults for write settings. * @param dir * the location of the objects 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 Config cfg, 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(NO_PACKS); this.fs = fs; + + alternates = new AtomicReference(); + if (alternatePaths != null) { + AlternateHandle[] alt; + + alt = new AlternateHandle[alternatePaths.length]; + for (int i = 0; i < alternatePaths.length; i++) + alt[i] = openAlternate(alternatePaths[i]); + alternates.set(alt); + } } /** @@ -141,11 +163,19 @@ public ObjectInserter newInserter() { } @Override - public void closeSelf() { + 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(); + } } /** @@ -209,8 +239,7 @@ public String toString() { 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)) { @@ -228,8 +257,7 @@ protected boolean hasObject1(final AnyObjectId objectId) { 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 (;;) { @@ -256,7 +284,7 @@ protected ObjectLoader openObject1(final WindowCursor curs, } @Override - void openObjectInAllPacks1(final Collection out, + void openObjectInAllPacks(final Collection out, final WindowCursor curs, final AnyObjectId objectId) throws IOException { PackList pList = packList.get(); @@ -282,13 +310,11 @@ void openObjectInAllPacks1(final Collection out, } } - @Override - protected boolean hasObject2(final String objectName) { + boolean hasObject2(final String objectName) { return fileFor(objectName).exists(); } - @Override - protected ObjectLoader openObject2(final WindowCursor curs, + ObjectLoader openObject2(final WindowCursor curs, final String objectName, final AnyObjectId objectId) throws IOException { try { @@ -298,8 +324,7 @@ protected ObjectLoader openObject2(final WindowCursor curs, } } - @Override - protected boolean tryAgain1() { + boolean tryAgain1() { final PackList old = packList.get(); if (old.tryAgain(packDirectory.lastModified())) return old != scanPacks(old); @@ -469,29 +494,36 @@ private Set listPackDirectory() { return nameSet; } - @Override - protected ObjectDatabase[] loadAlternates() throws IOException { - final List l = new ArrayList(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 l = new ArrayList(4); + final BufferedReader br = open(alternatesFile); + try { + String line; + while ((line = br.readLine()) != null) { + l.add(openAlternate(line)); + } + } finally { + br.close(); } - return l.toArray(new ObjectDatabase[l.size()]); + return l.toArray(new AlternateHandle[l.size()]); } private static BufferedReader open(final File f) @@ -499,19 +531,22 @@ private static BufferedReader open(final File f) 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(config, objdir, null, fs); + + ObjectDirectory db = new ObjectDirectory(config, objdir, null, fs); + return new AlternateHandle(db); } private static final class PackList { @@ -576,6 +611,10 @@ boolean tryAgain(final long currLastModified) { @Override public ObjectDatabase newCachedDatabase() { + return newCachedFileObjectDatabase(); + } + + FileObjectDatabase newCachedFileObjectDatabase() { return new CachedObjectDirectory(this); } } From 5cfc29b491eece0ceb71e653763b6cc771e7bdfe Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 18 Jun 2010 23:02:57 -0700 Subject: [PATCH 027/103] Replace WindowCache with ObjectReader The WindowCache is an implementation detail of PackFile and how its used by ObjectDirectory. Lets start to hide it and replace the public API with a more generic concept, ObjectReader. Because PackedObjectLoader is also considered a private detail of PackFile, we have to make PackWriter temporarily dependent upon the WindowCursor and thus FileRepository and ObjectDirectory in order to just start the refactoring. In later changes we will clean up the APIs more, exposing sufficient support to PackWriter without needing the file specific implementation details. Change-Id: I676be12b57f3534f1285854ee5de1aa483895398 Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/iplog/IpLogGenerator.java | 7 +- .../pgm/opt/AbstractTreeIteratorHandler.java | 4 +- .../eclipse/jgit/lib/T0004_PackReader.java | 2 +- .../transport/ReceivePackRefFilterTest.java | 2 +- .../jgit/dircache/DirCacheBuilder.java | 4 +- .../jgit/lib/CachedObjectDirectory.java | 9 +- .../eclipse/jgit/lib/FileObjectDatabase.java | 14 ++- .../org/eclipse/jgit/lib/FileRepository.java | 39 ------ .../org/eclipse/jgit/lib/ObjectDatabase.java | 66 ++++++---- .../org/eclipse/jgit/lib/ObjectDirectory.java | 4 +- .../org/eclipse/jgit/lib/ObjectReader.java | 114 ++++++++++++++++++ .../src/org/eclipse/jgit/lib/PackWriter.java | 7 +- .../src/org/eclipse/jgit/lib/Repository.java | 63 ++++------ .../org/eclipse/jgit/lib/WindowCursor.java | 41 +++++-- .../src/org/eclipse/jgit/merge/Merger.java | 4 +- .../src/org/eclipse/jgit/revwalk/RevWalk.java | 6 +- .../org/eclipse/jgit/transport/IndexPack.java | 22 +++- .../jgit/treewalk/AbstractTreeIterator.java | 4 +- .../jgit/treewalk/CanonicalTreeParser.java | 14 +-- .../org/eclipse/jgit/treewalk/TreeWalk.java | 5 +- 20 files changed, 274 insertions(+), 157 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java 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 f64c32984..f40c3896a 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 @@ -86,7 +86,7 @@ import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.PersonIdent; 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.RevTree; import org.eclipse.jgit.revwalk.RevWalk; @@ -144,7 +144,7 @@ public class IpLogGenerator { private NameConflictTreeWalk tw; - private final WindowCursor curs = new WindowCursor(); + private ObjectReader curs; private final MutableObjectId idbuf = new MutableObjectId(); @@ -184,6 +184,7 @@ public void scan(Repository repo, RevCommit startCommit, String version) throws IOException, ConfigInvalidException { try { db = repo; + curs = db.newObjectReader(); rw = new RevWalk(db); tw = new NameConflictTreeWalk(db); @@ -194,7 +195,7 @@ public void scan(Repository repo, RevCommit startCommit, String version) scanProjectCommits(meta.getProjects().get(0), c); commits.add(c); } finally { - WindowCursor.release(curs); + curs.release(); db = null; rw = null; tw = null; 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 2043ac209..d7b98c509 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.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,7 +121,7 @@ public int parseArguments(final Parameters params) throws CmdLineException { 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); } catch (MissingObjectException e) { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java index 336bba22c..54816f6f5 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java @@ -63,7 +63,7 @@ public void test003_lookupCompressedObject() throws IOException { 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()); 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 b87cc7a99..03726cba0 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 @@ -176,7 +176,7 @@ public void testSuccess() throws Exception { // 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. 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 e6b619781..adb8a8d77 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java @@ -51,7 +51,7 @@ 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; @@ -166,7 +166,7 @@ public void addTree(final byte[] pathPrefix, final int stage, final Repository db, final AnyObjectId tree) throws IOException { final TreeWalk tw = new TreeWalk(db); tw.reset(); - final WindowCursor curs = new WindowCursor(); + final ObjectReader curs = db.newObjectReader(); try { tw.addTree(new CanonicalTreeParser(pathPrefix, db, tree .toObjectId(), curs)); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java index 37a96d74f..351d6817f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java @@ -46,7 +46,6 @@ import java.io.File; import java.io.IOException; -import java.util.Collection; /** * The cached instance of an {@link ObjectDirectory}. @@ -116,12 +115,6 @@ FileObjectDatabase newCachedFileObjectDatabase() { return this; } - @Override - void openObjectInAllPacks(Collection out, - WindowCursor curs, AnyObjectId objectId) throws IOException { - wrapped.openObjectInAllPacks(out, curs, objectId); - } - @Override File getDirectory() { return wrapped.getDirectory(); @@ -157,7 +150,7 @@ boolean hasObject1(AnyObjectId objectId) { } @Override - public ObjectLoader openObject(final WindowCursor curs, + ObjectLoader openObject(final WindowCursor curs, final AnyObjectId objectId) throws IOException { return openObjectImpl1(curs, objectId); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java index a7bf60370..36e808c23 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java @@ -45,8 +45,14 @@ import java.io.File; import java.io.IOException; +import java.util.Collection; abstract class FileObjectDatabase extends ObjectDatabase { + @Override + public ObjectReader newReader() { + return new WindowCursor(this); + } + /** * Does the requested object exist in this database? *

@@ -98,8 +104,8 @@ final boolean hasObjectImpl2(final String objectId) { * object, or null if the object does not exist. * @throws IOException */ - public ObjectLoader openObject(final WindowCursor curs, - final AnyObjectId objectId) throws IOException { + ObjectLoader openObject(final WindowCursor curs, final AnyObjectId objectId) + throws IOException { ObjectLoader ldr; ldr = openObjectImpl1(curs, objectId); @@ -154,6 +160,10 @@ final ObjectLoader openObjectImpl2(final WindowCursor curs, return null; } + void openObjectInAllPacks(Collection reuseLoaders, + WindowCursor windowCursor, AnyObjectId otp) throws IOException { + } + abstract File getDirectory(); abstract AlternateHandle[] myAlternates(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java index 12248fb42..32f516f7d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java @@ -49,9 +49,7 @@ import java.io.File; import java.io.IOException; import java.text.MessageFormat; -import java.util.Collection; import java.util.HashSet; -import java.util.LinkedList; import java.util.Set; import org.eclipse.jgit.JGitText; @@ -396,43 +394,6 @@ public File toFile(final AnyObjectId objectId) { return objectDatabase.fileFor(objectId); } - /** - * Open object in all packs containing specified object. - * - * @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 - * @throws IOException - */ - public Collection openObjectInAllPacks( - final AnyObjectId objectId, final WindowCursor curs) - throws IOException { - Collection result = new LinkedList(); - openObjectInAllPacks(objectId, result, curs); - return result; - } - - /** - * Open object in all packs containing specified object. - * - * @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 resultLoaders, - final WindowCursor curs) throws IOException { - objectDatabase.openObjectInAllPacks(resultLoaders, curs, objectId); - } - /** * Objects known to exist but not expressed by {@link #getAllRefs()}. *

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 c44a7ac6f..c2d2beda9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java @@ -44,7 +44,8 @@ package org.eclipse.jgit.lib; import java.io.IOException; -import java.util.Collection; + +import org.eclipse.jgit.errors.MissingObjectException; /** * Abstraction of arbitrary object storage. @@ -89,6 +90,17 @@ public void create() throws IOException { */ public abstract ObjectInserter newInserter(); + /** + * Create a new {@code ObjectReader} to read existing objects. + *

+ * The returned reader is not itself thread-safe, but multiple concurrent + * reader instances created from the same {@code ObjectDatabase} must be + * thread-safe. + * + * @return reader the caller can use to load objects from this database. + */ + public abstract ObjectReader newReader(); + /** * Close any resources held by this database. */ @@ -96,42 +108,48 @@ public void create() throws IOException { /** * Does the requested object exist in this database? + *

+ * This is a one-shot call interface which may be faster than allocating a + * {@link #newReader()} to perform the lookup. * * @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 abstract boolean hasObject(AnyObjectId objectId); + public boolean hasObject(final AnyObjectId objectId) throws IOException { + final ObjectReader or = newReader(); + try { + return or.hasObject(objectId); + } finally { + or.release(); + } + } /** * Open an object from this database. + *

+ * 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 abstract ObjectLoader openObject(WindowCursor curs, - AnyObjectId objectId) throws IOException; - - /** - * Open the object from all packs containing it. - * - * @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 - */ - abstract void openObjectInAllPacks(final Collection out, - final WindowCursor curs, final AnyObjectId objectId) - throws IOException; + public ObjectLoader openObject(final AnyObjectId objectId) + throws IOException { + final ObjectReader or = newReader(); + try { + return or.openObject(objectId); + } finally { + or.release(); + } + } /** * Create a new cached database instance over this database. This instance might diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java index c605df9b8..810e48890 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java @@ -283,7 +283,6 @@ ObjectLoader openObject1(final WindowCursor curs, } } - @Override void openObjectInAllPacks(final Collection out, final WindowCursor curs, final AnyObjectId objectId) throws IOException { @@ -308,6 +307,9 @@ void openObjectInAllPacks(final Collection out, } break SEARCH; } + + for (AlternateHandle h : myAlternates()) + h.db.openObjectInAllPacks(out, curs, objectId); } boolean hasObject2(final String objectName) { 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 000000000..c95130ce2 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java @@ -0,0 +1,114 @@ +/* + * 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.MissingObjectException; + +/** Reads an {@link ObjectDatabase} for a single thread. */ +public abstract class ObjectReader { + /** Type hint indicating the caller doesn't know the type. */ + protected static final int OBJ_ANY = -1; + + /** + * 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 hasObject(AnyObjectId objectId) throws IOException { + try { + openObject(objectId); + 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 + */ + public ObjectLoader openObject(AnyObjectId objectId) + throws MissingObjectException, IOException { + return openObject(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 IOException + */ + public abstract ObjectLoader openObject(AnyObjectId objectId, int typeHint) + throws MissingObjectException, IOException; + + /** + * Release any resources used by this reader. + *

+ * 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/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java index e8e5fc21e..15ac17425 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java @@ -179,7 +179,7 @@ public class PackWriter { private final byte[] buf = new byte[16384]; // 16 KB - private final WindowCursor windowCursor = new WindowCursor(); + private final WindowCursor windowCursor; private List sortedByName; @@ -236,6 +236,9 @@ public PackWriter(final Repository repo, final ProgressMonitor monitor) { public PackWriter(final Repository repo, final ProgressMonitor imonitor, final ProgressMonitor wmonitor) { this.db = repo; + windowCursor = new WindowCursor((ObjectDirectory) repo + .getObjectDatabase()); + initMonitor = imonitor == null ? NullProgressMonitor.INSTANCE : imonitor; writeMonitor = wmonitor == null ? NullProgressMonitor.INSTANCE : wmonitor; @@ -621,7 +624,7 @@ private void searchForReuse() throws IOException { private void searchForReuse( final Collection reuseLoaders, final ObjectToPack otp) throws IOException { - db.openObjectInAllPacks(otp, reuseLoaders, windowCursor); + windowCursor.openObjectInAllPacks(otp, reuseLoaders); if (reuseDeltas) { selectDeltaReuseForObject(otp, reuseLoaders); } 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 334581415..400cae43d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -50,7 +50,6 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -64,6 +63,7 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.RevisionSyntaxException; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; @@ -153,6 +153,11 @@ public ObjectInserter newObjectInserter() { return getObjectDatabase().newInserter(); } + /** @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(); @@ -186,7 +191,12 @@ public FS getFS() { * known shared repositories. */ public boolean hasObject(AnyObjectId objectId) { - return getObjectDatabase().hasObject(objectId); + try { + return getObjectDatabase().hasObject(objectId); + } catch (IOException e) { + // Legacy API, assume error means "no" + return false; + } } /** @@ -199,11 +209,11 @@ public boolean hasObject(AnyObjectId objectId) { */ public ObjectLoader openObject(final AnyObjectId id) throws IOException { - final WindowCursor wc = new WindowCursor(); try { - return openObject(wc, id); - } finally { - wc.release(); + return getObjectDatabase().openObject(id); + } catch (MissingObjectException notFound) { + // Legacy API, return null + return null; } } @@ -216,43 +226,18 @@ public ObjectLoader openObject(final AnyObjectId id) * @return a {@link ObjectLoader} for accessing the data of the named * object, or null if the object does not exist. * @throws IOException + * @deprecated Use {code newObjectReader().open(id)}. */ - public ObjectLoader openObject(WindowCursor curs, AnyObjectId id) + @Deprecated + public ObjectLoader openObject(ObjectReader curs, AnyObjectId id) throws IOException { - return getObjectDatabase().openObject(curs, id); + try { + return curs.openObject(id); + } catch (MissingObjectException notFound) { + return null; + } } - /** - * Open object in all packs containing specified object. - * - * @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 - * @throws IOException - */ - public abstract Collection openObjectInAllPacks( - AnyObjectId objectId, WindowCursor curs) - throws IOException; - - /** - * Open object in all packs containing specified object. - * - * @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 - */ - abstract void openObjectInAllPacks(final AnyObjectId objectId, - final Collection resultLoaders, - final WindowCursor curs) throws IOException; - /** * @param id * SHA'1 of a blob diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java index 968c92e5c..afc7f7186 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java @@ -45,11 +45,14 @@ package org.eclipse.jgit.lib; import java.io.IOException; +import java.util.Collection; import java.util.zip.DataFormatException; import java.util.zip.Inflater; +import org.eclipse.jgit.errors.MissingObjectException; + /** Active handle to a ByteWindow. */ -public final class WindowCursor { +final class WindowCursor extends ObjectReader { /** Temporary buffer large enough for at least one raw object id. */ final byte[] tempId = new byte[Constants.OBJECT_ID_LENGTH]; @@ -57,6 +60,32 @@ public final class WindowCursor { private ByteWindow window; + private final FileObjectDatabase db; + + WindowCursor(FileObjectDatabase db) { + this.db = db; + } + + public boolean hasObject(AnyObjectId objectId) throws IOException { + return db.hasObject(objectId); + } + + public ObjectLoader openObject(AnyObjectId objectId, int typeHint) + throws MissingObjectException, IOException { + final ObjectLoader ldr = db.openObject(this, objectId); + if (ldr == null) { + if (typeHint == OBJ_ANY) + throw new MissingObjectException(objectId.copy(), "unknown"); + throw new MissingObjectException(objectId.copy(), typeHint); + } + return ldr; + } + + void openObjectInAllPacks(AnyObjectId otp, + Collection reuseLoaders) throws IOException { + db.openObjectInAllPacks(reuseLoaders, this, otp); + } + /** * Copy bytes from the window to a caller supplied buffer. * @@ -168,14 +197,4 @@ public void release() { 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/merge/Merger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java index 59b826438..38a8b8eae 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java @@ -53,7 +53,7 @@ import org.eclipse.jgit.lib.ObjectId; 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; @@ -202,7 +202,7 @@ protected AbstractTreeIterator mergeBase(final int aIdx, final int bIdx) */ protected AbstractTreeIterator openTree(final AnyObjectId treeId) throws IncorrectObjectTypeException, IOException { - final WindowCursor curs = new WindowCursor(); + final ObjectReader curs = db.newObjectReader(); try { return new CanonicalTreeParser(null, db, treeId, curs); } finally { 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 e42554811..122418296 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.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; @@ -159,7 +159,7 @@ public class RevWalk implements Iterable { final Repository db; - final WindowCursor curs; + final ObjectReader curs; final MutableObjectId idBuffer; @@ -193,7 +193,7 @@ public class RevWalk implements Iterable { */ public RevWalk(final Repository repo) { db = repo; - curs = new WindowCursor(); + curs = db.newObjectReader(); idBuffer = new MutableObjectId(); objects = new ObjectIdSubclassMap(); roots = new ArrayList(); 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 f6e04107f..0cd673369 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java @@ -79,7 +79,7 @@ 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.util.NB; /** Indexes Git pack files for local use. */ @@ -213,7 +213,7 @@ public static IndexPack create(final Repository db, final InputStream is) /** 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. @@ -232,7 +232,7 @@ public IndexPack(final Repository db, final InputStream src, objectDatabase = db.getObjectDatabase().newCachedDatabase(); in = src; inflater = InflaterCache.get(); - readCurs = new WindowCursor(); + readCurs = objectDatabase.newReader(); buf = new byte[BUFFER_SIZE]; objectData = new byte[BUFFER_SIZE]; objectDigest = Constants.newMessageDigest(); @@ -430,7 +430,13 @@ public void index(final ProgressMonitor progress) throws IOException { inflater = null; objectDatabase.close(); } - readCurs = WindowCursor.release(readCurs); + + try { + if (readCurs != null) + readCurs.release(); + } finally { + readCurs = null; + } progress.endTask(); if (packOut != null) @@ -847,12 +853,16 @@ private void verifySafeObject(final AnyObjectId id, final int type, } } - final ObjectLoader ldr = objectDatabase.openObject(readCurs, id); - if (ldr != null) { + try { + final ObjectLoader ldr = readCurs.openObject(id, type); final byte[] existingData = ldr.getCachedBytes(); if (ldr.getType() != type || !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/treewalk/AbstractTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java index 90cea0f1b..73357a4df 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java @@ -56,7 +56,7 @@ 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; /** @@ -474,7 +474,7 @@ public EmptyTreeIterator createEmptyTreeIterator() { * a loose object or pack file could not be read. */ public AbstractTreeIterator createSubtreeIterator(final Repository repo, - final MutableObjectId idBuffer, final WindowCursor curs) + final MutableObjectId idBuffer, final ObjectReader curs) throws IncorrectObjectTypeException, IOException { return createSubtreeIterator(repo); } 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 0b9dc0044..b7f980cca 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java @@ -56,7 +56,7 @@ 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 { @@ -102,7 +102,7 @@ public CanonicalTreeParser() { * a loose object or pack file could not be read. */ public CanonicalTreeParser(final byte[] prefix, final Repository repo, - final AnyObjectId treeId, final WindowCursor curs) + final AnyObjectId treeId, final ObjectReader curs) throws IncorrectObjectTypeException, IOException { super(prefix); reset(repo, treeId, curs); @@ -148,7 +148,7 @@ public void reset(final byte[] treeData) { * a loose object or pack file could not be read. */ public CanonicalTreeParser resetRoot(final Repository repo, - final AnyObjectId id, final WindowCursor curs) + final AnyObjectId id, final ObjectReader curs) throws IncorrectObjectTypeException, IOException { CanonicalTreeParser p = this; while (p.parent != null) @@ -197,7 +197,7 @@ public CanonicalTreeParser next() { * a loose object or pack file could not be read. */ public void reset(final Repository repo, final AnyObjectId id, - final WindowCursor curs) + final ObjectReader curs) throws IncorrectObjectTypeException, IOException { final ObjectLoader ldr = repo.openObject(curs, id); if (ldr == null) { @@ -214,7 +214,7 @@ public void reset(final Repository repo, final AnyObjectId id, @Override public CanonicalTreeParser createSubtreeIterator(final Repository repo, - final MutableObjectId idBuffer, final WindowCursor curs) + final MutableObjectId idBuffer, final ObjectReader curs) throws IncorrectObjectTypeException, IOException { idBuffer.fromRaw(idBuffer(), idOffset()); if (!FileMode.TREE.equals(mode)) { @@ -242,7 +242,7 @@ public CanonicalTreeParser createSubtreeIterator(final Repository repo, * a loose object or pack file could not be read. */ public final CanonicalTreeParser createSubtreeIterator0( - final Repository repo, final AnyObjectId id, final WindowCursor curs) + final Repository repo, final AnyObjectId id, final ObjectReader curs) throws IOException { final CanonicalTreeParser p = new CanonicalTreeParser(this); p.reset(repo, id, curs); @@ -251,7 +251,7 @@ public final CanonicalTreeParser createSubtreeIterator0( public CanonicalTreeParser createSubtreeIterator(final Repository repo) throws IncorrectObjectTypeException, IOException { - final WindowCursor curs = new WindowCursor(); + final ObjectReader curs = repo.newObjectReader(); try { return createSubtreeIterator(repo, new MutableObjectId(), curs); } finally { 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 25ab78ef0..aefa79c3a 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.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; @@ -155,7 +155,7 @@ public static TreeWalk forPath(final Repository db, final String path, private final MutableObjectId idBuffer = new MutableObjectId(); - private final WindowCursor curs = new WindowCursor(); + private final ObjectReader curs; private TreeFilter filter; @@ -181,6 +181,7 @@ public static TreeWalk forPath(final Repository db, final String path, */ public TreeWalk(final Repository repo) { db = repo; + curs = repo.newObjectReader(); filter = TreeFilter.ALL; trees = new AbstractTreeIterator[] { new EmptyTreeIterator() }; } From 41c04bbb28af2c5ed4b05c3ec7c7ceeb84f17563 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 23 Jun 2010 18:26:40 -0700 Subject: [PATCH 028/103] Use ObjectInserter for loose objects in WalkFetchConnection Rather than relying on the repository's ability to give us the local file path for a loose object, just pass its inflated form to the ObjectInserter for the repository. We have to recompress it, which may slow down fetches, but this is the slow dumb protocol. The extra cost to do the compression locally isn't going to be a major bottleneck. This nicely removes the nasty part about computing the object identity by hand, allowing us to instead rely upon the inserter's internal computation. Unfortunately it means we might store a loose object whose SHA-1 doesn't match the expected SHA-1, such as if the remote repository was corrupted. This is fairly harmless, as the incorrectly named object will now be stored under its proper name, and will eventually be garbage collected, as its not referenced by the local repository. We have to flush the inserter after the object is stored because we aren't sure if we need to read the object later, or not. Change-Id: Idb1e2b1af1433a23f8c3fd55aeb20575e6047ef0 Signed-off-by: Shawn O. Pearce --- .../jgit/transport/WalkFetchConnection.java | 74 +++++-------------- 1 file changed, 18 insertions(+), 56 deletions(-) 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 0469c1ac0..c9d2e9e19 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.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.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; @@ -72,6 +70,7 @@ import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectDirectory; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PackIndex; import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.ProgressMonitor; @@ -166,8 +165,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. *

@@ -181,10 +178,14 @@ class WalkFetchConnection extends BaseFetchConnection { private final List packLocks; + /** Inserter to write objects onto {@link #local}. */ + private final ObjectInserter inserter; + WalkFetchConnection(final WalkTransport t, final WalkRemoteObjectDatabase w) { Transport wt = (Transport)t; local = wt.local; objCheck = wt.isCheckFetchedObjects() ? new ObjectChecker() : null; + inserter = local.newObjectInserter(); remotes = new ArrayList(); remotes.add(w); @@ -241,6 +242,7 @@ public void setPackLockMessage(final String message) { @Override public void close() { + inserter.release(); for (final RemotePack p : unfetchedPacks) { if (p.tmpIdx != null) p.tmpIdx.delete(); @@ -559,8 +561,7 @@ private boolean downloadLooseObject(final AnyObjectId id, 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? @@ -573,8 +574,8 @@ private boolean downloadLooseObject(final AnyObjectId id, } } - private void verifyLooseObject(final AnyObjectId id, final byte[] compressed) - throws IOException { + private void verifyAndInsertLooseObject(final AnyObjectId id, + final byte[] compressed) throws IOException { final UnpackedObjectLoader uol; try { uol = new UnpackedObjectLoader(compressed); @@ -596,62 +597,23 @@ private void verifyLooseObject(final AnyObjectId id, final byte[] compressed) 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 expandOneAlternate( From 8f46ee4870d0a49fcce1d0cb2b8e2aee9aaafee1 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 23 Jun 2010 17:44:13 -0700 Subject: [PATCH 029/103] Remove Repository.toFile(ObjectId) Not every type of Repository will be able to map an ObjectId into a local file system path that stores that object's file contents. Heck, its not even true for the FileRepository, as an object can be stored in a pack file and not in its loose format. Remove this from our public API, it was a mistake to publish it. Change-Id: I20d1b8c39104023936e6d46a5b0d7ef39ff118e8 Signed-off-by: Shawn O. Pearce --- .../tst/org/eclipse/jgit/lib/T0003_Basic.java | 6 ++++-- .../src/org/eclipse/jgit/lib/FileRepository.java | 14 -------------- .../src/org/eclipse/jgit/lib/Repository.java | 12 ------------ 3 files changed, 4 insertions(+), 28 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java index 4e87fd624..c770d605d 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java @@ -365,8 +365,10 @@ public void test009_CreateCommitOldFormat() throws IOException { 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()); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java index 32f516f7d..86ae5fada 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java @@ -380,20 +380,6 @@ public FileBasedConfig getConfig() { return repoConfig; } - /** - * 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); - } - /** * Objects known to exist but not expressed by {@link #getAllRefs()}. *

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 400cae43d..09411a453 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -173,18 +173,6 @@ public FS getFS() { return fs; } - /** - * 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 abstract File toFile(AnyObjectId objectId); - /** * @param objectId * @return true if the specified object is stored in this repo or any of the From 532421d98925f23ddaa63c8d5f22be24879a6385 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Jun 2010 09:07:53 -0700 Subject: [PATCH 030/103] Refactor repository construction to builder class The new FileRepositoryBuilder class helps applications to construct a properly configured FileRepository, with properties assumed based upon the standard Git rules for the local filesystem. To better support simple command line applications, environment variable handling and repository searching was moved into this builder class. The change gets rid of the ever-growing FileRepository constructor variants, and the multitude of java.io.File typed parameters, by using simple named setter methods. Change-Id: I17e8e0392ad1dbf6a90a7eb49a6d809388d27e4c Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/pgm/Main.java | 76 +-- .../jgit/lib/RepositorySetupWorkDirTest.java | 52 +- .../tst/org/eclipse/jgit/lib/T0003_Basic.java | 22 +- .../org/eclipse/jgit/JGitText.properties | 1 + .../src/org/eclipse/jgit/JGitText.java | 1 + .../jgit/lib/BaseRepositoryBuilder.java | 613 ++++++++++++++++++ .../org/eclipse/jgit/lib/FileRepository.java | 180 +---- .../jgit/lib/FileRepositoryBuilder.java | 88 +++ .../src/org/eclipse/jgit/lib/Repository.java | 32 +- .../eclipse/jgit/lib/RepositoryBuilder.java | 72 ++ 10 files changed, 859 insertions(+), 278 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepositoryBuilder.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java 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 b0da55a43..2da210d7e 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.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.FileRepository; +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 @@ private void execute(final String[] argv) throws Exception { 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 frb = new RepositoryBuilder() // + .setGitDir(gitdir) // + .readEnvironment() // + .findGitDir(); + if (frb.getGitDir() == null) { writer.println(CLIText.get().cantFindGitDirectory); writer.flush(); System.exit(1); } - cmd.init(new FileRepository(gitdir, gitworktree, objectdir, altobjectdirs, indexfile), gitdir); + + cmd.init(frb.build(), null); } else { cmd.init(null, gitdir); } @@ -224,27 +185,6 @@ private void execute(final String[] argv) throws Exception { } } - private static File findGitDir() { - Set ceilingDirectories = new HashSet(); - 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.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java index 070713d7b..187d3ae9d 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java @@ -47,6 +47,7 @@ import java.io.File; import java.io.IOException; +import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; /** @@ -78,7 +79,7 @@ public void testWorkdirIsParentDir_CreateRepositoryFromDotGitGitDir() public void testNotBare_CreateRepositoryFromWorkDirOnly() throws Exception { File workdir = getFile("workdir", "repo"); - Repository repo = new FileRepository(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 +88,7 @@ public void testNotBare_CreateRepositoryFromWorkDirOnly() throws Exception { public void testWorkdirIsDotGit_CreateRepositoryFromWorkDirOnly() throws Exception { File workdir = getFile("workdir", "repo"); - Repository repo = new FileRepository(null, workdir); + FileRepository repo = new FileRepositoryBuilder().setWorkTree(workdir).build(); assertGitdirPath(repo, "workdir", "repo", Constants.DOT_GIT); } @@ -96,7 +97,7 @@ public void testNotBare_CreateRepositoryFromGitDirOnlyWithWorktreeConfig() File gitDir = getFile("workdir", "repoWithConfig"); File workTree = getFile("workdir", "treeRoot"); setWorkTree(gitDir, workTree); - Repository repo = new FileRepository(gitDir, null); + FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); assertFalse(repo.isBare()); assertWorkdirPath(repo, "workdir", "treeRoot"); assertGitdirPath(repo, "workdir", "repoWithConfig"); @@ -106,7 +107,7 @@ public void testBare_CreateRepositoryFromGitDirOnlyWithBareConfigTrue() throws Exception { File gitDir = getFile("workdir", "repoWithConfig"); setBare(gitDir, true); - Repository repo = new FileRepository(gitDir, null); + FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); assertTrue(repo.isBare()); } @@ -114,7 +115,7 @@ public void testWorkdirIsParent_CreateRepositoryFromGitDirOnlyWithBareConfigFals throws Exception { File gitDir = getFile("workdir", "repoWithBareConfigTrue", "child"); setBare(gitDir, false); - Repository repo = new FileRepository(gitDir, null); + FileRepository repo = new FileRepositoryBuilder().setGitDir(gitDir).build(); assertWorkdirPath(repo, "workdir", "repoWithBareConfigTrue"); } @@ -122,21 +123,12 @@ public void testNotBare_CreateRepositoryFromGitDirOnlyWithBareConfigFalse() throws Exception { File gitDir = getFile("workdir", "repoWithBareConfigFalse", "child"); setBare(gitDir, false); - Repository repo = new FileRepository(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 FileRepository(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 { @@ -176,20 +168,28 @@ private File getFile(String... pathComponents) { return result; } - private void setBare(File gitDir, boolean bare) throws IOException { - FileRepository repo = new FileRepository(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 { - FileRepository repo = new FileRepository(gitDir, null); - repo.getConfig() - .setString(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_WORKTREE, - workTree.getAbsolutePath()); - repo.getConfig().save(); + private void setWorkTree(File gitDir, File workTree) throws IOException, + ConfigInvalidException { + String path = workTree.getAbsolutePath(); + FileBasedConfig cfg = configFor(gitDir); + cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_WORKTREE, path); + cfg.save(); + } + + private FileBasedConfig configFor(File gitDir) throws IOException, + ConfigInvalidException { + FileBasedConfig cfg = new FileBasedConfig(new File(gitDir, "config")); + cfg.load(); + return cfg; } private void assertGitdirPath(Repository repo, String... expected) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java index c770d605d..8dd5ae2bd 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java @@ -80,7 +80,7 @@ public void test001_Initalize() { public void test000_openRepoBadArgs() throws IOException { try { - new FileRepository(null, null); + new FileRepositoryBuilder().build(); fail("Must pass either GIT_DIR or GIT_WORK_TREE"); } catch (IllegalArgumentException e) { assertEquals( @@ -102,7 +102,7 @@ public void test000_openrepo_default_gitDirSet() throws IOException { repo1initial.close(); File theDir = new File(repo1Parent, Constants.DOT_GIT); - Repository r = new FileRepository(theDir, null); + FileRepository r = new FileRepositoryBuilder().setGitDir(theDir).build(); assertEqualsPath(theDir, r.getDirectory()); assertEqualsPath(repo1Parent, r.getWorkDir()); assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); @@ -122,7 +122,8 @@ public void test000_openrepo_default_gitDirAndWorkTreeSet() throws IOException { repo1initial.close(); File theDir = new File(repo1Parent, Constants.DOT_GIT); - Repository r = new FileRepository(theDir, repo1Parent.getParentFile()); + FileRepository r = new FileRepositoryBuilder().setGitDir(theDir) + .setWorkTree(repo1Parent.getParentFile()).build(); assertEqualsPath(theDir, r.getDirectory()); assertEqualsPath(repo1Parent.getParentFile(), r.getWorkDir()); assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); @@ -142,7 +143,7 @@ public void test000_openrepo_default_workDirSet() throws IOException { repo1initial.close(); File theDir = new File(repo1Parent, Constants.DOT_GIT); - Repository r = new FileRepository(null, repo1Parent); + FileRepository r = new FileRepositoryBuilder().setWorkTree(repo1Parent).build(); assertEqualsPath(theDir, r.getDirectory()); assertEqualsPath(repo1Parent, r.getWorkDir()); assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); @@ -167,7 +168,7 @@ public void test000_openrepo_default_absolute_workdirconfig() repo1initial.close(); File theDir = new File(repo1Parent, Constants.DOT_GIT); - Repository r = new FileRepository(theDir, null); + FileRepository r = new FileRepositoryBuilder().setGitDir(theDir).build(); assertEqualsPath(theDir, r.getDirectory()); assertEqualsPath(workdir, r.getWorkDir()); assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); @@ -192,7 +193,7 @@ public void test000_openrepo_default_relative_workdirconfig() repo1initial.close(); File theDir = new File(repo1Parent, Constants.DOT_GIT); - Repository r = new FileRepository(theDir, null); + FileRepository r = new FileRepositoryBuilder().setGitDir(theDir).build(); assertEqualsPath(theDir, r.getDirectory()); assertEqualsPath(workdir, r.getWorkDir()); assertEqualsPath(new File(theDir, "index"), r.getIndexFile()); @@ -210,14 +211,17 @@ public void test000_openrepo_alternate_index_file_and_objdirs() 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() }; + File altObjDir = db.getObjectsDirectory(); 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 FileRepository(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(indexFile, r.getIndexFile()); diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties index 91b67daf8..f6e4ea5b4 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties @@ -298,6 +298,7 @@ remoteDoesNotSupportSmartHTTPPush=remote does not support smart HTTP push remoteHungUpUnexpectedly=remote hung up unexpectedly remoteNameCantBeNull=Remote name can't be null. repositoryAlreadyExists=Repository already exists: {0} +repositoryConfigFileInvalid=Repository config file {0} invalid {1} repositoryNotFound=repository not found: {0} requiredHashFunctionNotAvailable=Required hash function {0} not available. resolvingDeltas=Resolving deltas diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java index 0c64b9edd..377e9c119 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java @@ -357,6 +357,7 @@ public static JGitText get() { /***/ public String remoteHungUpUnexpectedly; /***/ public String remoteNameCantBeNull; /***/ public String repositoryAlreadyExists; + /***/ public String repositoryConfigFileInvalid; /***/ public String repositoryNotFound; /***/ public String requiredHashFunctionNotAvailable; /***/ public String resolvingDeltas; 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 000000000..f5dd7eec7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java @@ -0,0 +1,613 @@ +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.util.FS; +import org.eclipse.jgit.util.SystemReader; + +/** + * Base builder to customize repository construction. + *

+ * Repository implementations may subclass this builder in order to add custom + * repository detection methods. + * + * @param + * type of the repository builder. + * @param + * type of the repository that is constructed. + * @see RepositoryBuilder + * @see FileRepositoryBuilder + */ +public class BaseRepositoryBuilder { + private FS fs; + + private File gitDir; + + private File objectDirectory; + + private List alternateObjectDirectories; + + private File indexFile; + + private File workTree; + + /** Directories limiting the search for a Git repository. */ + private List 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. + *

+ * 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. + *

+ * 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(); + alternateObjectDirectories.add(other); + } + return self(); + } + + /** + * Add alternate object directories to the search list. + *

+ * 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 inList) { + if (inList != null) { + for (File path : inList) + addAlternateObjectDirectory(path); + } + return self(); + } + + /** + * Add alternate object directories to the search list. + *

+ * 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 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). + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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(); + ceilingDirectories.add(root); + } + return self(); + } + + /** + * Add ceiling directories to the search list. + *

+ * 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 inList) { + if (inList != null) { + for (File path : inList) + addCeilingDirectory(path); + } + return self(); + } + + /** + * Add ceiling directories to the search list. + *

+ * 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. + *

+ * 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}. + *

+ * 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. + *

+ * 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}. + *

+ * 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. + *

+ * 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. + *

+ * 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().eitherGIT_DIRorGIT_WORK_TREEmustBePassed); + } + + /** + * 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. + *

+ * 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. + *

+ * 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/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java index 86ae5fada..f45ffdf14 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java @@ -56,7 +56,6 @@ import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.FileObjectDatabase.AlternateHandle; import org.eclipse.jgit.lib.FileObjectDatabase.AlternateRepository; -import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.SystemReader; /** @@ -95,176 +94,41 @@ public class FileRepository extends Repository { /** * 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. + * rules by running {@link FileRepositoryBuilder}. This constructor is the + * same as saying: * - * @param d + *

+	 * new FileRepositoryBuilder().setGitDir(gitDir).build()
+	 * 
+ * + * @param gitDir * GIT_DIR (the location of the repository metadata). * @throws IOException * the repository appears to already exist but cannot be * accessed. + * @see FileRepositoryBuilder */ - public FileRepository(final File d) throws IOException { - this(d, null, null, null, null); // go figure it out + public FileRepository(final File gitDir) throws IOException { + this(new FileRepositoryBuilder().setGitDir(gitDir).setup()); } - /** - * 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 FileRepository(final File d, final File workTree) throws IOException { - this(d, workTree, null, null, null); // go figure it out - } + FileRepository(final BaseRepositoryBuilder options) throws IOException { + super(options); - /** - * 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 FileRepository(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. - * - * @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. - */ - public FileRepository(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); - repoConfig = new FileBasedConfig(userConfig, fs.resolve(gitDir, "config")); + userConfig = SystemReader.getInstance().openUserConfig(getFS()); + repoConfig = new FileBasedConfig(userConfig, getFS().resolve(getDirectory(), "config")); loadUserConfig(); loadRepoConfig(); - 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(repoConfig, // - fs.resolve(objectDir, ""), // - alternateObjectDir, // - fs); - } else { - objectDatabase = new ObjectDirectory(repoConfig, // - fs.resolve(gitDir, "objects"), // - alternateObjectDir, // - fs); - } - - if (indexFile != null) - this.indexFile = indexFile; - else - this.indexFile = new File(gitDir, "index"); + objectDatabase = new ObjectDirectory(repoConfig, // + options.getObjectDirectory(), // + options.getAlternateObjectDirectories(), // + getFS()); if (objectDatabase.exists()) { final String repositoryFormatVersion = getConfig().getString( @@ -314,13 +178,13 @@ public void create(boolean bare) throws IOException { final FileBasedConfig cfg = getConfig(); if (cfg.getFile().exists()) { throw new IllegalStateException(MessageFormat.format( - JGitText.get().repositoryAlreadyExists, gitDir)); + JGitText.get().repositoryAlreadyExists, getDirectory())); } - gitDir.mkdirs(); + getDirectory().mkdirs(); refs.create(); objectDatabase.create(); - new File(gitDir, "branches").mkdir(); + new File(getDirectory(), "branches").mkdir(); RefUpdate head = updateRef(Constants.HEAD); head.disableRefLog(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepositoryBuilder.java new file mode 100644 index 000000000..c0220d979 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepositoryBuilder.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import java.io.File; +import java.io.IOException; + +/** + * Constructs a {@link FileRepository}. + *

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

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

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

+ * If an option was not set, the build method will try to default the option + * based on other options. If insufficient information is available, an + * exception is thrown to the caller. + * + * @return a repository matching this configuration. + * @throws IllegalArgumentException + * insufficient parameters were set. + * @throws IOException + * the repository could not be accessed to configure the rest of + * the builder's parameters. + */ + @Override + public FileRepository build() throws IOException { + return new FileRepository(setup()); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index 09411a453..939d923a7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -85,10 +85,10 @@ public abstract class Repository { private final AtomicInteger useCnt = new AtomicInteger(1); /** Metadata directory holding the repository's critical files. */ - protected File gitDir; + private final File gitDir; /** File abstraction used to resolve paths. */ - protected FS fs; + private final FS fs; private GitIndex index; @@ -96,14 +96,22 @@ public abstract class Repository { static private final List allListeners = new Vector(); // thread safe /** If not bare, the top level directory of the working files. */ - protected File workDir; + private final File workDir; /** If not bare, the index file caching the working file states. */ - protected File indexFile; + private final File indexFile; - /** Initialize a new repository instance. */ - protected Repository() { - // Empty constructor, defined protected to require subclassing. + /** + * Initialize a new repository instance. + * + * @param options + * options to configure the repository. + */ + protected Repository(final BaseRepositoryBuilder options) { + gitDir = options.getGitDir(); + fs = options.getFS(); + workDir = options.getWorkTree(); + indexFile = options.getIndexFile(); } /** @@ -1018,16 +1026,6 @@ public File getWorkDir() throws IllegalStateException { 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. 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 000000000..4b0e1f690 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java @@ -0,0 +1,72 @@ +/* + * 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; + +/** + * Base class to support constructing a {@link Repository}. + *

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

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

+ * new RepositoryBuilder() //
+ * 		.setGitDir(gitDirArgument) // --git-dir if supplied, no-op if null
+ * 		.readEnviroment() // scan environment GIT_* variables
+ * 		.findGitDir() // scan up the file system tree
+ * 		.build()
+ * 
+ * + * @see FileRepositoryBuilder + */ +public class RepositoryBuilder extends + BaseRepositoryBuilder { + // Empty implementation, everything is inherited. +} From 203bd6626767015dfb04d421c572b26a34e9cecf Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 25 Jun 2010 17:53:03 -0700 Subject: [PATCH 031/103] Rename Repository getWorkDir to getWorkTree This better matches with the name used in the environment (GIT_WORK_TREE), in the configuration file (core.worktree), and in our builder object. Since we are already breaking a good chunk of other code related to repository access, and this fairly easy to fix in an application's code base, I'm not going to offer the wrapper getWorkDir() method. Change-Id: Ib698ba4bbc213c48114f342378cecfe377e37bb7 Signed-off-by: Shawn O. Pearce --- .../junit/LocalDiskRepositoryTestCase.java | 2 +- .../src/org/eclipse/jgit/pgm/Clone.java | 2 +- .../src/org/eclipse/jgit/pgm/Rm.java | 2 +- .../org/eclipse/jgit/pgm/eclipse/Ipzilla.java | 2 +- .../eclipse/jgit/lib/T0007_GitIndexTest.java | 2 +- .../org/eclipse/jgit/api/MergeCommandTest.java | 18 +++++++++--------- .../jgit/lib/RepositorySetupWorkDirTest.java | 6 +++--- .../eclipse/jgit/lib/RepositoryTestCase.java | 4 ++-- .../tst/org/eclipse/jgit/lib/T0003_Basic.java | 18 +++++++++--------- .../src/org/eclipse/jgit/api/MergeCommand.java | 2 +- .../src/org/eclipse/jgit/lib/IndexDiff.java | 2 +- .../src/org/eclipse/jgit/lib/Repository.java | 17 +++++++++-------- 12 files changed, 39 insertions(+), 38 deletions(-) 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 6920e9188..f45e5f67a 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 @@ -324,7 +324,7 @@ protected int runHook(final Repository db, final File hook, 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.pgm/src/org/eclipse/jgit/pgm/Clone.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java index 60663438f..cc59a6eb5 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 @@ -183,7 +183,7 @@ private void doCheckout(final Ref branch) throws IOException { 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/Rm.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java index 1b8711dc9..b0cc5248c 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,7 +67,7 @@ class Rm extends TextBuiltin { @Override protected void run() throws Exception { - root = db.getWorkDir(); + root = db.getWorkTree(); final DirCache dirc = DirCache.lock(db); final DirCacheBuilder edit = dirc.builder(); 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 4f0e338e8..b563f0791 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 @@ protected void run() throws Exception { } 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.test/exttst/org/eclipse/jgit/lib/T0007_GitIndexTest.java b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/T0007_GitIndexTest.java index dd3b51efc..c5591b9bf 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 void run() { 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/MergeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java index c965c6766..773d2f055 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 void testFastForwardWithFiles() throws Exception { 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 void testMultipleHeads() throws Exception { 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 @@ private void createBranch(ObjectId objectId, String branchName) throws IOExcepti } 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 @@ private void addNewFileToIndex(String filename) throws IOException, 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/lib/RepositorySetupWorkDirTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java index 187d3ae9d..52cb46bae 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java @@ -73,7 +73,7 @@ public void testWorkdirIsParentDir_CreateRepositoryFromDotGitGitDir() throws Exception { File gitDir = getFile("workdir", Constants.DOT_GIT); Repository repo = new FileRepository(gitDir); - String workdir = repo.getWorkDir().getName(); + String workdir = repo.getWorkTree().getName(); assertEquals(workdir, "workdir"); } @@ -132,7 +132,7 @@ public void testNotBare_CreateRepositoryFromGitDirOnlyWithBareConfigFalse() public void testExceptionThrown_BareRepoGetWorkDir() throws Exception { File gitDir = getFile("workdir"); try { - new FileRepository(gitDir).getWorkDir(); + new FileRepository(gitDir).getWorkTree(); fail("Expected IllegalStateException missing"); } catch (IllegalStateException e) { // expected @@ -202,7 +202,7 @@ private void assertGitdirPath(Repository repo, String... expected) 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/RepositoryTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java index 64df65b4c..cc4572244 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 @@ -83,7 +83,7 @@ protected static void copyFile(final File src, final File dst) 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; } @@ -111,6 +111,6 @@ protected static void checkFile(File f, final String checkData) 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/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java index 8dd5ae2bd..aab57aa59 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java @@ -104,7 +104,7 @@ public void test000_openrepo_default_gitDirSet() throws IOException { File theDir = new File(repo1Parent, Constants.DOT_GIT); 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()); } @@ -125,7 +125,7 @@ public void test000_openrepo_default_gitDirAndWorkTreeSet() throws IOException { 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()); } @@ -145,7 +145,7 @@ public void test000_openrepo_default_workDirSet() throws IOException { File theDir = new File(repo1Parent, Constants.DOT_GIT); 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()); } @@ -170,7 +170,7 @@ public void test000_openrepo_default_absolute_workdirconfig() File theDir = new File(repo1Parent, Constants.DOT_GIT); 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()); } @@ -195,7 +195,7 @@ public void test000_openrepo_default_relative_workdirconfig() File theDir = new File(repo1Parent, Constants.DOT_GIT); 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()); } @@ -223,7 +223,7 @@ public void test000_openrepo_alternate_index_file_and_objdirs() .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()); assertNotNull(r.mapCommit("6db9c2ebf75590eef973081736730a9ea169a0c4")); @@ -726,10 +726,10 @@ public void test30_stripWorkDir() { 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/src/org/eclipse/jgit/api/MergeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java index 00a030915..76a3bc479 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java @@ -163,7 +163,7 @@ private void checkoutNewHead(RevWalk revWalk, RevCommit headCommit, 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/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java index d655a97eb..2e491af5a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java @@ -89,7 +89,7 @@ public IndexDiff(Tree tree, GitIndex index) { * @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/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index 939d923a7..7b32d69a0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -96,7 +96,7 @@ public abstract class Repository { static private final List allListeners = new Vector(); // thread safe /** If not bare, the top level directory of the working files. */ - private final File workDir; + private final File workTree; /** If not bare, the index file caching the working file states. */ private final File indexFile; @@ -110,7 +110,7 @@ public abstract class Repository { protected Repository(final BaseRepositoryBuilder options) { gitDir = options.getGitDir(); fs = options.getFS(); - workDir = options.getWorkTree(); + workTree = options.getWorkTree(); indexFile = options.getIndexFile(); } @@ -886,7 +886,7 @@ public RepositoryState getRepositoryState() { 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(getDirectory(), ".dotest-merge").exists()) return RepositoryState.REBASING_INTERACTIVE; @@ -1011,19 +1011,20 @@ public static String stripWorkDir(File workDir, File file) { * @return the "bare"-ness of this Repository */ public boolean isBare() { - return workDir == null; + return workTree == null; } /** - * @return the workdir file, i.e. where the files are checked out + * @return the root directory of the working tree, where files are checked + * out for viewing and editing. * @throws IllegalStateException - * if the repository is "bare" + * if the repository is bare and has no working directory. */ - public File getWorkDir() throws IllegalStateException { + public File getWorkTree() throws IllegalStateException { if (isBare()) throw new IllegalStateException( JGitText.get().bareRepositoryNoWorkdirAndIndex); - return workDir; + return workTree; } /** From 8a9844b2afc4e30e60759c03a1428dc99a13619e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Jun 2010 11:12:40 -0700 Subject: [PATCH 032/103] Redo event listeners to be more generic Replace the old crude event listener system with a much more generic implementation, patterned after the event dispatch techniques used in Google Web Toolkit 1.5 and later. Each event delivers to an interface that defines a single method, and the event itself is what performs the delivery in a type-safe way through its own dispatch method. Listeners are registered in a generic listener list, indexed by the interface they implement and wish to receive an event for. Delivery of events is performed by looping through all listeners implementing the event's corresponding listener interface, and using the event's own dispatch method to deliver the event. This is the classical "double dispatch" pattern for event delivery. Listeners can be unregistered by invoking remove() on their registration handle. This change therefore requires application code to track the handle if it wishes to remove the listener at a later point in time. Event delivery is now exposed as a generic public method on the Repository class, making it easier for any type of message to be sent out to any type of listener that has registered, without needing to pre-arrange for type-safe fireFoo() methods. New event types can be added in the future simply by defining a new RepositoryEvent subclass and a corresponding RepositoryListener interface that it dispatches to. By always adding new events through a new interface, we never need to worry about defining an Adapter to provide default no-op implementations of new event methods. Change-Id: I651417b3098b9afc93d91085e9f0b2265df8fc81 Signed-off-by: Shawn O. Pearce --- org.eclipse.jgit/META-INF/MANIFEST.MF | 1 + .../{lib => events}/IndexChangedEvent.java | 28 ++-- .../IndexChangedListener.java} | 25 ++-- .../ListenerHandle.java} | 40 +++--- .../org/eclipse/jgit/events/ListenerList.java | 130 ++++++++++++++++++ .../eclipse/jgit/events/RefsChangedEvent.java | 57 ++++++++ .../RefsChangedListener.java} | 31 ++--- .../eclipse/jgit/events/RepositoryEvent.java | 95 +++++++++++++ .../{lib => events}/RepositoryListener.java | 28 +--- .../src/org/eclipse/jgit/lib/GitIndex.java | 5 +- .../org/eclipse/jgit/lib/RefDirectory.java | 3 +- .../src/org/eclipse/jgit/lib/Repository.java | 98 ++++--------- 12 files changed, 371 insertions(+), 170 deletions(-) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => events}/IndexChangedEvent.java (74%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib/RepositoryAdapter.java => events/IndexChangedListener.java} (84%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib/RefsChangedEvent.java => events/ListenerHandle.java} (75%) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedEvent.java rename org.eclipse.jgit/src/org/eclipse/jgit/{lib/RepositoryChangedEvent.java => events/RefsChangedListener.java} (78%) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => events}/RepositoryListener.java (77%) diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index 258e6781b..81441dbdb 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.lib;version="0.9.0", diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java similarity index 74% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexChangedEvent.java rename to org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java index c866db531..a54288ee9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexChangedEvent.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, Robin Rosenberg + * 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,17 @@ * 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); +/** Describes a change to one or more paths in the index file. */ +public class IndexChangedEvent extends RepositoryEvent { + @Override + public Class getListenerType() { + return IndexChangedListener.class; } @Override - public String toString() { - return "IndexChangedEvent[" + getRepository() + "]"; + public void dispatch(IndexChangedListener listener) { + listener.onIndexChanged(this); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedListener.java similarity index 84% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java rename to org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedListener.java index e43c33ad7..d41ef74ee 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedListener.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 IndexChangedEvent}s. */ +public interface IndexChangedListener extends RepositoryListener { + /** + * Invoked when any change is made to the index. + * + * @param event + * information about the changes. + */ + void onIndexChanged(IndexChangedEvent event); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefsChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerHandle.java similarity index 75% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/RefsChangedEvent.java rename to org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerHandle.java index 705c6138e..ef90b2205 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefsChangedEvent.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerHandle.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, Robin Rosenberg + * 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 RefsChangedEvent extends RepositoryChangedEvent { - /** - * Create an event describing reference changes in a repository. - * - * @param repository - * the repository whose references recently changed. - */ - public RefsChangedEvent(final Repository repository) { - super(repository); +/** Tracks a previously registered {@link RepositoryListener}. */ +public class ListenerHandle { + private final ListenerList parent; + + final Class type; + + final RepositoryListener listener; + + ListenerHandle(ListenerList parent, + Class 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 "RefsChangedEvent[" + 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 000000000..24e2d4da0 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.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.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, CopyOnWriteArrayList> lists = new ConcurrentHashMap, CopyOnWriteArrayList>(); + + /** + * 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); + } + + /** + * Add a listener to the list. + * + * @param + * 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 ListenerHandle addListener( + Class type, T listener) { + ListenerHandle handle = new ListenerHandle(this, type, listener); + add(handle); + return handle; + } + + /** + * Dispatch an event to all interested listeners. + *

+ * 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 list = lists.get(event.getListenerType()); + if (list != null) { + for (ListenerHandle handle : list) + event.dispatch(handle.listener); + } + } + + private void add(ListenerHandle handle) { + List list = lists.get(handle.type); + if (list == null) { + CopyOnWriteArrayList newList; + + newList = new CopyOnWriteArrayList(); + list = lists.putIfAbsent(handle.type, newList); + if (list == null) + list = newList; + } + list.add(handle); + } + + void remove(ListenerHandle handle) { + List 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 000000000..36af3f8b7 --- /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 { + @Override + public Class getListenerType() { + return RefsChangedListener.class; + } + + @Override + public void dispatch(RefsChangedListener listener) { + listener.onRefsChanged(this); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedListener.java similarity index 78% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java rename to org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedListener.java index 495049ce7..9c0f4ed58 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedListener.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 RefsChangedEvent}s. */ +public interface RefsChangedListener extends RepositoryListener { /** - * @return the repository that was changed + * Invoked when any reference changes. + * + * @param event + * information about the changes. */ - public Repository getRepository() { - return repository; - } - - @Override - public String toString() { - return "RepositoryChangedEvent[" + repository + "]"; - } + 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 000000000..fa1b1bd41 --- /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 + * 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 + * type of listener this event dispatches to. + */ +public abstract class RepositoryEvent { + private volatile Repository repository; + + /** + * Set the repository this event occurred on. + *

+ * 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 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 similarity index 77% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryListener.java rename to org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryListener.java index 0473093e2..4f951e5f8 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 + * 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. - *

- * It currently does not 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/GitIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java index 42c16cb10..929cd2d2e 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.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 boolean isChanged() { public void rereadIfNecessary() throws IOException { if (cacheFile.exists() && cacheFile.lastModified() != lastCacheTime) { read(); - db.fireIndexChanged(); + db.fireEvent(new IndexChangedEvent()); } } @@ -307,7 +308,7 @@ public void write() throws IOException { changed = false; statDirty = false; lastCacheTime = cacheFile.lastModified(); - db.fireIndexChanged(); + db.fireEvent(new IndexChangedEvent()); } finally { if (!lock.delete()) throw new IOException( diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java index 0cbcf2b5c..13e9c22d9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java @@ -75,6 +75,7 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.ObjectWritingException; +import org.eclipse.jgit.events.RefsChangedEvent; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; @@ -829,7 +830,7 @@ private void fireRefsChanged() { 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/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index 7b32d69a0..2d99f6587 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -49,7 +49,6 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -57,7 +56,6 @@ 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; @@ -65,6 +63,8 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; 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; @@ -82,6 +82,13 @@ * This class is thread-safe. */ 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. */ @@ -92,8 +99,7 @@ public abstract class Repository { private GitIndex index; - private final List listeners = new Vector(); // thread safe - static private final List allListeners = new Vector(); // thread safe + private final ListenerList myListeners = new ListenerList(); /** If not bare, the top level directory of the working files. */ private final File workTree; @@ -114,6 +120,26 @@ protected Repository(final BaseRepositoryBuilder options) { indexFile = options.getIndexFile(); } + /** @return listeners observing only events on this repository. */ + public ListenerList getListenerList() { + return myListeners; + } + + /** + * Fire an event to all registered listeners. + *

+ * The source repository of the event is automatically set to this + * repository, before the event is delivered to any listeners. + * + * @param event + * the event to deliver. + */ + public void fireEvent(RepositoryEvent event) { + event.setRepository(this); + myListeners.dispatch(event); + globalListeners.dispatch(event); + } + /** * Create a new Git repository. *

@@ -1027,70 +1053,6 @@ public File getWorkTree() throws IllegalStateException { return 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 all; - synchronized (listeners) { - all = new ArrayList(listeners); - } - synchronized (allListeners) { - all.addAll(allListeners); - } - for (final RepositoryListener l : all) { - l.refsChanged(event); - } - } - - void fireIndexChanged() { - final IndexChangedEvent event = new IndexChangedEvent(this); - List all; - synchronized (listeners) { - all = new ArrayList(listeners); - } - synchronized (allListeners) { - all.addAll(allListeners); - } - for (final RepositoryListener l : all) { - l.indexChanged(event); - } - } - /** * Force a scan for changed refs. * From ffe0614d4db653cbcd48c19e9f599fd87cdcfaba Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 25 Jun 2010 17:46:07 -0700 Subject: [PATCH 033/103] Allow Repository.getDirectory() to be null Some types of repositories might not be stored on local disk. For these, they will most likely return null for getDirectory() as the java.io.File type cannot describe where their storage is, its not in the host's filesystem. Document that getDirectory() can return null now, and update all current non-test callers in JGit that might run into problems on such repositories. For the most part, just act like its bare. Change-Id: I061236a691372a267fd7d41f0550650e165d2066 Signed-off-by: Shawn O. Pearce --- .../jgit/http/server/TextFileServlet.java | 2 ++ .../http/server/resolver/FileResolver.java | 4 +++- .../src/org/eclipse/jgit/pgm/Glog.java | 8 +++++--- .../org/eclipse/jgit/api/CommitCommand.java | 13 +++++++------ .../src/org/eclipse/jgit/lib/Repository.java | 18 +++++++++++------- .../org/eclipse/jgit/lib/RepositoryCache.java | 10 ++++++++-- .../eclipse/jgit/transport/FetchProcess.java | 6 ++++-- .../jgit/transport/RemoteRefUpdate.java | 2 +- 8 files changed, 41 insertions(+), 22 deletions(-) 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 5bf5546cf..650059bd3 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 @@ public void doGet(final HttpServletRequest req, 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/resolver/FileResolver.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/FileResolver.java index cc062dbe8..296725b67 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 @@ protected boolean isExportOk(HttpServletRequest req, String repositoryName, 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.pgm/src/org/eclipse/jgit/pgm/Glog.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java index 3dfd8ff62..ae11f6731 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 @@ protected RevWalk createWalk() { } 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/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java index f12c94b33..1cf2fe669 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -171,17 +171,18 @@ public RevCommit call() throws NoHeadException, NoMessageException, Result rc = ru.update(); switch (rc) { case NEW: - case FAST_FORWARD: + case FAST_FORWARD: { setCallable(false); - if (state == RepositoryState.MERGING_RESOLVED) { + File meta = repo.getDirectory(); + if (state == RepositoryState.MERGING_RESOLVED + && meta != null) { // 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(); + new File(meta, Constants.MERGE_HEAD).delete(); + new File(meta, Constants.MERGE_MSG).delete(); } return revCommit; + } case REJECTED: case LOCK_FAILURE: throw new ConcurrentRefUpdateException( 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 2d99f6587..6b91481d3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -165,9 +165,7 @@ public void create() throws IOException { */ 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; } @@ -712,7 +710,13 @@ protected void doClose() { 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 + "]"; } /** @@ -908,7 +912,7 @@ static byte[] gitInternalSlash(byte[] bytes) { * @return an important state */ public RepositoryState getRepositoryState() { - if (isBare()) + if (isBare() || getDirectory() == null) return RepositoryState.BARE; // Pre Git-1.6 logic @@ -1096,7 +1100,7 @@ public abstract ReflogReader getReflogReader(String refName) * if the repository is "bare" */ public String readMergeCommitMsg() throws IOException { - if (isBare()) + if (isBare() || getDirectory() == null) throw new IllegalStateException( JGitText.get().bareRepositoryNoWorkdirAndIndex); @@ -1123,7 +1127,7 @@ public String readMergeCommitMsg() throws IOException { * if the repository is "bare" */ public List readMergeHeads() throws IOException { - if (isBare()) + if (isBare() || getDirectory() == null) throw new IllegalStateException( JGitText.get().bareRepositoryNoWorkdirAndIndex); 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 2bea8fecf..39d734a32 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java @@ -120,7 +120,10 @@ public static Repository open(final Key location, final boolean mustExist) * 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 +136,10 @@ public static void register(final Repository db) { * 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. */ 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 fc203f69c..27505bef6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java @@ -271,8 +271,10 @@ private void removeFetchHeadRecord(final ObjectId want) { } 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()); 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 1b17c9f0f..37e03fd62 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 String toString() { + "..." + (newObjectId != null ? newObjectId.abbreviate(localDb).name() : "(null)") + (fastForward ? ", fastForward" : "") + ", srcRef=" + srcRef + (forceUpdate ? ", forceUpdate" : "") + ", message=" + (message != null ? "\"" - + message + "\"" : "null") + ", " + localDb.getDirectory() + "]"; + + message + "\"" : "null") + "]"; } } From 767fd314ada5649f77c18e9f4e9b744261301520 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 24 Jun 2010 11:32:58 -0700 Subject: [PATCH 034/103] Use getObjectsDatabase().getDirectory() to find objects Only the ObjectDirectory type of database knows where to find the objects directory on the local filesystem, so defer to it whenever we need to know where the objects reside. Since this is the type returned by FileRepository's getObjectDatabase() method, we mostly don't have to do much other than use a slightly longer invocation. Change-Id: Ie5f58132a6411b56c3acad73646ad169d78a0654 Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/lib/ConcurrentRepackTest.java | 2 +- .../tst/org/eclipse/jgit/lib/PackWriterTest.java | 2 +- .../jgit/lib/SampleDataRepositoryTestCase.java | 2 +- .../tst/org/eclipse/jgit/lib/T0003_Basic.java | 16 ++++++++-------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java index 69430ed33..e3a62faf2 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java @@ -245,7 +245,7 @@ private static void touch(final long begin, final File dir) { } 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/lib/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackWriterTest.java index 76b663a29..c291968b7 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackWriterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackWriterTest.java @@ -238,7 +238,7 @@ public void testWritePack2DeltasReuseOffsets() throws IOException { * @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, 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 10c005679..7bc9bb22e 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 @@ protected void setUp() throws Exception { "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/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java index aab57aa59..484692ee2 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java @@ -106,7 +106,7 @@ public void test000_openrepo_default_gitDirSet() throws IOException { assertEqualsPath(theDir, r.getDirectory()); 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()); } /** @@ -127,7 +127,7 @@ public void test000_openrepo_default_gitDirAndWorkTreeSet() throws IOException { assertEqualsPath(theDir, r.getDirectory()); 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()); } /** @@ -147,7 +147,7 @@ public void test000_openrepo_default_workDirSet() throws IOException { assertEqualsPath(theDir, r.getDirectory()); 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()); } /** @@ -172,7 +172,7 @@ public void test000_openrepo_default_absolute_workdirconfig() assertEqualsPath(theDir, r.getDirectory()); 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()); } /** @@ -197,7 +197,7 @@ public void test000_openrepo_default_relative_workdirconfig() assertEqualsPath(theDir, r.getDirectory()); 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()); } /** @@ -211,7 +211,7 @@ public void test000_openrepo_alternate_index_file_and_objdirs() File repo1Parent = new File(trash.getParentFile(), "r1"); File indexFile = new File(trash, "idx"); File objDir = new File(trash, "../obj"); - File altObjDir = db.getObjectsDirectory(); + File altObjDir = db.getObjectDatabase().getDirectory(); Repository repo1initial = new FileRepository(new File(repo1Parent, Constants.DOT_GIT)); repo1initial.create(); repo1initial.close(); @@ -225,7 +225,7 @@ public void test000_openrepo_alternate_index_file_and_objdirs() assertEqualsPath(theDir, r.getDirectory()); 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. @@ -327,7 +327,7 @@ public void test006_ReadUglyConfig() throws IOException, public void test007_Open() throws IOException { 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()); } From a2208be6aaf4a3763beb1b6e0ef374d77570a165 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 25 Jun 2010 18:05:13 -0700 Subject: [PATCH 035/103] Extract ObjectToPack to be top-level This shortens the implementation within PackWriter, and starts to open the door for some other refactorings based on changing the ObjectToPack to be a public part of the API. Change-Id: Id849cbffc4de20b903e844a2de7737eeb8b7a3ff Signed-off-by: Shawn O. Pearce --- .../org/eclipse/jgit/lib/ObjectToPack.java | 191 ++++++++++++++++++ .../src/org/eclipse/jgit/lib/PackWriter.java | 143 ------------- 2 files changed, 191 insertions(+), 143 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java new file mode 100644 index 000000000..75378cec1 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2008-2010, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import java.io.IOException; + +import org.eclipse.jgit.transport.PackedObjectInfo; + +/** + * 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. + * + */ +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: + *

    + *
  • 1 bit: wantWrite
  • + *
  • 3 bits: type
  • + *
  • 28 bits: deltaDepth
  • + *
+ */ + private int flags; + + /** + * Construct object for specified object id.
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/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java index 15ac17425..4128345ab 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java @@ -61,7 +61,6 @@ 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; /** @@ -899,146 +898,4 @@ public void addObject(final RevObject object) } 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: - *
    - *
  • 1 bit: wantWrite
  • - *
  • 3 bits: type
  • - *
  • 28 bits: deltaDepth
  • - *
- */ - private int flags; - - /** - * Construct object for specified object id.
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; - } - } } From 6fc3ecac848b357891da8a9749d35090dbb0ace3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 25 Jun 2010 18:23:44 -0700 Subject: [PATCH 036/103] Extract PackFile specific code to ObjectToPack subclass The ObjectReader class is dual-purposed into being a factory for the ObjectToPack, permitting specific ObjectDatabase implementations to override the method and offer their own custom subclass of the generic ObjectToPack class. By allowing them to directly extend the type, each implementation can add custom fields to support tracking where an object is stored, without incurring any additional penalties like a parallel Map would cost. The reader was chosen to act as a factory rather than the database, as the reader will eventually be tied more tightly with the ObjectWalk and TreeWalk. During object enumeration the reader would have had to load the object for the RevWalk, and may chose to cache object position data internally so it can later be reused and fed into the ObjectToPack instance supplied to the PackWriter. Since a reader is not thread-safe, and is scoped to this PackWriter and its internal ObjectWalk, its a great place for the database to perform caching, if any. Right now this change goes a bit backwards by changing what should be generic ObjectToPack references inside of PackWriter to the very PackFile specific LocalObjectToPack subclass. We will correct these in a later commit as we start to refine what the ObjectToPack API will eventually look like in order to better support the PackWriter. Change-Id: I9f047d26b97e46dee3bc0ccb4060bbebedbe8ea9 Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/lib/LocalObjectToPack.java | 78 +++++++++++++++++++ .../org/eclipse/jgit/lib/ObjectReader.java | 24 ++++++ .../org/eclipse/jgit/lib/ObjectToPack.java | 54 +++++-------- .../src/org/eclipse/jgit/lib/PackWriter.java | 38 ++++----- .../org/eclipse/jgit/lib/WindowCursor.java | 6 ++ 5 files changed, 148 insertions(+), 52 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java new file mode 100644 index 000000000..a102e96ee --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/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.lib; + +import java.io.IOException; + +import org.eclipse.jgit.revwalk.RevObject; + +/** {@link ObjectToPack} for {@link ObjectDirectory}. */ +class LocalObjectToPack extends ObjectToPack { + /** Pack to reuse compressed data from, otherwise null. */ + private PackFile copyFromPack; + + /** Offset of the object's header in {@link #copyFromPack}. */ + private long copyOffset; + + LocalObjectToPack(RevObject obj) { + super(obj); + } + + 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; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java index c95130ce2..de4b3eea5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java @@ -46,6 +46,7 @@ import java.io.IOException; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.revwalk.RevObject; /** Reads an {@link ObjectDatabase} for a single thread. */ public abstract class ObjectReader { @@ -102,6 +103,29 @@ public ObjectLoader openObject(AnyObjectId objectId) public abstract ObjectLoader openObject(AnyObjectId objectId, int typeHint) throws MissingObjectException, IOException; + /** + * Allocate a new {@code PackWriter} state structure for an object. + *

+ * {@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 override this method to provide their + * own subclass with additional object state, such as to remember what file + * and position contains the object's data. + *

+ * The default implementation of this object does not provide very efficient + * packing support; it inflates the object on the fly through {@code + * openObject} and deflates it again into the generated stream. + * + * @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) { + return new ObjectToPack(obj, obj.getType()); + } + /** * Release any resources used by this reader. *

diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java index 75378cec1..dfc3d6f4c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java @@ -44,26 +44,20 @@ package org.eclipse.jgit.lib; -import java.io.IOException; - +import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.transport.PackedObjectInfo; /** - * 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. - * + * Per-object state used by {@link PackWriter}. + *

+ * {@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. */ -class ObjectToPack extends PackedObjectInfo { +public 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: *

    @@ -75,19 +69,30 @@ class ObjectToPack extends PackedObjectInfo { private int flags; /** - * Construct object for specified object id.
    By default object is - * marked as not written and non-delta packed (as a whole object). + * 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. */ - ObjectToPack(AnyObjectId src, final int type) { + public ObjectToPack(AnyObjectId src, final int type) { super(src); flags |= type << 1; } + /** + * 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 @@ -145,23 +150,6 @@ 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; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java index 4128345ab..2079931ea 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java @@ -152,13 +152,13 @@ public class PackWriter { private static final int PACK_VERSION_GENERATED = 2; @SuppressWarnings("unchecked") - private final List objectsLists[] = new List[Constants.OBJ_TAG + 1]; + private final List objectsLists[] = new List[Constants.OBJ_TAG + 1]; { - objectsLists[0] = Collections. emptyList(); - objectsLists[Constants.OBJ_COMMIT] = new ArrayList(); - objectsLists[Constants.OBJ_TREE] = new ArrayList(); - objectsLists[Constants.OBJ_BLOB] = new ArrayList(); - objectsLists[Constants.OBJ_TAG] = new ArrayList(); + objectsLists[0] = Collections. emptyList(); + objectsLists[Constants.OBJ_COMMIT] = new ArrayList(); + objectsLists[Constants.OBJ_TREE] = new ArrayList(); + objectsLists[Constants.OBJ_BLOB] = new ArrayList(); + objectsLists[Constants.OBJ_TAG] = new ArrayList(); } private final ObjectIdSubclassMap objectsMap = new ObjectIdSubclassMap(); @@ -557,8 +557,8 @@ public void writeIndex(final OutputStream indexStream) throws IOException { private List sortByName() { if (sortedByName == null) { sortedByName = new ArrayList(objectsMap.size()); - for (List list : objectsLists) { - for (ObjectToPack otp : list) + for (List list : objectsLists) { + for (LocalObjectToPack otp : list) sortedByName.add(otp); } Collections.sort(sortedByName); @@ -606,8 +606,8 @@ public void writePack(OutputStream packStream) throws IOException { private void searchForReuse() throws IOException { initMonitor.beginTask(SEARCHING_REUSE_PROGRESS, getObjectsNumber()); final Collection reuseLoaders = new ArrayList(); - for (List list : objectsLists) { - for (ObjectToPack otp : list) { + for (List list : objectsLists) { + for (LocalObjectToPack otp : list) { if (initMonitor.isCancelled()) throw new IOException( JGitText.get().packingCancelledDuringObjectsWriting); @@ -622,7 +622,7 @@ private void searchForReuse() throws IOException { private void searchForReuse( final Collection reuseLoaders, - final ObjectToPack otp) throws IOException { + final LocalObjectToPack otp) throws IOException { windowCursor.openObjectInAllPacks(otp, reuseLoaders); if (reuseDeltas) { selectDeltaReuseForObject(otp, reuseLoaders); @@ -633,7 +633,7 @@ private void searchForReuse( } } - private void selectDeltaReuseForObject(final ObjectToPack otp, + private void selectDeltaReuseForObject(final LocalObjectToPack otp, final Collection loaders) throws IOException { PackedObjectLoader bestLoader = null; ObjectId bestBase = null; @@ -671,7 +671,7 @@ private static boolean isBetterDeltaReuseLoader( .supportsFastCopyRawData()); } - private void selectObjectReuseForObject(final ObjectToPack otp, + private void selectObjectReuseForObject(final LocalObjectToPack otp, final Collection loaders) { for (final PackedObjectLoader loader : loaders) { if (loader instanceof WholePackedObjectLoader) { @@ -689,8 +689,8 @@ private void writeHeader() throws IOException { } private void writeObjects() throws IOException { - for (List list : objectsLists) { - for (ObjectToPack otp : list) { + for (List list : objectsLists) { + for (LocalObjectToPack otp : list) { if (writeMonitor.isCancelled()) throw new IOException( JGitText.get().packingCancelledDuringObjectsWriting); @@ -700,10 +700,10 @@ private void writeObjects() throws IOException { } } - private void writeObject(final ObjectToPack otp) throws IOException { + private void writeObject(final LocalObjectToPack otp) throws IOException { otp.markWantWrite(); if (otp.isDeltaRepresentation()) { - ObjectToPack deltaBase = otp.getDeltaBase(); + LocalObjectToPack deltaBase = (LocalObjectToPack)otp.getDeltaBase(); assert deltaBase != null || thin; if (deltaBase != null && !deltaBase.isWritten()) { if (deltaBase.wantWrite()) { @@ -741,7 +741,7 @@ private void writeObject(final ObjectToPack otp) throws IOException { writeMonitor.update(1); } - private PackedObjectLoader open(final ObjectToPack otp) throws IOException { + private PackedObjectLoader open(final LocalObjectToPack otp) throws IOException { while (otp.isCopyable()) { try { PackedObjectLoader reuse = otp.getCopyLoader(windowCursor); @@ -885,7 +885,7 @@ public void addObject(final RevObject object) return; } - final ObjectToPack otp = new ObjectToPack(object, object.getType()); + final LocalObjectToPack otp = windowCursor.newObjectToPack(object); try { objectsLists[object.getType()].add(otp); } catch (ArrayIndexOutOfBoundsException x) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java index afc7f7186..cf5bce73c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java @@ -50,6 +50,7 @@ import java.util.zip.Inflater; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.revwalk.RevObject; /** Active handle to a ByteWindow. */ final class WindowCursor extends ObjectReader { @@ -81,6 +82,11 @@ public ObjectLoader openObject(AnyObjectId objectId, int typeHint) return ldr; } + @Override + public LocalObjectToPack newObjectToPack(RevObject obj) { + return new LocalObjectToPack(obj); + } + void openObjectInAllPacks(AnyObjectId otp, Collection reuseLoaders) throws IOException { db.openObjectInAllPacks(reuseLoaders, this, otp); From e0c9368f3e7c3857b9b91d12f76dfa09b1540a24 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 25 Jun 2010 19:21:43 -0700 Subject: [PATCH 037/103] Reclaim some bits in ObjectToPack flags field Make the lower bits available for flags that PackWriter can use to keep track of facts about the object. We shouldn't need more than 2^24 delta depths, unpacking that chain is unfathomable anyway. This change gets us 4 bits that are unused in the lower end of the word, which are typically easier to load from Java and most machine instruction sets. We can use these in later changes. Change-Id: Ib9e11221b5bca17c8a531e4ed130ba14c0e3744f Signed-off-by: Shawn O. Pearce --- .../org/eclipse/jgit/lib/ObjectToPack.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java index dfc3d6f4c..c0cf8901d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java @@ -55,6 +55,14 @@ * 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 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; @@ -62,8 +70,10 @@ public class ObjectToPack extends PackedObjectInfo { * Bit field, from bit 0 to bit 31: *
      *
    • 1 bit: wantWrite
    • + *
    • 4 bits: unused
    • *
    • 3 bits: type
    • - *
    • 28 bits: deltaDepth
    • + *
    • --
    • + *
    • 24 bits: deltaDepth
    • *
    */ private int flags; @@ -78,7 +88,7 @@ public class ObjectToPack extends PackedObjectInfo { */ public ObjectToPack(AnyObjectId src, final int type) { super(src); - flags |= type << 1; + flags = type << TYPE_SHIFT; } /** @@ -151,11 +161,11 @@ boolean isWritten() { } int getType() { - return (flags>>1) & 0x7; + return (flags >> TYPE_SHIFT) & 0x7; } int getDeltaDepth() { - return flags >>> 4; + return flags >>> DELTA_SHIFT; } void updateDeltaDepth() { @@ -166,14 +176,14 @@ else if (deltaBase != null) d = 1; else d = 0; - flags = (d << 4) | flags & 0x15; + flags = (d << DELTA_SHIFT) | (flags & NON_DELTA_MASK); } boolean wantWrite() { - return (flags & 1) == 1; + return (flags & WANT_WRITE) != 0; } void markWantWrite() { - flags |= 1; + flags |= WANT_WRITE; } } From bf4ffff07fb5be4a405ca13ae8baa13dee693b10 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 26 Jun 2010 14:16:06 -0700 Subject: [PATCH 038/103] Redo PackWriter object reuse selection The new selection implementation uses a public API on the ObjectReader, allowing the storage library to enumerate its candidates and select the best one for this packer without needing to build a temporary list of the candidates first. Change-Id: Ie01496434f7d3581d6d3bbb9e33c8f9fa649b6cd Signed-off-by: Shawn O. Pearce --- .../jgit/lib/CachedObjectDirectory.java | 6 + .../eclipse/jgit/lib/FileObjectDatabase.java | 6 +- .../jgit/lib/LocalObjectRepresentation.java | 80 +++++++++++ .../eclipse/jgit/lib/LocalObjectToPack.java | 16 +-- .../org/eclipse/jgit/lib/ObjectDirectory.java | 15 +- .../org/eclipse/jgit/lib/ObjectReader.java | 31 +--- .../org/eclipse/jgit/lib/ObjectReuseAsIs.java | 98 +++++++++++++ .../org/eclipse/jgit/lib/ObjectToPack.java | 50 ++++++- .../src/org/eclipse/jgit/lib/PackWriter.java | 136 +++++++++--------- .../jgit/lib/StoredObjectRepresentation.java | 88 ++++++++++++ .../org/eclipse/jgit/lib/WindowCursor.java | 18 ++- 11 files changed, 415 insertions(+), 129 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectRepresentation.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReuseAsIs.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredObjectRepresentation.java diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java index 351d6817f..a32571e60 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java @@ -175,4 +175,10 @@ ObjectLoader openObject2(WindowCursor curs, String objectName, // This method should never be invoked. throw new UnsupportedOperationException(); } + + @Override + void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, + WindowCursor curs) throws IOException { + wrapped.selectObjectRepresentation(packer, otp, curs); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java index 36e808c23..5328b327e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java @@ -45,7 +45,6 @@ import java.io.File; import java.io.IOException; -import java.util.Collection; abstract class FileObjectDatabase extends ObjectDatabase { @Override @@ -160,9 +159,8 @@ final ObjectLoader openObjectImpl2(final WindowCursor curs, return null; } - void openObjectInAllPacks(Collection reuseLoaders, - WindowCursor windowCursor, AnyObjectId otp) throws IOException { - } + abstract void selectObjectRepresentation(PackWriter packer, + ObjectToPack otp, WindowCursor curs) throws IOException; abstract File getDirectory(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectRepresentation.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectRepresentation.java new file mode 100644 index 000000000..f8535bdf2 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectRepresentation.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.lib; + +import java.io.IOException; + +class LocalObjectRepresentation extends StoredObjectRepresentation { + final PackedObjectLoader ldr; + + LocalObjectRepresentation(PackedObjectLoader ldr) { + this.ldr = ldr; + } + + @Override + public int getFormat() { + if (ldr instanceof DeltaPackedObjectLoader) + return PACK_DELTA; + if (ldr instanceof WholePackedObjectLoader) + return PACK_WHOLE; + return FORMAT_OTHER; + } + + @Override + public int getWeight() { + long sz = ldr.getRawSize(); + if (Integer.MAX_VALUE < sz) + return WEIGHT_UNKNOWN; + return (int) sz; + } + + @Override + public ObjectId getDeltaBase() { + try { + return ldr.getDeltaBase(); + } catch (IOException e) { + return null; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java index a102e96ee..516cf631c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java @@ -59,20 +59,14 @@ class LocalObjectToPack extends ObjectToPack { super(obj); } - 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; + @Override + public void select(StoredObjectRepresentation ref) { + LocalObjectRepresentation ptr = (LocalObjectRepresentation)ref; + this.copyFromPack = ptr.ldr.pack; + this.copyOffset = ptr.ldr.objectOffset; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java index 810e48890..bf8f32334 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java @@ -283,17 +283,16 @@ ObjectLoader openObject1(final WindowCursor curs, } } - void openObjectInAllPacks(final Collection out, - final WindowCursor curs, final AnyObjectId objectId) - throws IOException { + @Override + void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, + WindowCursor curs) 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); - } + PackedObjectLoader ldr = p.get(curs, otp); + if (ldr != null) + packer.select(otp, new LocalObjectRepresentation(ldr)); } catch (PackMismatchException e) { // Pack was modified; refresh the entire pack list. // @@ -309,7 +308,7 @@ void openObjectInAllPacks(final Collection out, } for (AlternateHandle h : myAlternates()) - h.db.openObjectInAllPacks(out, curs, objectId); + h.db.selectObjectRepresentation(packer, otp, curs); } boolean hasObject2(final String objectName) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java index de4b3eea5..670ead9ef 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java @@ -46,9 +46,13 @@ import java.io.IOException; import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.revwalk.RevObject; -/** Reads an {@link ObjectDatabase} for a single thread. */ +/** + * Reads an {@link ObjectDatabase} for a single thread. + *

    + * 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; @@ -103,29 +107,6 @@ public ObjectLoader openObject(AnyObjectId objectId) public abstract ObjectLoader openObject(AnyObjectId objectId, int typeHint) throws MissingObjectException, IOException; - /** - * Allocate a new {@code PackWriter} state structure for an object. - *

    - * {@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 override this method to provide their - * own subclass with additional object state, such as to remember what file - * and position contains the object's data. - *

    - * The default implementation of this object does not provide very efficient - * packing support; it inflates the object on the fly through {@code - * openObject} and deflates it again into the generated stream. - * - * @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) { - return new ObjectToPack(obj, obj.getType()); - } - /** * Release any resources used by this reader. *

    diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReuseAsIs.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReuseAsIs.java new file mode 100644 index 000000000..f7aebf124 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReuseAsIs.java @@ -0,0 +1,98 @@ +/* + * 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.MissingObjectException; +import org.eclipse.jgit.revwalk.RevObject; + +/** + * Extension of {@link ObjectReader} that supports reusing objects in packs. + *

    + * {@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. + *

    + * {@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. + *

    + * 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; +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java index c0cf8901d..34a969666 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java @@ -57,6 +57,8 @@ 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 TYPE_SHIFT = 5; private static final int DELTA_SHIFT = 8; @@ -70,7 +72,8 @@ public class ObjectToPack extends PackedObjectInfo { * Bit field, from bit 0 to bit 31: *

      *
    • 1 bit: wantWrite
    • - *
    • 4 bits: unused
    • + *
    • 1 bit: canReuseAsIs
    • + *
    • 3 bits: unused
    • *
    • 3 bits: type
    • *
    • --
    • *
    • 24 bits: deltaDepth
    • @@ -186,4 +189,49 @@ boolean wantWrite() { void markWantWrite() { flags |= WANT_WRITE; } + + boolean isReuseAsIs() { + return (flags & REUSE_AS_IS) != 0; + } + + void setReuseAsIs() { + flags |= REUSE_AS_IS; + } + + void clearReuseAsIs() { + flags &= ~REUSE_AS_IS; + } + + 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); + } + + /** + * Remember a specific representation for reuse at a later time. + *

      + * 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. + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java index 2079931ea..462b12f39 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java @@ -44,6 +44,9 @@ package org.eclipse.jgit.lib; +import static org.eclipse.jgit.lib.StoredObjectRepresentation.PACK_DELTA; +import static org.eclipse.jgit.lib.StoredObjectRepresentation.PACK_WHOLE; + import java.io.IOException; import java.io.OutputStream; import java.security.MessageDigest; @@ -605,82 +608,18 @@ public void writePack(OutputStream packStream) throws IOException { private void searchForReuse() throws IOException { initMonitor.beginTask(SEARCHING_REUSE_PROGRESS, getObjectsNumber()); - final Collection reuseLoaders = new ArrayList(); for (List list : objectsLists) { - for (LocalObjectToPack otp : list) { + for (ObjectToPack otp : list) { if (initMonitor.isCancelled()) throw new IOException( JGitText.get().packingCancelledDuringObjectsWriting); - reuseLoaders.clear(); - searchForReuse(reuseLoaders, otp); + windowCursor.selectObjectRepresentation(this, otp); initMonitor.update(1); } } - initMonitor.endTask(); } - private void searchForReuse( - final Collection reuseLoaders, - final LocalObjectToPack otp) throws IOException { - windowCursor.openObjectInAllPacks(otp, reuseLoaders); - if (reuseDeltas) { - selectDeltaReuseForObject(otp, reuseLoaders); - } - // delta reuse is preferred over object reuse - if (reuseObjects && !otp.isCopyable()) { - selectObjectReuseForObject(otp, reuseLoaders); - } - } - - private void selectDeltaReuseForObject(final LocalObjectToPack otp, - final Collection 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 LocalObjectToPack otp, - final Collection 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); @@ -708,7 +647,7 @@ private void writeObject(final LocalObjectToPack otp) throws IOException { if (deltaBase != null && !deltaBase.isWritten()) { if (deltaBase.wantWrite()) { otp.clearDeltaBase(); // cycle detected - otp.clearSourcePack(); + otp.clearReuseAsIs(); } else { writeObject(deltaBase); } @@ -742,7 +681,7 @@ private void writeObject(final LocalObjectToPack otp) throws IOException { } private PackedObjectLoader open(final LocalObjectToPack otp) throws IOException { - while (otp.isCopyable()) { + while (otp.isReuseAsIs()) { try { PackedObjectLoader reuse = otp.getCopyLoader(windowCursor); reuse.beginCopyRawData(); @@ -752,8 +691,8 @@ private PackedObjectLoader open(final LocalObjectToPack otp) throws IOException // it has been overwritten with a different layout. // otp.clearDeltaBase(); - otp.clearSourcePack(); - searchForReuse(new ArrayList(), otp); + otp.clearReuseAsIs(); + windowCursor.selectObjectRepresentation(this, otp); continue; } } @@ -898,4 +837,61 @@ public void addObject(final RevObject object) } objectsMap.add(otp); } + + /** + * Select an object representation for this writer. + *

      + * 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/lib/StoredObjectRepresentation.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredObjectRepresentation.java new file mode 100644 index 000000000..0eb05f5e0 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredObjectRepresentation.java @@ -0,0 +1,88 @@ +/* + * 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; + +/** + * 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/lib/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java index cf5bce73c..e16735492 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java @@ -45,7 +45,6 @@ package org.eclipse.jgit.lib; import java.io.IOException; -import java.util.Collection; import java.util.zip.DataFormatException; import java.util.zip.Inflater; @@ -53,7 +52,7 @@ import org.eclipse.jgit.revwalk.RevObject; /** Active handle to a ByteWindow. */ -final class WindowCursor extends ObjectReader { +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]; @@ -82,14 +81,13 @@ public ObjectLoader openObject(AnyObjectId objectId, int typeHint) return ldr; } - @Override public LocalObjectToPack newObjectToPack(RevObject obj) { return new LocalObjectToPack(obj); } - void openObjectInAllPacks(AnyObjectId otp, - Collection reuseLoaders) throws IOException { - db.openObjectInAllPacks(reuseLoaders, this, otp); + public void selectObjectRepresentation(PackWriter packer, ObjectToPack otp) + throws IOException, MissingObjectException { + db.selectObjectRepresentation(packer, otp, this); } /** @@ -108,8 +106,8 @@ void openObjectInAllPacks(AnyObjectId otp, * bytes remaining in the window starting at offset * pos. * @return number of bytes actually copied; this may be less than - * cnt if cnt exceeded the number of - * bytes available. + * cnt if cnt exceeded the number of bytes + * available. * @throws IOException * this cursor does not match the provider or id and the proper * window could not be acquired through the provider's cache. @@ -161,8 +159,8 @@ int inflate(final PackFile pack, long position, final byte[] dstbuf, } } - void inflateVerify(final PackFile pack, long position) - throws IOException, DataFormatException { + void inflateVerify(final PackFile pack, long position) throws IOException, + DataFormatException { prepareInflater(); for (;;) { pin(pack, position); From ece88b99eb2ea6541b667aa066573184c25b6a8b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 26 Jun 2010 14:46:05 -0700 Subject: [PATCH 039/103] Redo PackWriter object reuse output Output of selected reuses is refactored to use a new ObjectReuseAsIs interface that extends the ObjectReader. This interface allows the reader to control how it performs the reuse into the output stream, but also allows it to throw an exception to request the writer to find a different candidate representation. The PackFile reuse code was overhauled, cleaning up the APIs so they aren't exposed in the object loader, but instead are now a single method on the PackFile itself. The reuse algorithm was changed to do a data verification pass, followed by the copy pass to the output. This permits us to work around a corrupt object in a pack file by seeking another copy of that object when this one is bad. The reuse code was also optimized for the common case, where the in-pack representation is under 16 KiB. In these smaller cases data is sent to the pack writer more directly, avoiding some copying. Change-Id: I6350c2b444118305e8446ce1dfd049259832bcca Signed-off-by: Shawn O. Pearce --- ...ctRepresentationNotAvailableException.java | 61 ++++ .../org/eclipse/jgit/lib/ByteArrayWindow.java | 14 - .../eclipse/jgit/lib/ByteBufferWindow.java | 18 -- .../src/org/eclipse/jgit/lib/ByteWindow.java | 10 - .../eclipse/jgit/lib/LocalObjectToPack.java | 10 +- .../org/eclipse/jgit/lib/ObjectReuseAsIs.java | 37 +++ .../src/org/eclipse/jgit/lib/PackFile.java | 269 ++++++++++++++---- .../eclipse/jgit/lib/PackOutputStream.java | 90 +++++- .../src/org/eclipse/jgit/lib/PackWriter.java | 205 ++++++------- .../eclipse/jgit/lib/PackedObjectLoader.java | 64 ----- .../org/eclipse/jgit/lib/WindowCursor.java | 18 +- .../src/org/eclipse/jgit/util/LongList.java | 14 + 12 files changed, 514 insertions(+), 296 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/StoredObjectRepresentationNotAvailableException.java 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 000000000..cff449927 --- /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.lib.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/lib/ByteArrayWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java index 804261031..4f2373d3d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java @@ -80,18 +80,4 @@ protected int inflate(final int pos, final byte[] b, int o, o += inf.inflate(b, o, b.length - o); return o; } - - @Override - protected void inflateVerify(final int pos, final Inflater inf) - 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); - } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java index 1b29934d2..794d7428e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java @@ -89,22 +89,4 @@ protected int inflate(final int pos, final byte[] b, int o, o += inf.inflate(b, o, b.length - o); return o; } - - @Override - protected void inflateVerify(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); - } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java index cbef4218a..69d255c78 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java @@ -172,14 +172,4 @@ final int inflate(long pos, byte[] dstbuf, int dstoff, Inflater inf) */ 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); - } - - protected abstract void inflateVerify(int pos, Inflater inf) - throws DataFormatException; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java index 516cf631c..8db58707e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java @@ -43,26 +43,20 @@ package org.eclipse.jgit.lib; -import java.io.IOException; - import org.eclipse.jgit.revwalk.RevObject; /** {@link ObjectToPack} for {@link ObjectDirectory}. */ class LocalObjectToPack extends ObjectToPack { /** Pack to reuse compressed data from, otherwise null. */ - private PackFile copyFromPack; + PackFile copyFromPack; /** Offset of the object's header in {@link #copyFromPack}. */ - private long copyOffset; + long copyOffset; LocalObjectToPack(RevObject obj) { super(obj); } - PackedObjectLoader getCopyLoader(WindowCursor curs) throws IOException { - return copyFromPack.resolveBase(curs, copyOffset); - } - @Override public void select(StoredObjectRepresentation ref) { LocalObjectRepresentation ptr = (LocalObjectRepresentation)ref; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReuseAsIs.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReuseAsIs.java index f7aebf124..f87b8301c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReuseAsIs.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReuseAsIs.java @@ -46,6 +46,7 @@ import java.io.IOException; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; import org.eclipse.jgit.revwalk.RevObject; /** @@ -95,4 +96,40 @@ public interface ObjectReuseAsIs { */ public void selectObjectRepresentation(PackWriter packer, ObjectToPack otp) throws IOException, MissingObjectException; + + /** + * Output a previously selected representation. + *

      + * {@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. + *

      + * Implementors must write the object header before copying the raw data to + * the output stream. The typical implementation is like: + * + *

      +	 * MyToPack mtp = (MyToPack) otp;
      +	 * byte[] raw = validate(mtp); // throw SORNAE here, if at all
      +	 * out.writeHeader(mtp, mtp.inflatedSize);
      +	 * out.write(raw);
      +	 * 
      + * + * @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/lib/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java index 829832e6a..25835e2ff 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java @@ -48,7 +48,6 @@ 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; @@ -58,13 +57,15 @@ import java.util.Comparator; import java.util.Iterator; import java.util.zip.CRC32; -import java.util.zip.CheckedOutputStream; import java.util.zip.DataFormatException; +import java.util.zip.Inflater; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.PackInvalidException; import org.eclipse.jgit.errors.PackMismatchException; +import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; +import org.eclipse.jgit.util.LongList; import org.eclipse.jgit.util.NB; import org.eclipse.jgit.util.RawParseUtils; @@ -108,6 +109,15 @@ public int compare(final PackFile a, final PackFile b) { private PackReverseIndex reverseIdx; + /** + * Objects we have tried to read, and discovered to be corrupt. + *

      + * The list is allocated after the first corruption is found, and filled in + * as more entries are discovered. Typically this list is never used, as + * pack files do not usually contain corrupt objects. + */ + private volatile LongList corruptObjects; + /** * Construct a reader for an existing, pre-indexed packfile. * @@ -152,6 +162,10 @@ else if (!Arrays.equals(packChecksum, idx.packChecksum)) final PackedObjectLoader resolveBase(final WindowCursor curs, final long ofs) throws IOException { + if (isCorrupt(ofs)) { + throw new CorruptObjectException(MessageFormat.format(JGitText + .get().objectAtHasBadZlibStream, ofs, getPackFile())); + } return reader(curs, ofs); } @@ -174,7 +188,8 @@ public File getPackFile() { * the index file cannot be loaded into memory. */ public boolean hasObject(final AnyObjectId id) throws IOException { - return idx().hasObject(id); + final long offset = idx().findOffset(id); + return 0 < offset && !isCorrupt(offset); } /** @@ -192,7 +207,7 @@ public boolean hasObject(final AnyObjectId id) throws IOException { public PackedObjectLoader get(final WindowCursor curs, final AnyObjectId id) throws IOException { final long offset = idx().findOffset(id); - return 0 < offset ? reader(curs, offset) : null; + return 0 < offset && !isCorrupt(offset) ? reader(curs, offset) : null; } /** @@ -269,48 +284,163 @@ final byte[] decompress(final long position, final int totalSize, 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); + final void copyAsIs(PackOutputStream out, LocalObjectToPack src, + WindowCursor curs) throws IOException, + StoredObjectRepresentationNotAvailableException { + beginCopyAsIs(src); + try { + copyAsIs2(out, src, curs); + } finally { + endCopyAsIs(); } } - boolean supportsFastCopyRawData() throws IOException { - return idx().hasCRC32Support(); + private void copyAsIs2(PackOutputStream out, LocalObjectToPack src, + WindowCursor curs) throws IOException, + StoredObjectRepresentationNotAvailableException { + final CRC32 crc1 = new CRC32(); + final CRC32 crc2 = new CRC32(); + final byte[] buf = out.getCopyBuffer(); + + // Rip apart the header so we can discover the size. + // + readFully(src.copyOffset, buf, 0, 20, curs); + int c = buf[0] & 0xff; + final int typeCode = (c >> 4) & 7; + long inflatedLength = c & 15; + int shift = 4; + int headerCnt = 1; + while ((c & 0x80) != 0) { + c = buf[headerCnt++] & 0xff; + inflatedLength += (c & 0x7f) << shift; + shift += 7; + } + + if (typeCode == Constants.OBJ_OFS_DELTA) { + do { + c = buf[headerCnt++] & 0xff; + } while ((c & 128) != 0); + crc1.update(buf, 0, headerCnt); + crc2.update(buf, 0, headerCnt); + } else if (typeCode == Constants.OBJ_REF_DELTA) { + crc1.update(buf, 0, headerCnt); + crc2.update(buf, 0, headerCnt); + + readFully(src.copyOffset + headerCnt, buf, 0, 20, curs); + crc1.update(buf, 0, 20); + crc2.update(buf, 0, headerCnt); + headerCnt += 20; + } else { + crc1.update(buf, 0, headerCnt); + crc2.update(buf, 0, headerCnt); + } + + final long dataOffset = src.copyOffset + headerCnt; + final long dataLength; + final long expectedCRC; + + // Verify the object isn't corrupt before sending. If it is, + // we report it missing instead. + // + try { + dataLength = findEndOffset(src.copyOffset) - dataOffset; + + if (idx().hasCRC32Support()) { + // Index has the CRC32 code cached, validate the object. + // + expectedCRC = idx().findCRC32(src); + + long pos = dataOffset; + long cnt = dataLength; + while (cnt > 0) { + final int n = (int) Math.min(cnt, buf.length); + readFully(pos, buf, 0, n, curs); + crc1.update(buf, 0, n); + pos += n; + cnt -= n; + } + if (crc1.getValue() != expectedCRC) { + setCorrupt(src.copyOffset); + throw new CorruptObjectException(MessageFormat.format( + JGitText.get().objectAtHasBadZlibStream, + src.copyOffset, getPackFile())); + } + } else { + // We don't have a CRC32 code in the index, so compute it + // now while inflating the raw data to get zlib to tell us + // whether or not the data is safe. + // + long pos = dataOffset; + long cnt = dataLength; + Inflater inf = curs.inflater(); + byte[] tmp = new byte[1024]; + 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()) { + setCorrupt(src.copyOffset); + throw new EOFException(MessageFormat.format( + JGitText.get().shortCompressedStreamAt, + src.copyOffset)); + } + expectedCRC = crc1.getValue(); + } + } catch (DataFormatException dataFormat) { + setCorrupt(src.copyOffset); + + CorruptObjectException corruptObject = new CorruptObjectException( + MessageFormat.format( + JGitText.get().objectAtHasBadZlibStream, + src.copyOffset, getPackFile())); + corruptObject.initCause(dataFormat); + + StoredObjectRepresentationNotAvailableException gone; + gone = new StoredObjectRepresentationNotAvailableException(src); + gone.initCause(corruptObject); + throw gone; + + } catch (IOException ioError) { + StoredObjectRepresentationNotAvailableException gone; + gone = new StoredObjectRepresentationNotAvailableException(src); + gone.initCause(ioError); + throw gone; + } + + if (dataLength <= buf.length) { + // Tiny optimization: Lots of objects are very small deltas or + // deflated commits that are likely to fit in the copy buffer. + // + out.writeHeader(src, inflatedLength); + out.write(buf, 0, (int) dataLength); + } else { + // Now we are committed to sending the object. As we spool it out, + // check its CRC32 code to make sure there wasn't corruption between + // the verification we did above, and us actually outputting it. + // + out.writeHeader(src, inflatedLength); + long pos = dataOffset; + long cnt = dataLength; + while (cnt > 0) { + final int n = (int) Math.min(cnt, buf.length); + readFully(pos, buf, 0, n, curs); + crc2.update(buf, 0, n); + out.write(buf, 0, n); + pos += n; + cnt -= n; + } + if (crc2.getValue() != expectedCRC) { + throw new CorruptObjectException(MessageFormat.format(JGitText + .get().objectAtHasBadZlibStream, src.copyOffset, + getPackFile())); + } + } } boolean invalid() { @@ -324,24 +454,22 @@ private void readFully(final long position, final byte[] dstbuf, 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); + 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; + } } } - synchronized void beginCopyRawData() throws IOException { - if (++activeCopyRawData == 1 && activeWindows == 0) - doOpen(); - } - - synchronized void endCopyRawData() { + private synchronized void endCopyAsIs() { if (--activeCopyRawData == 0 && activeWindows == 0) doClose(); } @@ -523,4 +651,29 @@ private synchronized PackReverseIndex getReverseIdx() throws IOException { 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/PackOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackOutputStream.java index a348f1e54..48ec2b5a1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackOutputStream.java @@ -49,35 +49,44 @@ import java.security.MessageDigest; import java.util.zip.CRC32; +import org.eclipse.jgit.util.NB; + /** Custom output stream to support {@link PackWriter}. */ -final class PackOutputStream extends OutputStream { +public final class PackOutputStream extends OutputStream { private final OutputStream out; + private final boolean ofsDelta; + private final CRC32 crc = new CRC32(); private final MessageDigest md = Constants.newMessageDigest(); private long count; - PackOutputStream(final OutputStream out) { + private byte[] headerBuffer = new byte[32]; + + private byte[] copyBuffer; + + PackOutputStream(final OutputStream out, final boolean ofsDelta) { this.out = out; + this.ofsDelta = ofsDelta; } @Override public void write(final int b) throws IOException { + count++; out.write(b); crc.update(b); md.update((byte) b); - count++; } @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); - count += len; } @Override @@ -85,6 +94,79 @@ 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. + *

      + * 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; + } + /** @return total number of bytes written since stream start. */ long length() { return count; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java index 462b12f39..80d8fff53 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java @@ -58,13 +58,14 @@ import java.util.zip.Deflater; import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; 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.util.NB; /** *

      @@ -155,13 +156,13 @@ public class PackWriter { private static final int PACK_VERSION_GENERATED = 2; @SuppressWarnings("unchecked") - private final List objectsLists[] = new List[Constants.OBJ_TAG + 1]; + private final List objectsLists[] = new List[Constants.OBJ_TAG + 1]; { - objectsLists[0] = Collections. emptyList(); - objectsLists[Constants.OBJ_COMMIT] = new ArrayList(); - objectsLists[Constants.OBJ_TREE] = new ArrayList(); - objectsLists[Constants.OBJ_BLOB] = new ArrayList(); - objectsLists[Constants.OBJ_TAG] = new ArrayList(); + objectsLists[0] = Collections. emptyList(); + objectsLists[Constants.OBJ_COMMIT] = new ArrayList(); + objectsLists[Constants.OBJ_TREE] = new ArrayList(); + objectsLists[Constants.OBJ_BLOB] = new ArrayList(); + objectsLists[Constants.OBJ_TAG] = new ArrayList(); } private final ObjectIdSubclassMap objectsMap = new ObjectIdSubclassMap(); @@ -179,9 +180,10 @@ public class PackWriter { private ProgressMonitor writeMonitor; - private final byte[] buf = new byte[16384]; // 16 KB + private final ObjectReader reader; - private final WindowCursor windowCursor; + /** {@link #reader} recast to the reuse interface, if it supports it. */ + private final ObjectReuseAsIs reuseSupport; private List sortedByName; @@ -238,8 +240,12 @@ public PackWriter(final Repository repo, final ProgressMonitor monitor) { public PackWriter(final Repository repo, final ProgressMonitor imonitor, final ProgressMonitor wmonitor) { this.db = repo; - windowCursor = new WindowCursor((ObjectDirectory) repo - .getObjectDatabase()); + + reader = db.newObjectReader(); + if (reader instanceof ObjectReuseAsIs) + reuseSupport = ((ObjectReuseAsIs) reader); + else + reuseSupport = null; initMonitor = imonitor == null ? NullProgressMonitor.INSTANCE : imonitor; writeMonitor = wmonitor == null ? NullProgressMonitor.INSTANCE : wmonitor; @@ -525,6 +531,7 @@ public boolean willInclude(final AnyObjectId id) { * @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); @@ -560,8 +567,8 @@ public void writeIndex(final OutputStream indexStream) throws IOException { private List sortByName() { if (sortedByName == null) { sortedByName = new ArrayList(objectsMap.size()); - for (List list : objectsLists) { - for (LocalObjectToPack otp : list) + for (List list : objectsLists) { + for (ObjectToPack otp : list) sortedByName.add(otp); } Collections.sort(sortedByName); @@ -592,44 +599,38 @@ private List sortByName() { * stream. */ public void writePack(OutputStream packStream) throws IOException { - if (reuseDeltas || reuseObjects) + if ((reuseDeltas || reuseObjects) && reuseSupport != null) searchForReuse(); - out = new PackOutputStream(packStream); + out = new PackOutputStream(packStream, isDeltaBaseAsOffset()); writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber()); - writeHeader(); + out.writeFileHeader(PACK_VERSION_GENERATED, getObjectsNumber()); writeObjects(); writeChecksum(); - windowCursor.release(); + out = null; + reader.release(); writeMonitor.endTask(); } private void searchForReuse() throws IOException { initMonitor.beginTask(SEARCHING_REUSE_PROGRESS, getObjectsNumber()); - for (List list : objectsLists) { + for (List list : objectsLists) { for (ObjectToPack otp : list) { if (initMonitor.isCancelled()) throw new IOException( JGitText.get().packingCancelledDuringObjectsWriting); - windowCursor.selectObjectRepresentation(this, otp); + reuseSupport.selectObjectRepresentation(this, otp); initMonitor.update(1); } } initMonitor.endTask(); } - 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 list : objectsLists) { - for (LocalObjectToPack otp : list) { + for (List list : objectsLists) { + for (ObjectToPack otp : list) { if (writeMonitor.isCancelled()) throw new IOException( JGitText.get().packingCancelledDuringObjectsWriting); @@ -639,74 +640,88 @@ private void writeObjects() throws IOException { } } - private void writeObject(final LocalObjectToPack otp) throws IOException { - otp.markWantWrite(); - if (otp.isDeltaRepresentation()) { - LocalObjectToPack deltaBase = (LocalObjectToPack)otp.getDeltaBase(); - assert deltaBase != null || thin; - if (deltaBase != null && !deltaBase.isWritten()) { - if (deltaBase.wantWrite()) { - otp.clearDeltaBase(); // cycle detected - otp.clearReuseAsIs(); - } else { - writeObject(deltaBase); - } - } - } + private void writeObject(final ObjectToPack otp) throws IOException { + if (otp.isWritten()) + return; // We shouldn't be here. - assert !otp.isWritten(); + otp.markWantWrite(); + if (otp.isDeltaRepresentation()) + writeBaseFirst(otp); out.resetCRC32(); otp.setOffset(out.length()); - final PackedObjectLoader reuse = open(otp); - if (reuse != null) { + while (otp.isReuseAsIs()) { try { - if (otp.isDeltaRepresentation()) - writeDeltaObjectHeader(otp, reuse); - else - writeObjectHeader(otp.getType(), reuse.getSize()); - reuse.copyRawData(out, buf, windowCursor); - } finally { - reuse.endCopyRawData(); + reuseSupport.copyObjectAsIs(out, otp); + otp.setCRC(out.getCRC32()); + writeMonitor.update(1); + 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; + } } - } else if (otp.isDeltaRepresentation()) { - throw new IOException(JGitText.get().creatingDeltasIsNotImplemented); - } else { - writeWholeObjectDeflate(otp); } - otp.setCRC(out.getCRC32()); + // If we reached here, reuse wasn't possible. + // + writeWholeObjectDeflate(otp); + otp.setCRC(out.getCRC32()); writeMonitor.update(1); } - private PackedObjectLoader open(final LocalObjectToPack otp) throws IOException { - while (otp.isReuseAsIs()) { - 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.clearReuseAsIs(); - windowCursor.selectObjectRepresentation(this, otp); - continue; + private void writeBaseFirst(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(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(); } - return null; + } + + private void redoSearchForReuse(final ObjectToPack otp) throws IOException, + MissingObjectException { + otp.clearDeltaBase(); + otp.clearReuseAsIs(); + reuseSupport.selectObjectRepresentation(this, otp); } private void writeWholeObjectDeflate(final ObjectToPack otp) throws IOException { - final ObjectLoader loader = db.openObject(windowCursor, otp); + final ObjectLoader loader = db.openObject(reader, otp); final byte[] data = loader.getCachedBytes(); - writeObjectHeader(otp.getType(), data.length); + out.writeHeader(otp, data.length); deflater.reset(); deflater.setInput(data, 0, data.length); deflater.finish(); + + byte[] buf = out.getCopyBuffer(); do { final int n = deflater.deflate(buf, 0, buf.length); if (n > 0) @@ -714,42 +729,6 @@ private void writeWholeObjectDeflate(final ObjectToPack otp) } 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); @@ -824,7 +803,11 @@ public void addObject(final RevObject object) return; } - final LocalObjectToPack otp = windowCursor.newObjectToPack(object); + final ObjectToPack otp; + if (reuseSupport != null) + otp = reuseSupport.newObjectToPack(object); + else + otp = new ObjectToPack(object); try { objectsLists[object.getType()].add(otp); } catch (ArrayIndexOutOfBoundsException x) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java index 026b008f1..47f5e67a7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java @@ -47,7 +47,6 @@ 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. @@ -115,69 +114,6 @@ public final long getObjectOffset() { return objectOffset; } - /** - * Peg the pack file open to support data copying. - *

      - * Applications trying to copy raw pack data should ensure the pack stays - * open and available throughout the entire copy. To do that use: - * - *

      -	 * loader.beginCopyRawData();
      -	 * try {
      -	 * 	loader.copyRawData(out, tmpbuf, curs);
      -	 * } finally {
      -	 * 	loader.endCopyRawData();
      -	 * }
      -	 * 
      - * - * @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. - *

      - * Copied data doesn't include object header. User must provide temporary - * buffer used during copying by underlying I/O layer. - *

      - * - * @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. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java index e16735492..36095ed5e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java @@ -49,6 +49,7 @@ import java.util.zip.Inflater; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; import org.eclipse.jgit.revwalk.RevObject; /** Active handle to a ByteWindow. */ @@ -90,6 +91,12 @@ public void selectObjectRepresentation(PackWriter packer, ObjectToPack otp) db.selectObjectRepresentation(packer, otp, this); } + public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp) + throws IOException, StoredObjectRepresentationNotAvailableException { + LocalObjectToPack src = (LocalObjectToPack) otp; + src.copyFromPack.copyAsIs(out, src, this); + } + /** * Copy bytes from the window to a caller supplied buffer. * @@ -159,16 +166,9 @@ int inflate(final PackFile pack, long position, final byte[] dstbuf, } } - void inflateVerify(final PackFile pack, long position) throws IOException, - DataFormatException { + Inflater inflater() { prepareInflater(); - for (;;) { - pin(pack, position); - window.inflateVerify(position, inf); - if (inf.finished()) - return; - position = window.end; - } + return inf; } private void prepareInflater() { 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 26608bb2a..96b311dfb 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 long get(final int i) { 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; From 3a7aec03e07ac853df5a00cbf06e6cd5e4ba2bc2 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 26 Jun 2010 15:17:09 -0700 Subject: [PATCH 040/103] Implement zero-copy for single window objects Objects that fall completely within a single window can be worked with in a zero-copy fashion, provided that the window is backed by a normal byte[] and not by a ByteBuffer. This works for a surprising number of objects. The default window size is 8 KiB, but most deltas are quite a bit smaller than that. Objects smaller than 1/2 of the window size have a very good chance of falling completely within a window's array, which means we can work with them without copying their data around. Larger objects, or objects which are unlucky enough to span over a window boundary, get copied through the temporary buffer. We pay a tiny penalty to realize we can't use the zero-copy code path, but its easier than trying to keep track of two adjacent windows. With this change (as well as everything preceeding it), packing is actually a bit faster. Some crude benchmarks based on cloning linux-2.6.git (~324 MiB, 1,624,785 objects) over localhost using C git client and JGit daemon shows we get better throughput, and slightly better times: Total Time | Throughput (old) (now) | (old) (now) --------------+--------------------------- 2m45s 2m37s | 12.49 MiB/s 21.17 MiB/s 2m42s 2m36s | 16.29 MiB/s 22.63 MiB/s 2m37s 2m31s | 16.07 MiB/s 21.92 MiB/s Change-Id: I48b2c8d37f08d7bf5e76c5a8020cde4a16ae3396 Signed-off-by: Shawn O. Pearce --- .../org/eclipse/jgit/lib/ByteArrayWindow.java | 18 ++++++ .../src/org/eclipse/jgit/lib/PackFile.java | 60 ++++++++++++------- .../org/eclipse/jgit/lib/WindowCursor.java | 9 +++ 3 files changed, 65 insertions(+), 22 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java index 4f2373d3d..0c5c81899 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java @@ -45,6 +45,9 @@ package org.eclipse.jgit.lib; +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.CRC32; import java.util.zip.DataFormatException; import java.util.zip.Inflater; @@ -80,4 +83,19 @@ protected int inflate(final int pos, final byte[] b, int o, o += inf.inflate(b, o, b.length - o); return o; } + + void crc32(CRC32 out, long pos, int cnt) { + out.update(array, (int) (pos - start), cnt); + } + + void write(OutputStream out, long pos, int cnt) throws IOException { + out.write(array, (int) (pos - start), cnt); + } + + void check(Inflater inf, byte[] tmp, long pos, int cnt) + throws DataFormatException { + inf.setInput(array, (int) (pos - start), cnt); + while (inf.inflate(tmp, 0, tmp.length) > 0) + continue; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java index 25835e2ff..ab25f6188 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java @@ -338,26 +338,31 @@ private void copyAsIs2(PackOutputStream out, LocalObjectToPack src, final long dataOffset = src.copyOffset + headerCnt; final long dataLength; final long expectedCRC; + final ByteArrayWindow quickCopy; // Verify the object isn't corrupt before sending. If it is, // we report it missing instead. // try { dataLength = findEndOffset(src.copyOffset) - dataOffset; + quickCopy = curs.quickCopy(this, dataOffset, dataLength); if (idx().hasCRC32Support()) { // Index has the CRC32 code cached, validate the object. // expectedCRC = idx().findCRC32(src); - - 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 (quickCopy != null) { + quickCopy.crc32(crc1, dataOffset, (int) dataLength); + } else { + long pos = dataOffset; + long cnt = dataLength; + while (cnt > 0) { + final int n = (int) Math.min(cnt, buf.length); + readFully(pos, buf, 0, n, curs); + crc1.update(buf, 0, n); + pos += n; + cnt -= n; + } } if (crc1.getValue() != expectedCRC) { setCorrupt(src.copyOffset); @@ -370,21 +375,25 @@ private void copyAsIs2(PackOutputStream out, LocalObjectToPack src, // now while inflating the raw data to get zlib to tell us // whether or not the data is safe. // - long pos = dataOffset; - long cnt = dataLength; Inflater inf = curs.inflater(); byte[] tmp = new byte[1024]; - 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 (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()) { + if (!inf.finished() || inf.getBytesRead() != dataLength) { setCorrupt(src.copyOffset); throw new EOFException(MessageFormat.format( JGitText.get().shortCompressedStreamAt, @@ -413,7 +422,14 @@ private void copyAsIs2(PackOutputStream out, LocalObjectToPack src, throw gone; } - if (dataLength <= buf.length) { + 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. // diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java index 36095ed5e..98916efcb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java @@ -166,6 +166,15 @@ int inflate(final PackFile pack, long position, final byte[] dstbuf, } } + ByteArrayWindow quickCopy(PackFile p, long pos, long cnt) + throws IOException { + pin(p, pos); + if (window instanceof ByteArrayWindow + && window.contains(p, pos + (cnt - 1))) + return (ByteArrayWindow) window; + return null; + } + Inflater inflater() { prepareInflater(); return inf; From ad5238dc67aa5922c425e6bc829e1152c2e20439 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 26 Jun 2010 16:56:55 -0700 Subject: [PATCH 041/103] Move FileRepository to storage.file.FileRepository This move isolates all of the local file specific implementation code into a single package, where their package-private methods and support classes are properly hidden away from the rest of the core library. Because of the sheer number of files impacted, I have limited this change to only the renames and the updated imports. Change-Id: Icca4884e1a418f83f8b617d0c4c78b73d8a4bd17 Signed-off-by: Shawn O. Pearce --- .../META-INF/MANIFEST.MF | 1 + .../jgit/http/server/InfoPacksServlet.java | 4 +-- .../jgit/http/server/IsLocalFilter.java | 2 +- .../jgit/http/server/ObjectFileServlet.java | 2 +- .../META-INF/MANIFEST.MF | 1 + .../jgit/http/test/AdvertiseErrorTest.java | 4 +-- .../jgit/http/test/HookMessageTest.java | 4 +-- .../jgit/http/test/HttpClientTests.java | 2 +- .../http/test/SmartClientSmartServerTest.java | 6 ++-- .../jgit/http/test/util/HttpTestCase.java | 2 +- org.eclipse.jgit.iplog/META-INF/MANIFEST.MF | 1 + .../src/org/eclipse/jgit/iplog/IpLogMeta.java | 4 +-- org.eclipse.jgit.junit/META-INF/MANIFEST.MF | 1 + .../junit/LocalDiskRepositoryTestCase.java | 8 ++--- .../eclipse/jgit/junit/MockSystemReader.java | 2 +- .../eclipse/jgit/junit/TestRepository.java | 10 +++---- org.eclipse.jgit.pgm/META-INF/MANIFEST.MF | 1 + .../src/org/eclipse/jgit/pgm/Clone.java | 2 +- .../src/org/eclipse/jgit/pgm/Init.java | 2 +- .../jgit/pgm/debug/RebuildCommitGraph.java | 2 +- .../org/eclipse/jgit/pgm/eclipse/Iplog.java | 2 +- org.eclipse.jgit.test/META-INF/MANIFEST.MF | 1 + .../eclipse/jgit/lib/RepositoryTestCase.java | 1 + .../file}/ConcurrentRepackTest.java | 11 ++++++- .../file}/PackIndexTestCase.java | 5 ++-- .../file}/PackIndexV1Test.java | 3 +- .../file}/PackIndexV2Test.java | 3 +- .../file}/PackReverseIndexTest.java | 5 ++-- .../{lib => storage/file}/PackWriterTest.java | 8 +++-- .../file}/RefDirectoryTest.java | 6 +++- .../{lib => storage/file}/RefUpdateTest.java | 29 ++++++++++++++----- .../file}/ReflogReaderTest.java | 7 +++-- .../file}/RepositorySetupWorkDirTest.java | 5 +++- .../{lib => storage/file}/T0003_Basic.java | 17 ++++++++++- .../file}/T0004_PackReader.java | 6 +++- .../file}/WindowCacheGetTest.java | 6 +++- .../file}/WindowCacheReconfigureTest.java | 4 ++- .../{lib => storage/file}/XInputStream.java | 2 +- .../eclipse/jgit/transport/IndexPackTest.java | 2 +- .../transport/ReceivePackRefFilterTest.java | 2 +- org.eclipse.jgit/META-INF/MANIFEST.MF | 1 + .../org/eclipse/jgit/dircache/DirCache.java | 2 +- .../jgit/lib/BaseRepositoryBuilder.java | 3 ++ .../src/org/eclipse/jgit/lib/PackWriter.java | 1 + .../src/org/eclipse/jgit/lib/RefWriter.java | 1 + .../src/org/eclipse/jgit/lib/Repository.java | 1 + .../eclipse/jgit/lib/RepositoryBuilder.java | 2 ++ .../org/eclipse/jgit/lib/RepositoryCache.java | 1 + .../file}/ByteArrayWindow.java | 2 +- .../file}/ByteBufferWindow.java | 2 +- .../{lib => storage/file}/ByteWindow.java | 2 +- .../file}/CachedObjectDirectory.java | 12 +++++++- .../file}/DeltaOfsPackedObjectLoader.java | 6 ++-- .../file}/DeltaPackedObjectLoader.java | 6 ++-- .../file}/DeltaRefPackedObjectLoader.java | 6 ++-- .../file}/FileBasedConfig.java | 4 ++- .../file}/FileObjectDatabase.java | 9 +++++- .../{lib => storage/file}/FileRepository.java | 25 +++++++++++++--- .../file}/FileRepositoryBuilder.java | 4 ++- .../file}/LocalObjectRepresentation.java | 5 +++- .../file}/LocalObjectToPack.java | 4 ++- .../jgit/{lib => storage/file}/LockFile.java | 4 ++- .../file}/ObjectDirectory.java | 11 ++++++- .../file}/ObjectDirectoryInserter.java | 7 ++++- .../jgit/{lib => storage/file}/PackFile.java | 7 ++++- .../jgit/{lib => storage/file}/PackIndex.java | 5 +++- .../{lib => storage/file}/PackIndexV1.java | 5 +++- .../{lib => storage/file}/PackIndexV2.java | 5 +++- .../file}/PackIndexWriter.java | 4 ++- .../file}/PackIndexWriterV1.java | 2 +- .../file}/PackIndexWriterV2.java | 2 +- .../jgit/{lib => storage/file}/PackLock.java | 4 ++- .../file}/PackReverseIndex.java | 5 ++-- .../file}/PackedObjectLoader.java | 11 ++++--- .../{lib => storage/file}/RefDirectory.java | 14 ++++++++- .../file}/RefDirectoryRename.java | 6 +++- .../file}/RefDirectoryUpdate.java | 6 +++- .../{lib => storage/file}/ReflogReader.java | 6 +++- .../file}/UnpackedObjectCache.java | 2 +- .../file}/UnpackedObjectLoader.java | 6 +++- .../file}/WholePackedObjectLoader.java | 8 +++-- .../{lib => storage/file}/WindowCache.java | 2 +- .../file}/WindowCacheConfig.java | 4 ++- .../{lib => storage/file}/WindowCursor.java | 11 ++++++- .../transport/BasePackFetchConnection.java | 2 +- .../jgit/transport/BundleFetchConnection.java | 2 +- .../jgit/transport/FetchConnection.java | 2 +- .../eclipse/jgit/transport/FetchProcess.java | 4 +-- .../org/eclipse/jgit/transport/IndexPack.java | 4 +-- .../eclipse/jgit/transport/ReceivePack.java | 2 +- .../eclipse/jgit/transport/TransportHttp.java | 2 +- .../jgit/transport/TransportLocal.java | 2 +- .../jgit/transport/WalkFetchConnection.java | 8 ++--- .../transport/WalkRemoteObjectDatabase.java | 2 +- .../org/eclipse/jgit/util/SystemReader.java | 2 +- 95 files changed, 332 insertions(+), 122 deletions(-) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/{lib => storage/file}/ConcurrentRepackTest.java (95%) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/{lib => storage/file}/PackIndexTestCase.java (97%) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/{lib => storage/file}/PackIndexV1Test.java (97%) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/{lib => storage/file}/PackIndexV2Test.java (98%) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/{lib => storage/file}/PackReverseIndexTest.java (96%) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/{lib => storage/file}/PackWriterTest.java (98%) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/{lib => storage/file}/RefDirectoryTest.java (99%) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/{lib => storage/file}/RefUpdateTest.java (95%) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/{lib => storage/file}/ReflogReaderTest.java (97%) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/{lib => storage/file}/RepositorySetupWorkDirTest.java (97%) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/{lib => storage/file}/T0003_Basic.java (98%) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/{lib => storage/file}/T0004_PackReader.java (94%) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/{lib => storage/file}/WindowCacheGetTest.java (95%) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/{lib => storage/file}/WindowCacheReconfigureTest.java (98%) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/{lib => storage/file}/XInputStream.java (98%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/ByteArrayWindow.java (98%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/ByteBufferWindow.java (98%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/ByteWindow.java (99%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/CachedObjectDirectory.java (92%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/DeltaOfsPackedObjectLoader.java (94%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/DeltaPackedObjectLoader.java (95%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/DeltaRefPackedObjectLoader.java (94%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/FileBasedConfig.java (98%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/FileObjectDatabase.java (95%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/FileRepository.java (91%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/FileRepositoryBuilder.java (97%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/LocalObjectRepresentation.java (95%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/LocalObjectToPack.java (94%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/LockFile.java (99%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/ObjectDirectory.java (97%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/ObjectDirectoryInserter.java (95%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/PackFile.java (98%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/PackIndex.java (98%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/PackIndexV1.java (97%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/PackIndexV2.java (98%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/PackIndexWriter.java (98%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/PackIndexWriterV1.java (98%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/PackIndexWriterV2.java (98%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/PackLock.java (97%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/PackReverseIndex.java (97%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/PackedObjectLoader.java (93%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/RefDirectory.java (98%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/RefDirectoryRename.java (97%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/RefDirectoryUpdate.java (96%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/ReflogReader.java (96%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/UnpackedObjectCache.java (99%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/UnpackedObjectLoader.java (97%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/WholePackedObjectLoader.java (94%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/WindowCache.java (99%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/WindowCacheConfig.java (98%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/file}/WindowCursor.java (94%) diff --git a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF index b9aeef519..edaa1edd1 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 dff1e8252..d217fe1c3 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.HttpServletResponse; import org.eclipse.jgit.lib.ObjectDatabase; -import org.eclipse.jgit.lib.ObjectDirectory; -import org.eclipse.jgit.lib.PackFile; +import org.eclipse.jgit.storage.file.ObjectDirectory; +import org.eclipse.jgit.storage.file.PackFile; /** Sends the current list of pack files, sorted most recent first. */ class InfoPacksServlet extends HttpServlet { diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java index 34edf8279..019ec90bc 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.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 5d774a824..84865121c 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.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jgit.lib.ObjectDirectory; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.ObjectDirectory; /** Sends any object from {@code GIT_DIR/objects/??/0 38}, or any pack file. */ abstract class ObjectFileServlet extends HttpServlet { diff --git a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF index 370bd4035..21dd60817 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 0f6299cd5..db4aa802e 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java @@ -59,13 +59,13 @@ import org.eclipse.jgit.http.test.util.HttpTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileBasedConfig; -import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.transport.ReceivePack; import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.Transport; diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java index a2292e6c0..18f8dc928 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java @@ -61,13 +61,13 @@ import org.eclipse.jgit.http.test.util.HttpTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileBasedConfig; -import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.transport.PreReceiveHook; import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.ReceiveCommand; diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java index daf169eb1..4cc141bb4 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java @@ -62,11 +62,11 @@ import org.eclipse.jgit.http.test.util.HttpTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.transport.FetchConnection; import org.eclipse.jgit.transport.Transport; import org.eclipse.jgit.transport.URIish; diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java index 867af2bf6..a7b51c681 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java @@ -76,15 +76,15 @@ import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRng; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileBasedConfig; -import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.ReflogReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.storage.file.FileRepository; +import org.eclipse.jgit.storage.file.ReflogReader; import org.eclipse.jgit.transport.FetchConnection; import org.eclipse.jgit.transport.HttpTransport; import org.eclipse.jgit.transport.RemoteRefUpdate; diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java index a25ca5678..313b6ad90 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java @@ -57,11 +57,11 @@ import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.URIish; diff --git a/org.eclipse.jgit.iplog/META-INF/MANIFEST.MF b/org.eclipse.jgit.iplog/META-INF/MANIFEST.MF index 4ff614400..31a19b5ca 100644 --- a/org.eclipse.jgit.iplog/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.iplog/META-INF/MANIFEST.MF @@ -15,6 +15,7 @@ Import-Package: org.eclipse.jgit.diff;version="[0.9.0,0.10.0)", org.eclipse.jgit.revplot;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk.filter;version="[0.9.0,0.10.0)", + org.eclipse.jgit.storage.file;version="[0.9.0,0.10.0)", org.eclipse.jgit.transport;version="[0.9.0,0.10.0)", org.eclipse.jgit.treewalk;version="[0.9.0,0.10.0)", org.eclipse.jgit.treewalk.filter;version="[0.9.0,0.10.0)", diff --git a/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/IpLogMeta.java b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/IpLogMeta.java index 89695bdb8..2799a4a30 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 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 051079b78..ab24b5aaf 100644 --- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF @@ -18,6 +18,7 @@ Import-Package: junit.framework;version="[3.8.2,4.0.0)", org.eclipse.jgit.revplot;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk.filter;version="[0.9.0,0.10.0)", + org.eclipse.jgit.storage.file;version="[0.9.0,0.10.0)", org.eclipse.jgit.transport;version="[0.9.0,0.10.0)", org.eclipse.jgit.treewalk;version="[0.9.0,0.10.0)", org.eclipse.jgit.treewalk.filter;version="[0.9.0,0.10.0)", diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java index f45e5f67a..2b82d82d7 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java @@ -62,13 +62,13 @@ import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileBasedConfig; -import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryCache; -import org.eclipse.jgit.lib.WindowCache; -import org.eclipse.jgit.lib.WindowCacheConfig; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.storage.file.FileRepository; +import org.eclipse.jgit.storage.file.WindowCache; +import org.eclipse.jgit.storage.file.WindowCacheConfig; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.SystemReader; diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java index c502fb634..eb08417bc 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 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 dbf4eaf4c..ec34e3c3a 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java @@ -73,14 +73,10 @@ import org.eclipse.jgit.lib.Commit; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; -import org.eclipse.jgit.lib.FileRepository; -import org.eclipse.jgit.lib.LockFile; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectChecker; -import org.eclipse.jgit.lib.ObjectDirectory; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.PackFile; import org.eclipse.jgit.lib.PackWriter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; @@ -88,7 +84,6 @@ import org.eclipse.jgit.lib.RefWriter; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Tag; -import org.eclipse.jgit.lib.PackIndex.MutableEntry; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; @@ -96,6 +91,11 @@ import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.FileRepository; +import org.eclipse.jgit.storage.file.LockFile; +import org.eclipse.jgit.storage.file.ObjectDirectory; +import org.eclipse.jgit.storage.file.PackFile; +import org.eclipse.jgit.storage.file.PackIndex.MutableEntry; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF index 862889678..0a4376921 100644 --- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF @@ -17,6 +17,7 @@ Import-Package: org.eclipse.jgit.api;version="[0.9.0,0.10.0)", org.eclipse.jgit.revplot;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk.filter;version="[0.9.0,0.10.0)", + org.eclipse.jgit.storage.file;version="[0.9.0,0.10.0)", org.eclipse.jgit.transport;version="[0.9.0,0.10.0)", org.eclipse.jgit.treewalk;version="[0.9.0,0.10.0)", org.eclipse.jgit.treewalk.filter;version="[0.9.0,0.10.0)", diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java index cc59a6eb5..22302bba8 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java @@ -57,7 +57,6 @@ import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.Commit; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.GitIndex; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefComparator; @@ -65,6 +64,7 @@ import org.eclipse.jgit.lib.TextProgressMonitor; import org.eclipse.jgit.lib.Tree; import org.eclipse.jgit.lib.WorkDirCheckout; +import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.transport.FetchResult; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteConfig; diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java index b2a9fbde0..c5a696a57 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 org.kohsuke.args4j.Option; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileRepository; +import org.eclipse.jgit.storage.file.FileRepository; @Command(common = true, usage = "usage_CreateAnEmptyGitRepository") class Init extends TextBuiltin { diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java index 1681dbc96..8ba3e4b90 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.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.pgm.CLIText; import org.eclipse.jgit.pgm.TextBuiltin; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.LockFile; /** * Recreates a repository from another one's commit graph. diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/eclipse/Iplog.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/eclipse/Iplog.java index e13bb1f13..a99e0abca 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 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.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.LockFile; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index 3b82cf7d1..b88ccf346 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -28,6 +28,7 @@ Import-Package: junit.framework;version="[3.8.2,4.0.0)", org.eclipse.jgit.revplot;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk;version="[0.9.0,0.10.0)", org.eclipse.jgit.revwalk.filter;version="[0.9.0,0.10.0)", + org.eclipse.jgit.storage.file;version="[0.9.0,0.10.0)", org.eclipse.jgit.transport;version="[0.9.0,0.10.0)", org.eclipse.jgit.treewalk;version="[0.9.0,0.10.0)", org.eclipse.jgit.treewalk.filter;version="[0.9.0,0.10.0)", diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java index cc4572244..e78f8512a 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.Reader; import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.storage.file.FileRepository; /** * Base class for most JGit unit tests. diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java similarity index 95% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java index e3a62faf2..db770f777 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,6 +53,15 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectWriter; +import org.eclipse.jgit.lib.PackWriter; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryTestCase; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; 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 similarity index 97% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexTestCase.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexTestCase.java index d1c5c5ecc..9884142e5 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 similarity index 97% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV1Test.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV1Test.java index f3082fb29..303eeff72 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 similarity index 98% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV2Test.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackIndexV2Test.java index c5669f9d2..2d3ec7b72 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 similarity index 96% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackReverseIndexTest.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackReverseIndexTest.java index 19b705813..07a40a425 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 similarity index 98% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackWriterTest.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java index c291968b7..cf0d7eb6a 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,13 @@ import java.util.List; import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.lib.PackIndex.MutableEntry; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PackWriter; +import org.eclipse.jgit.lib.SampleDataRepositoryTestCase; +import org.eclipse.jgit.lib.TextProgressMonitor; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.PackIndex.MutableEntry; import org.eclipse.jgit.transport.IndexPack; import org.eclipse.jgit.util.JGitTestUtil; 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 similarity index 99% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefDirectoryTest.java index a2812901b..6e9854160 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 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 similarity index 95% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefUpdateTest.java index 161bd3eff..875c2e96f 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.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 void testNoCacheObjectIdSubclass() throws IOException { assertNotSame(newid, r.getObjectId()); assertSame(ObjectId.class, r.getObjectId().getClass()); assertEquals(newid.copy(), r.getObjectId()); - List reverseEntries1 = db.getReflogReader("refs/heads/abc").getReverseEntries(); - org.eclipse.jgit.lib.ReflogReader.Entry entry1 = reverseEntries1.get(0); + List reverseEntries1 = db.getReflogReader("refs/heads/abc").getReverseEntries(); + org.eclipse.jgit.storage.file.ReflogReader.Entry entry1 = reverseEntries1.get(0); assertEquals(1, reverseEntries1.size()); assertEquals(ObjectId.zeroId(), entry1.getOldId()); assertEquals(r.getObjectId(), entry1.getNewId()); assertEquals(new PersonIdent(db).toString(), entry1.getWho().toString()); assertEquals("", entry1.getComment()); - List reverseEntries2 = db.getReflogReader("HEAD").getReverseEntries(); + List reverseEntries2 = db.getReflogReader("HEAD").getReverseEntries(); assertEquals(0, reverseEntries2.size()); } @@ -326,7 +339,7 @@ public void testUpdateRefDetached() throws Exception { // 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 void testUpdateRefDetachedUnbornHead() throws Exception { // 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()); @@ -677,9 +690,9 @@ public void tryRenameWhenLocked(String toLock, String fromName, ObjectId oldHeadId = db.resolve(Constants.HEAD); writeReflog(db, oldfromId, oldfromId, "Just a message", fromName); - List oldFromLog = db + List oldFromLog = db .getReflogReader(fromName).getReverseEntries(); - List oldHeadLog = oldHeadId != null ? db + List 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 similarity index 97% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogReaderTest.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ReflogReaderTest.java index 6144851fc..1d268a474 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.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 similarity index 97% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositorySetupWorkDirTest.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java index 52cb46bae..caaeda2b0 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,13 +42,16 @@ * 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.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 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 similarity index 98% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0003_Basic.java index 484692ee2..c9013a6a0 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; @@ -54,6 +54,21 @@ import java.io.PrintWriter; import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.Commit; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileTreeEntry; +import org.eclipse.jgit.lib.ObjectDatabase; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectWriter; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.SampleDataRepositoryTestCase; +import org.eclipse.jgit.lib.Tag; +import org.eclipse.jgit.lib.Tree; +import org.eclipse.jgit.lib.TreeEntry; +import org.eclipse.jgit.lib.WriteTree; public class T0003_Basic extends SampleDataRepositoryTestCase { public void test001_Initalize() { 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 similarity index 94% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java index 54816f6f5..38b532726 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 { 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 similarity index 95% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheGetTest.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java index 8ff022ddc..ec512cf71 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.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; 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 similarity index 98% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheReconfigureTest.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheReconfigureTest.java index 9e093c85b..e52b19d92 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 similarity index 98% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/XInputStream.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/XInputStream.java index eef32b927..9978c8e13 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/transport/IndexPackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/IndexPackTest.java index e18f741ac..110804f91 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.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 03726cba0..cb1ce6f38 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.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.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.storage.file.ObjectDirectory; import org.eclipse.jgit.util.NB; import org.eclipse.jgit.util.TemporaryBuffer; diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index 81441dbdb..e96ba038d 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -19,6 +19,7 @@ Export-Package: org.eclipse.jgit;version="0.9.0", org.eclipse.jgit.revplot;version="0.9.0", org.eclipse.jgit.revwalk;version="0.9.0", org.eclipse.jgit.revwalk.filter;version="0.9.0", + org.eclipse.jgit.storage.file;version="0.9.0", org.eclipse.jgit.transport;version="0.9.0", org.eclipse.jgit.treewalk;version="0.9.0", org.eclipse.jgit.treewalk.filter;version="0.9.0", diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java index 9ced005a4..6b1349ade 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java @@ -64,10 +64,10 @@ import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.UnmergedPathException; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.LockFile; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.LockFile; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.MutableInteger; import org.eclipse.jgit.util.NB; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java index f5dd7eec7..90929e721 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java @@ -21,6 +21,9 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.RepositoryCache.FileKey; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.storage.file.FileRepository; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.SystemReader; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java index 80d8fff53..a6f6b2507 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java @@ -66,6 +66,7 @@ import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevSort; +import org.eclipse.jgit.storage.file.PackIndexWriter; /** *

      diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java index f2738245d..4acb3ecd8 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.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 6b91481d3..65f75094e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -69,6 +69,7 @@ import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.ReflogReader; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java index 4b0e1f690..f9185e8f2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java @@ -45,6 +45,8 @@ import java.io.File; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; + /** * Base class to support constructing a {@link Repository}. *

      diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java index 39d734a32..dc5eae517 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.concurrent.ConcurrentHashMap; import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java similarity index 98% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java index 0c5c81899..840244b77 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,7 +43,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/ByteBufferWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java similarity index 98% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java index 794d7428e..52bc00f35 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; 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 similarity index 99% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java index 69d255c78..5c77cff01 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; 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 similarity index 92% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDirectory.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java index a32571e60..79730d055 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,11 +42,21 @@ * 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.lib.ObjectToPack; +import org.eclipse.jgit.lib.PackWriter; + /** * The cached instance of an {@link ObjectDirectory}. *

      diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java similarity index 94% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java index d0e98a2a9..1a5fe8ea9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java @@ -44,12 +44,14 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.IOException; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; /** Reads a deltified object which uses an offset to find its base. */ class DeltaOfsPackedObjectLoader extends DeltaPackedObjectLoader { @@ -72,7 +74,7 @@ public int getRawType() { } @Override - public ObjectId getDeltaBase() throws IOException { + ObjectId getDeltaBase() throws IOException { final ObjectId id = pack.findObjectForOffset(deltaBase); if (id == null) throw new CorruptObjectException( diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java similarity index 95% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java index bbc1c62a8..4a9323ccc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.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.text.MessageFormat; @@ -52,6 +52,8 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.BinaryDelta; +import org.eclipse.jgit.lib.Constants; /** Reader for a deltified object stored in a pack file. */ abstract class DeltaPackedObjectLoader extends PackedObjectLoader { @@ -67,7 +69,7 @@ abstract class DeltaPackedObjectLoader extends PackedObjectLoader { } @Override - public void materialize(final WindowCursor curs) throws IOException { + void materialize(final WindowCursor curs) throws IOException { if (cachedBytes != null) { return; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java similarity index 94% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java index 9f7589c29..40e1975e9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java @@ -44,11 +44,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.IOException; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; /** Reads a deltified object which uses an {@link ObjectId} to find its base. */ class DeltaRefPackedObjectLoader extends DeltaPackedObjectLoader { @@ -74,7 +76,7 @@ public int getRawType() { } @Override - public ObjectId getDeltaBase() throws IOException { + ObjectId getDeltaBase() throws IOException { return deltaBase; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java similarity index 98% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java index eb0091791..b39efb0e8 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 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/lib/FileObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java similarity index 95% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java index 5328b327e..6266a7c08 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java @@ -41,11 +41,18 @@ * 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.ObjectDatabase; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.lib.PackWriter; + abstract class FileObjectDatabase extends ObjectDatabase { @Override public ObjectReader newReader() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepository.java similarity index 91% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepository.java index f45ffdf14..c86549b2e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepository.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.File; import java.io.IOException; @@ -54,8 +54,16 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.lib.FileObjectDatabase.AlternateHandle; -import org.eclipse.jgit.lib.FileObjectDatabase.AlternateRepository; +import org.eclipse.jgit.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; /** @@ -115,7 +123,16 @@ public FileRepository(final File gitDir) throws IOException { this(new FileRepositoryBuilder().setGitDir(gitDir).setup()); } - FileRepository(final BaseRepositoryBuilder options) throws IOException { + /** + * 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()); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java similarity index 97% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepositoryBuilder.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.java index c0220d979..31d3e99ad 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepositoryBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepositoryBuilder.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.BaseRepositoryBuilder; + /** * Constructs a {@link FileRepository}. *

      diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectRepresentation.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java similarity index 95% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectRepresentation.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java index f8535bdf2..4949b507e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectRepresentation.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java @@ -41,10 +41,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.IOException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.StoredObjectRepresentation; + class LocalObjectRepresentation extends StoredObjectRepresentation { final PackedObjectLoader ldr; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java similarity index 94% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java index 8db58707e..9ee43f398 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LocalObjectToPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java @@ -41,8 +41,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; +import org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.lib.StoredObjectRepresentation; import org.eclipse.jgit.revwalk.RevObject; /** {@link ObjectToPack} for {@link ObjectDirectory}. */ 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 similarity index 99% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java index 13f158ded..ad89a2484 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.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 similarity index 97% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java index bf8f32334..8a8055605 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,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.File; @@ -62,6 +62,15 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.PackMismatchException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ObjectDatabase; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.lib.PackWriter; +import org.eclipse.jgit.lib.RepositoryCache; import org.eclipse.jgit.lib.RepositoryCache.FileKey; import org.eclipse.jgit.util.FS; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java similarity index 95% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java index 146d2d625..e6ed54022 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.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.EOFException; import java.io.File; @@ -58,6 +58,11 @@ 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 { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java similarity index 98% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java index ab25f6188..e5f6f03f4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.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.EOFException; import java.io.File; @@ -65,6 +65,11 @@ import org.eclipse.jgit.errors.PackInvalidException; import org.eclipse.jgit.errors.PackMismatchException; import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.lib.PackOutputStream; import org.eclipse.jgit.util.LongList; import org.eclipse.jgit.util.NB; import org.eclipse.jgit.util.RawParseUtils; 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 similarity index 98% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java index 13985e78e..62d1c9d8f 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 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 similarity index 97% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java index bb7cd8b53..3b68edc19 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.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 similarity index 98% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java index 128b2df8c..cef7cc429 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 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 similarity index 98% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriter.java index 4d2714bc5..6bd73adcb 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.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 similarity index 98% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV1.java index eb44b3a8c..722ab0e06 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 similarity index 98% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java index b6ac7b89e..21ebd1ca9 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/lib/PackLock.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackLock.java similarity index 97% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackLock.java index de8e3fa63..be250114c 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 .keep 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 similarity index 97% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java index f4f57aed4..96abaeefd 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; /** *

      diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java similarity index 93% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java index 47f5e67a7..e784f3d18 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java @@ -44,10 +44,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.file; import java.io.IOException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; + /** * Base class for a set of object loader classes for packed objects. */ @@ -92,7 +95,7 @@ abstract class PackedObjectLoader extends ObjectLoader { * @throws IOException * the object cannot be read. */ - public abstract void materialize(WindowCursor curs) throws IOException; + abstract void materialize(WindowCursor curs) throws IOException; public final int getType() { return objectType; @@ -110,7 +113,7 @@ public final byte[] getCachedBytes() { /** * @return offset of object header within pack file */ - public final long getObjectOffset() { + final long getObjectOffset() { return objectOffset; } @@ -120,5 +123,5 @@ public final long getObjectOffset() { * @throws IOException * when delta base cannot read. */ - public abstract ObjectId getDeltaBase() throws IOException; + abstract ObjectId getDeltaBase() throws IOException; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java similarity index 98% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java index 13e9c22d9..f7ffa3e39 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; @@ -76,6 +76,18 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.ObjectWritingException; import org.eclipse.jgit.events.RefsChangedEvent; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.CoreConfig; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefComparator; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.RefWriter; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.SymbolicRef; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; 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 similarity index 97% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java index fec00d932..b43b70f1e 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; 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 similarity index 96% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryUpdate.java index 447be104a..8d35ec34f 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 similarity index 96% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ReflogReader.java index 4c5503f32..75214308d 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.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/lib/UnpackedObjectCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectCache.java similarity index 99% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectCache.java index 3cef48242..92f482425 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/UnpackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java similarity index 97% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java index cd2eb38ef..054a4ae37 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.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 org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.InflaterCache; +import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.MutableInteger; import org.eclipse.jgit.util.RawParseUtils; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java similarity index 94% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java index fcfa57339..ba7781a0f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.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.IOException; import java.text.MessageFormat; @@ -51,6 +51,8 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; /** Reader for a non-delta (just deflated) object in a pack file. */ class WholePackedObjectLoader extends PackedObjectLoader { @@ -64,7 +66,7 @@ class WholePackedObjectLoader extends PackedObjectLoader { } @Override - public void materialize(final WindowCursor curs) throws IOException { + void materialize(final WindowCursor curs) throws IOException { if (cachedBytes != null) { return; } @@ -104,7 +106,7 @@ public long getRawSize() { } @Override - public ObjectId getDeltaBase() { + ObjectId getDeltaBase() { return null; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCache.java similarity index 99% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCache.java index a44a30ee2..39633ee5e 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 similarity index 98% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java index 2d8aef34c..48d7018e4 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 similarity index 94% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java index 98916efcb..a88261162 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,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.util.zip.DataFormatException; @@ -50,6 +50,15 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.InflaterCache; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.ObjectReuseAsIs; +import org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.lib.PackOutputStream; +import org.eclipse.jgit.lib.PackWriter; import org.eclipse.jgit.revwalk.RevObject; /** Active handle to a ByteWindow. */ 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 2819ae26d..8c336c525 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.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.RevWalk; import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter; import org.eclipse.jgit.revwalk.filter.RevFilter; +import org.eclipse.jgit.storage.file.PackLock; import org.eclipse.jgit.transport.PacketLineIn.AckNackResult; import org.eclipse.jgit.util.TemporaryBuffer; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java index 3b97dfc0d..98ecc5540 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.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; -import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.PackLock; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java index 50c0866f2..9dc54da00 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 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 27505bef6..72d73eb59 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.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.LockFile; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.LockFile; +import org.eclipse.jgit.storage.file.PackLock; class FetchProcess { /** Transport we will fetch over. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java index 0cd673369..5a37825ce 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java @@ -75,11 +75,11 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdSubclassMap; import org.eclipse.jgit.lib.ObjectLoader; -import org.eclipse.jgit.lib.PackIndexWriter; -import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.storage.file.PackIndexWriter; +import org.eclipse.jgit.storage.file.PackLock; import org.eclipse.jgit.util.NB; /** Indexes Git pack files for local use. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java index 91105cc8a..1597a35fe 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.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdSubclassMap; -import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; @@ -86,6 +85,7 @@ import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.PackLock; import org.eclipse.jgit.transport.ReceiveCommand.Result; import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; import org.eclipse.jgit.util.io.InterruptTimer; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java index 71e7bf285..0f4c1314a 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.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 ae54a155a..c9b18be1f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java @@ -60,8 +60,8 @@ import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileRepository; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.FileRepository; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.io.MessageWriter; import org.eclipse.jgit.util.io.StreamCopyThread; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java index c9d2e9e19..89dd6b490 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java @@ -68,15 +68,11 @@ import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectChecker; -import org.eclipse.jgit.lib.ObjectDirectory; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.PackIndex; -import org.eclipse.jgit.lib.PackLock; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.UnpackedObjectLoader; import org.eclipse.jgit.revwalk.DateRevQueue; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; @@ -84,6 +80,10 @@ import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.ObjectDirectory; +import org.eclipse.jgit.storage.file.PackIndex; +import org.eclipse.jgit.storage.file.PackLock; +import org.eclipse.jgit.storage.file.UnpackedObjectLoader; import org.eclipse.jgit.treewalk.TreeWalk; /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java index f1743b378..0e2adae50 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.ObjectIdRef; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.RefDirectory; +import org.eclipse.jgit.storage.file.RefDirectory; import org.eclipse.jgit.util.IO; /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java index 9d7feb08f..f4382eb18 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.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. From 86547022f02b8e37bb88c8501f1c8c9e59d3b647 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 26 Jun 2010 17:37:16 -0700 Subject: [PATCH 042/103] Tighten up local packed object representation during packing Rather than making a loader, and then using that to fill the object representation, parse the header and set up our data directly. This saves some time, as we don't waste cycles on information we won't use right now. The weight computed for a representation is now its actual stored size in the pack file, rather than its inflated size. This accounts for changes made when the compression level is modified on the repository. It is however more costly to determine the weight of the object, since we have to find its length in the pack. To try and recover that cost we now cache the length as part of our ObjectToPack record, so it doesn't have to be found during the output phase. A LocalObjectToPack now costs us (assuming 32 bit pointers): (32 bit) (64 bit) vm header: 8 bytes 8 bytes ObjectId: 20 bytes 20 bytes PackedObjectInfo: 12 bytes 12 bytes ObjectToPack: 8 bytes 12 bytes LocalOTP: 20 bytes 24 bytes ----------- --------- 68 bytes 74 bytes Change-Id: I923d2736186eb2ac8ab498d3eb137e17930fcb50 Signed-off-by: Shawn O. Pearce --- .../file/LocalObjectRepresentation.java | 73 ++++++++++++++----- .../jgit/storage/file/LocalObjectToPack.java | 16 ++-- .../jgit/storage/file/ObjectDirectory.java | 6 +- .../eclipse/jgit/storage/file/PackFile.java | 72 +++++++++++++++--- .../jgit/storage/file/WindowCursor.java | 2 +- 5 files changed, 128 insertions(+), 41 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java index 4949b507e..6ddd66e6d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java @@ -49,35 +49,70 @@ import org.eclipse.jgit.lib.StoredObjectRepresentation; class LocalObjectRepresentation extends StoredObjectRepresentation { - final PackedObjectLoader ldr; - - LocalObjectRepresentation(PackedObjectLoader ldr) { - this.ldr = ldr; + 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 int getFormat() { - if (ldr instanceof DeltaPackedObjectLoader) - return PACK_DELTA; - if (ldr instanceof WholePackedObjectLoader) - return PACK_WHOLE; - return FORMAT_OTHER; + 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; } + 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; + } + + PackFile pack; + + long offset; + + long length; + + private long baseOffset; + + private ObjectId baseId; + @Override public int getWeight() { - long sz = ldr.getRawSize(); - if (Integer.MAX_VALUE < sz) - return WEIGHT_UNKNOWN; - return (int) sz; + return (int) Math.min(length, Integer.MAX_VALUE); } @Override public ObjectId getDeltaBase() { - try { - return ldr.getDeltaBase(); - } catch (IOException e) { - return null; + if (baseId == null && getFormat() == PACK_DELTA) { + try { + baseId = pack.findObjectForOffset(baseOffset); + } catch (IOException error) { + return null; + } + } + return baseId; + } + + 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 index 9ee43f398..e1b254267 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java @@ -50,10 +50,13 @@ /** {@link ObjectToPack} for {@link ObjectDirectory}. */ class LocalObjectToPack extends ObjectToPack { /** Pack to reuse compressed data from, otherwise null. */ - PackFile copyFromPack; + PackFile pack; - /** Offset of the object's header in {@link #copyFromPack}. */ - long copyOffset; + /** 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); @@ -61,8 +64,9 @@ class LocalObjectToPack extends ObjectToPack { @Override public void select(StoredObjectRepresentation ref) { - LocalObjectRepresentation ptr = (LocalObjectRepresentation)ref; - this.copyFromPack = ptr.ldr.pack; - this.copyOffset = ptr.ldr.objectOffset; + 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/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java index 8a8055605..8db258bb4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java @@ -299,9 +299,9 @@ void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, SEARCH: for (;;) { for (final PackFile p : pList.packs) { try { - PackedObjectLoader ldr = p.get(curs, otp); - if (ldr != null) - packer.select(otp, new LocalObjectRepresentation(ldr)); + LocalObjectRepresentation rep = p.representation(curs, otp); + if (rep != null) + packer.select(otp, rep); } catch (PackMismatchException e) { // Pack was modified; refresh the entire pack list. // 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 index e5f6f03f4..01db79dcd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java @@ -309,7 +309,7 @@ private void copyAsIs2(PackOutputStream out, LocalObjectToPack src, // Rip apart the header so we can discover the size. // - readFully(src.copyOffset, buf, 0, 20, curs); + readFully(src.offset, buf, 0, 20, curs); int c = buf[0] & 0xff; final int typeCode = (c >> 4) & 7; long inflatedLength = c & 15; @@ -331,7 +331,7 @@ private void copyAsIs2(PackOutputStream out, LocalObjectToPack src, crc1.update(buf, 0, headerCnt); crc2.update(buf, 0, headerCnt); - readFully(src.copyOffset + headerCnt, buf, 0, 20, curs); + readFully(src.offset + headerCnt, buf, 0, 20, curs); crc1.update(buf, 0, 20); crc2.update(buf, 0, headerCnt); headerCnt += 20; @@ -340,8 +340,8 @@ private void copyAsIs2(PackOutputStream out, LocalObjectToPack src, crc2.update(buf, 0, headerCnt); } - final long dataOffset = src.copyOffset + headerCnt; - final long dataLength; + final long dataOffset = src.offset + headerCnt; + final long dataLength = src.length; final long expectedCRC; final ByteArrayWindow quickCopy; @@ -349,7 +349,6 @@ private void copyAsIs2(PackOutputStream out, LocalObjectToPack src, // we report it missing instead. // try { - dataLength = findEndOffset(src.copyOffset) - dataOffset; quickCopy = curs.quickCopy(this, dataOffset, dataLength); if (idx().hasCRC32Support()) { @@ -370,10 +369,10 @@ private void copyAsIs2(PackOutputStream out, LocalObjectToPack src, } } if (crc1.getValue() != expectedCRC) { - setCorrupt(src.copyOffset); + setCorrupt(src.offset); throw new CorruptObjectException(MessageFormat.format( JGitText.get().objectAtHasBadZlibStream, - src.copyOffset, getPackFile())); + src.offset, getPackFile())); } } else { // We don't have a CRC32 code in the index, so compute it @@ -399,20 +398,20 @@ private void copyAsIs2(PackOutputStream out, LocalObjectToPack src, } } if (!inf.finished() || inf.getBytesRead() != dataLength) { - setCorrupt(src.copyOffset); + setCorrupt(src.offset); throw new EOFException(MessageFormat.format( JGitText.get().shortCompressedStreamAt, - src.copyOffset)); + src.offset)); } expectedCRC = crc1.getValue(); } } catch (DataFormatException dataFormat) { - setCorrupt(src.copyOffset); + setCorrupt(src.offset); CorruptObjectException corruptObject = new CorruptObjectException( MessageFormat.format( JGitText.get().objectAtHasBadZlibStream, - src.copyOffset, getPackFile())); + src.offset, getPackFile())); corruptObject.initCause(dataFormat); StoredObjectRepresentationNotAvailableException gone; @@ -458,7 +457,7 @@ private void copyAsIs2(PackOutputStream out, LocalObjectToPack src, } if (crc2.getValue() != expectedCRC) { throw new CorruptObjectException(MessageFormat.format(JGitText - .get().objectAtHasBadZlibStream, src.copyOffset, + .get().objectAtHasBadZlibStream, src.offset, getPackFile())); } } @@ -661,6 +660,55 @@ private PackedObjectLoader reader(final WindowCursor curs, } } + 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; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java index a88261162..d359de07e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java @@ -103,7 +103,7 @@ public void selectObjectRepresentation(PackWriter packer, ObjectToPack otp) public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp) throws IOException, StoredObjectRepresentationNotAvailableException { LocalObjectToPack src = (LocalObjectToPack) otp; - src.copyFromPack.copyAsIs(out, src, this); + src.pack.copyAsIs(out, src, this); } /** From 68518ca3aadc83d99e97fcef171b137903d2d05a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 26 Jun 2010 17:47:53 -0700 Subject: [PATCH 043/103] Remove getRawSize, getRawType from ObjectLoader These were only used by PackWriter to help it filter object representations. Their only user disappeared when we rewrote the object selection code path to use the new representation type. Change-Id: I9ed676bfe4f87fcf94aa21e53bda43115912e145 Signed-off-by: Shawn O. Pearce --- .../jgit/storage/file/T0004_PackReader.java | 4 ++-- .../jgit/storage/file/WindowCacheGetTest.java | 7 ++----- .../org/eclipse/jgit/lib/ObjectLoader.java | 13 ------------- .../file/DeltaOfsPackedObjectLoader.java | 19 ------------------- .../storage/file/DeltaPackedObjectLoader.java | 5 ----- .../file/DeltaRefPackedObjectLoader.java | 11 ----------- .../eclipse/jgit/storage/file/PackFile.java | 2 +- .../jgit/storage/file/PackedObjectLoader.java | 18 ------------------ .../storage/file/UnpackedObjectLoader.java | 10 ---------- .../storage/file/WholePackedObjectLoader.java | 16 ---------------- 10 files changed, 5 insertions(+), 100 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java index 38b532726..59ad42db2 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java @@ -71,7 +71,7 @@ public void test003_lookupCompressedObject() throws IOException { assertNotNull(or); assertEquals(Constants.OBJ_TREE, or.getType()); assertEquals(35, or.getSize()); - assertEquals(7736, or.getObjectOffset()); + assertEquals(7736, or.objectOffset); pr.close(); } @@ -85,6 +85,6 @@ public void test004_lookupDeltifiedObject() throws IOException { assertTrue(or instanceof PackedObjectLoader); assertEquals(Constants.OBJ_BLOB, or.getType()); assertEquals(18009, or.getSize()); - assertEquals(516, ((PackedObjectLoader) or).getObjectOffset()); + assertEquals(516, ((PackedObjectLoader) or).objectOffset); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java index ec512cf71..47d8a9582 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java @@ -77,7 +77,7 @@ public void setUp() throws Exception { 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]); toLoad.add(o); @@ -130,8 +130,7 @@ private void doCacheTests() throws IOException { assertNotNull(or); assertTrue(or instanceof PackedObjectLoader); assertEquals(o.type, or.getType()); - assertEquals(o.rawSize, or.getRawSize()); - assertEquals(o.offset, ((PackedObjectLoader) or).getObjectOffset()); + assertEquals(o.offset, ((PackedObjectLoader) or).objectOffset); } } @@ -140,8 +139,6 @@ private class TestObject { int type; - int rawSize; - long offset; void setType(final String typeStr) throws CorruptObjectException { 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 4839a1c28..1a8d1ba9b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java @@ -89,17 +89,4 @@ public final byte[] getBytes() { * @return the cached bytes of this object. Do not modify it. */ public abstract byte[] getCachedBytes(); - - /** - * @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}). - */ - public abstract int getRawType(); - - /** - * @return raw size of object from object header (pack, loose file). - * Interpretation of this value depends on {@link #getRawType()}. - */ - public abstract long getRawSize(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java index 1a5fe8ea9..059912a9c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java @@ -48,11 +48,6 @@ import java.io.IOException; -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.ObjectId; - /** Reads a deltified object which uses an offset to find its base. */ class DeltaOfsPackedObjectLoader extends DeltaPackedObjectLoader { private final long deltaBase; @@ -67,18 +62,4 @@ protected PackedObjectLoader getBaseLoader(final WindowCursor curs) throws IOException { return pack.resolveBase(curs, deltaBase); } - - @Override - public int getRawType() { - return Constants.OBJ_OFS_DELTA; - } - - @Override - ObjectId getDeltaBase() throws IOException { - final ObjectId id = pack.findObjectForOffset(deltaBase); - if (id == null) - throw new CorruptObjectException( - JGitText.get().offsetWrittenDeltaBaseForObjectNotFoundInAPack); - return id; - } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java index 4a9323ccc..135282589 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java @@ -104,11 +104,6 @@ void materialize(final WindowCursor curs) throws IOException { } } - @Override - public long getRawSize() { - return deltaSize; - } - /** * @param curs * temporary thread storage during data access. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java index 40e1975e9..741ac7438 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java @@ -49,7 +49,6 @@ import java.io.IOException; import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; /** Reads a deltified object which uses an {@link ObjectId} to find its base. */ @@ -69,14 +68,4 @@ protected PackedObjectLoader getBaseLoader(final WindowCursor curs) throw new MissingObjectException(deltaBase, "delta base"); return or; } - - @Override - public int getRawType() { - return Constants.OBJ_REF_DELTA; - } - - @Override - ObjectId getDeltaBase() throws IOException { - return deltaBase; - } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java index 01db79dcd..783422bf3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java @@ -209,7 +209,7 @@ public boolean hasObject(final AnyObjectId id) throws IOException { * @throws IOException * the pack file or the index could not be read. */ - public PackedObjectLoader get(final WindowCursor curs, final AnyObjectId id) + PackedObjectLoader get(final WindowCursor curs, final AnyObjectId id) throws IOException { final long offset = idx().findOffset(id); return 0 < offset && !isCorrupt(offset) ? reader(curs, offset) : null; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java index e784f3d18..b575781cf 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java @@ -48,7 +48,6 @@ import java.io.IOException; -import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; /** @@ -86,8 +85,6 @@ abstract class PackedObjectLoader extends ObjectLoader { *

    • {@link #getType()}
    • *
    • {@link #getSize()}
    • *
    • {@link #getBytes()}, {@link #getCachedBytes}
    • - *
    • {@link #getRawSize()}
    • - *
    • {@link #getRawType()}
    • *
    * * @param curs @@ -109,19 +106,4 @@ public final long getSize() { public final byte[] getCachedBytes() { return cachedBytes; } - - /** - * @return offset of object header within pack file - */ - final long getObjectOffset() { - return objectOffset; - } - - /** - * @return id of delta base object for this object representation. null if - * object is not stored as delta. - * @throws IOException - * when delta base cannot read. - */ - abstract ObjectId getDeltaBase() throws IOException; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java index 054a4ae37..b85ec149e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java @@ -214,14 +214,4 @@ public long getSize() { public byte[] getCachedBytes() { return bytes; } - - @Override - public int getRawType() { - return objectType; - } - - @Override - public long getRawSize() { - return objectSize; - } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java index ba7781a0f..70fa616f7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java @@ -52,7 +52,6 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.ObjectId; /** Reader for a non-delta (just deflated) object in a pack file. */ class WholePackedObjectLoader extends PackedObjectLoader { @@ -94,19 +93,4 @@ void materialize(final WindowCursor curs) throws IOException { throw coe; } } - - @Override - public int getRawType() { - return objectType; - } - - @Override - public long getRawSize() { - return objectSize; - } - - @Override - ObjectId getDeltaBase() { - return null; - } } From 71aace52f7bfc36c65c92ab54c6f020dffc873ab Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 26 Jun 2010 18:34:37 -0700 Subject: [PATCH 044/103] Simplify ObjectLoaders coming from PackFile We no longer need an ObjectLoader to be lazy and try to delay the materialization of the object content. That was done only to support PackWriter searching for a good reuse candidate. Instead, simplify the code base by doing the materialization immediately when the loader asks for it, because any caller asking for the loader is going to need the content. Change-Id: Id867b1004529744f234ab8f9cfab3d2c52ca3bd0 Signed-off-by: Shawn O. Pearce --- .../jgit/storage/file/T0004_PackReader.java | 2 - .../jgit/storage/file/WindowCacheGetTest.java | 5 +- .../org/eclipse/jgit/JGitText.properties | 1 + .../src/org/eclipse/jgit/JGitText.java | 1 + .../file/DeltaOfsPackedObjectLoader.java | 65 --------- .../storage/file/DeltaPackedObjectLoader.java | 115 ---------------- .../file/DeltaRefPackedObjectLoader.java | 71 ---------- .../jgit/storage/file/ObjectDirectory.java | 4 +- .../eclipse/jgit/storage/file/PackFile.java | 126 ++++++++++++------ .../jgit/storage/file/PackedObjectLoader.java | 55 ++------ .../storage/file/WholePackedObjectLoader.java | 96 ------------- 11 files changed, 100 insertions(+), 441 deletions(-) delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java index 59ad42db2..70016f9ea 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java @@ -71,7 +71,6 @@ public void test003_lookupCompressedObject() throws IOException { assertNotNull(or); assertEquals(Constants.OBJ_TREE, or.getType()); assertEquals(35, or.getSize()); - assertEquals(7736, or.objectOffset); pr.close(); } @@ -85,6 +84,5 @@ public void test004_lookupDeltifiedObject() throws IOException { assertTrue(or instanceof PackedObjectLoader); assertEquals(Constants.OBJ_BLOB, or.getType()); assertEquals(18009, or.getSize()); - assertEquals(516, ((PackedObjectLoader) or).objectOffset); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java index 47d8a9582..89b91558b 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java @@ -79,7 +79,7 @@ public void setUp() throws Exception { o.setType(parts[1]); // 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 { @@ -130,7 +130,6 @@ private void doCacheTests() throws IOException { assertNotNull(or); assertTrue(or instanceof PackedObjectLoader); assertEquals(o.type, or.getType()); - assertEquals(o.offset, ((PackedObjectLoader) or).objectOffset); } } @@ -139,8 +138,6 @@ private class TestObject { int type; - 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/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties index f6e4ea5b4..aece51874 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties @@ -221,6 +221,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: diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java index 377e9c119..301e411ee 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java @@ -281,6 +281,7 @@ public static JGitText get() { /***/ public String mergeStrategyDoesNotSupportHeads; /***/ public String mergeUsingStrategyResultedInDescription; /***/ public String missingAccesskey; + /***/ public String missingDeltaBase; /***/ public String missingForwardImageInGITBinaryPatch; /***/ public String missingObject; /***/ public String missingPrerequisiteCommits; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java deleted file mode 100644 index 059912a9c..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaOfsPackedObjectLoader.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.storage.file; - -import java.io.IOException; - -/** 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); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java deleted file mode 100644 index 135282589..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaPackedObjectLoader.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.storage.file; - -import java.io.IOException; -import java.text.MessageFormat; -import java.util.zip.DataFormatException; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.lib.BinaryDelta; -import org.eclipse.jgit.lib.Constants; - -/** Reader for a deltified object stored in a pack file. */ -abstract class DeltaPackedObjectLoader extends PackedObjectLoader { - private static final int OBJ_COMMIT = Constants.OBJ_COMMIT; - - private final int deltaSize; - - DeltaPackedObjectLoader(final PackFile pr, final long objectOffset, - final int headerSize, final int deltaSz) { - super(pr, objectOffset, headerSize); - objectType = -1; - deltaSize = deltaSz; - } - - @Override - void materialize(final WindowCursor curs) throws IOException { - if (cachedBytes != null) { - return; - } - - if (objectType != OBJ_COMMIT) { - UnpackedObjectCache.Entry cache = pack.readCache(objectOffset); - if (cache != null) { - curs.release(); - objectType = cache.type; - objectSize = cache.data.length; - cachedBytes = cache.data; - return; - } - } - - try { - final PackedObjectLoader baseLoader = getBaseLoader(curs); - baseLoader.materialize(curs); - cachedBytes = BinaryDelta.apply(baseLoader.getCachedBytes(), pack - .decompress(objectOffset + headerSize, deltaSize, curs)); - curs.release(); - objectType = baseLoader.getType(); - objectSize = cachedBytes.length; - if (objectType != OBJ_COMMIT) - pack.saveCache(objectOffset, cachedBytes, objectType); - } catch (DataFormatException dfe) { - final CorruptObjectException coe; - coe = new CorruptObjectException(MessageFormat.format(JGitText.get().objectAtHasBadZlibStream, - objectOffset, pack.getPackFile())); - coe.initCause(dfe); - throw coe; - } - } - - /** - * @param curs - * temporary thread storage during data access. - * @return the object loader for the base object - * @throws IOException - */ - protected abstract PackedObjectLoader getBaseLoader(WindowCursor curs) - throws IOException; -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java deleted file mode 100644 index 741ac7438..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/DeltaRefPackedObjectLoader.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.storage.file; - -import java.io.IOException; - -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.lib.ObjectId; - -/** Reads a deltified object which uses an {@link ObjectId} to find its base. */ -class DeltaRefPackedObjectLoader extends DeltaPackedObjectLoader { - private final ObjectId deltaBase; - - DeltaRefPackedObjectLoader(final PackFile pr, final long objectOffset, - final int headerSz, final int deltaSz, final ObjectId base) { - super(pr, objectOffset, headerSz, deltaSz); - deltaBase = base; - } - - protected PackedObjectLoader getBaseLoader(final WindowCursor curs) - throws IOException { - final PackedObjectLoader or = pack.get(curs, deltaBase); - if (or == null) - throw new MissingObjectException(deltaBase, "delta base"); - return or; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java index 8db258bb4..2936efd29 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java @@ -273,10 +273,8 @@ ObjectLoader openObject1(final WindowCursor curs, for (final PackFile p : pList.packs) { try { final PackedObjectLoader ldr = p.get(curs, objectId); - if (ldr != null) { - ldr.materialize(curs); + if (ldr != null) return ldr; - } } catch (PackMismatchException e) { // Pack was modified; refresh the entire pack list. // 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 index 783422bf3..de9c8eca5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java @@ -62,10 +62,12 @@ 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.BinaryDelta; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectToPack; @@ -171,7 +173,7 @@ final PackedObjectLoader resolveBase(final WindowCursor curs, final long ofs) throw new CorruptObjectException(MessageFormat.format(JGitText .get().objectAtHasBadZlibStream, ofs, getPackFile())); } - return reader(curs, ofs); + return load(curs, ofs); } /** @return the File object which locates this pack on disk. */ @@ -212,7 +214,7 @@ public boolean hasObject(final AnyObjectId id) throws IOException { PackedObjectLoader get(final WindowCursor curs, final AnyObjectId id) throws IOException { final long offset = idx().findOffset(id); - return 0 < offset && !isCorrupt(offset) ? reader(curs, offset) : null; + return 0 < offset && !isCorrupt(offset) ? load(curs, offset) : null; } /** @@ -273,17 +275,19 @@ ObjectId findObjectForOffset(final long offset) throws IOException { return getReverseIdx().findObject(offset); } - final UnpackedObjectCache.Entry readCache(final long position) { + private final UnpackedObjectCache.Entry readCache(final long position) { return UnpackedObjectCache.get(this, position); } - final void saveCache(final long position, final byte[] data, final int type) { + private 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]; + private final byte[] decompress(final long position, final long totalSize, + final WindowCursor curs) throws IOException, DataFormatException { + if (totalSize > Integer.MAX_VALUE) + throw new OutOfMemoryError(); + 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; @@ -615,49 +619,91 @@ private void onOpenPack() throws IOException { , getPackFile())); } - private PackedObjectLoader reader(final WindowCursor curs, - final long objOffset) throws IOException { - int p = 0; + private PackedObjectLoader load(final WindowCursor curs, final long pos) + throws IOException { 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; + 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; - dataSize += (c & 0x7f) << shift; + sz += (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); + try { + switch (type) { + case Constants.OBJ_COMMIT: + case Constants.OBJ_TREE: + case Constants.OBJ_BLOB: + case Constants.OBJ_TAG: { + byte[] data = decompress(pos + p, sz, curs); + return new PackedObjectLoader(type, data); } - return new DeltaOfsPackedObjectLoader(this, objOffset, p, - (int) dataSize, objOffset - ofs); + + 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; } - 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 findDeltaBase(ObjectId baseId) throws IOException, + MissingObjectException { + long ofs = idx().findOffset(baseId); + if (ofs < 0) + throw new MissingObjectException(baseId, + JGitText.get().missingDeltaBase); + return ofs; + } + + private PackedObjectLoader loadDelta(final long posData, long sz, + final long posBase, final WindowCursor curs) throws IOException, + DataFormatException { + byte[] data; + int type; + + UnpackedObjectCache.Entry e = readCache(posBase); + if (e != null) { + data = e.data; + type = e.type; + } else { + PackedObjectLoader p = load(curs, posBase); + data = p.getCachedBytes(); + type = p.getType(); + saveCache(posBase, data, type); } + + data = BinaryDelta.apply(data, decompress(posData, sz, curs)); + return new PackedObjectLoader(type, data); } LocalObjectRepresentation representation(final WindowCursor curs, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java index b575781cf..ad4042e17 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java @@ -46,64 +46,29 @@ package org.eclipse.jgit.storage.file; -import java.io.IOException; - import org.eclipse.jgit.lib.ObjectLoader; -/** - * Base class for a set of object loader classes for packed objects. - */ -abstract class PackedObjectLoader extends ObjectLoader { - protected final PackFile pack; +/** Object loaded in from a {@link PackFile}. */ +final class PackedObjectLoader extends ObjectLoader { + private final int type; - /** Position of the first byte of the object's header. */ - protected final long objectOffset; + private final byte[] data; - /** 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; + PackedObjectLoader(int type, byte[] data) { + this.type = type; + this.data = data; } - /** - * Force this object to be loaded into memory and pinned in this loader. - *

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

      - *
    • {@link #getType()}
    • - *
    • {@link #getSize()}
    • - *
    • {@link #getBytes()}, {@link #getCachedBytes}
    • - *
    - * - * @param curs - * temporary thread storage during data access. - * @throws IOException - * the object cannot be read. - */ - abstract void materialize(WindowCursor curs) throws IOException; - public final int getType() { - return objectType; + return type; } public final long getSize() { - return objectSize; + return getCachedBytes().length; } @Override public final byte[] getCachedBytes() { - return cachedBytes; + return data; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java deleted file mode 100644 index 70fa616f7..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WholePackedObjectLoader.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2006-2008, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.storage.file; - -import java.io.IOException; -import java.text.MessageFormat; -import java.util.zip.DataFormatException; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.lib.Constants; - -/** Reader for a non-delta (just deflated) object in a pack file. */ -class WholePackedObjectLoader extends PackedObjectLoader { - private static final int OBJ_COMMIT = Constants.OBJ_COMMIT; - - WholePackedObjectLoader(final PackFile pr, final long objectOffset, - final int headerSize, final int type, final int size) { - super(pr, objectOffset, headerSize); - objectType = type; - objectSize = size; - } - - @Override - void materialize(final WindowCursor curs) throws IOException { - if (cachedBytes != null) { - return; - } - - if (objectType != OBJ_COMMIT) { - UnpackedObjectCache.Entry cache = pack.readCache(objectOffset); - if (cache != null) { - curs.release(); - cachedBytes = cache.data; - return; - } - } - - try { - cachedBytes = pack.decompress(objectOffset + headerSize, - objectSize, curs); - curs.release(); - if (objectType != OBJ_COMMIT) - pack.saveCache(objectOffset, cachedBytes, objectType); - } catch (DataFormatException dfe) { - final CorruptObjectException coe; - coe = new CorruptObjectException(MessageFormat.format(JGitText.get().objectAtHasBadZlibStream, - objectOffset, pack.getPackFile())); - coe.initCause(dfe); - throw coe; - } - } -} From ea21c111cb7ac0c8dc39c4df3fe90c08ddcce066 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 26 Jun 2010 18:48:39 -0700 Subject: [PATCH 045/103] Move PackWriter over to storage.pack.PackWriter Similar to what we did with the file code, move the pack writer into its own package so the related classes and their package private methods are hidden from the rest of the library. Change-Id: Ic1b5c7c8c8d266e90c910d8d68dfc8e93586854f Signed-off-by: Shawn O. Pearce --- org.eclipse.jgit.junit/META-INF/MANIFEST.MF | 1 + .../org/eclipse/jgit/junit/TestRepository.java | 2 +- org.eclipse.jgit.test/META-INF/MANIFEST.MF | 1 + .../jgit/storage/file/ConcurrentRepackTest.java | 2 +- .../jgit/storage/file/PackWriterTest.java | 2 +- org.eclipse.jgit/META-INF/MANIFEST.MF | 1 + ...bjectRepresentationNotAvailableException.java | 2 +- .../src/org/eclipse/jgit/lib/ObjectReader.java | 1 + .../jgit/storage/file/CachedObjectDirectory.java | 4 ++-- .../jgit/storage/file/FileObjectDatabase.java | 4 ++-- .../storage/file/LocalObjectRepresentation.java | 2 +- .../jgit/storage/file/LocalObjectToPack.java | 4 ++-- .../jgit/storage/file/ObjectDirectory.java | 4 ++-- .../org/eclipse/jgit/storage/file/PackFile.java | 6 +++--- .../eclipse/jgit/storage/file/WindowCursor.java | 8 ++++---- .../jgit/{lib => storage/pack}/BinaryDelta.java | 2 +- .../{lib => storage/pack}/ObjectReuseAsIs.java | 3 ++- .../jgit/{lib => storage/pack}/ObjectToPack.java | 4 +++- .../{lib => storage/pack}/PackOutputStream.java | 3 ++- .../jgit/{lib => storage/pack}/PackWriter.java | 16 +++++++++++++--- .../pack}/StoredObjectRepresentation.java | 4 +++- .../jgit/transport/BasePackPushConnection.java | 2 +- .../org/eclipse/jgit/transport/BundleWriter.java | 2 +- .../org/eclipse/jgit/transport/IndexPack.java | 2 +- .../org/eclipse/jgit/transport/UploadPack.java | 2 +- .../jgit/transport/WalkPushConnection.java | 2 +- 26 files changed, 53 insertions(+), 33 deletions(-) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/pack}/BinaryDelta.java (99%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/pack}/ObjectReuseAsIs.java (98%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/pack}/ObjectToPack.java (98%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/pack}/PackOutputStream.java (98%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/pack}/PackWriter.java (97%) rename org.eclipse.jgit/src/org/eclipse/jgit/{lib => storage/pack}/StoredObjectRepresentation.java (97%) diff --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF index ab24b5aaf..7f4dcfddd 100644 --- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF @@ -19,6 +19,7 @@ Import-Package: junit.framework;version="[3.8.2,4.0.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/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java index ec34e3c3a..18275ec71 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 @@ -77,7 +77,6 @@ import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.PackWriter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; @@ -96,6 +95,7 @@ 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; diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index b88ccf346..c8ad25d81 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -29,6 +29,7 @@ Import-Package: junit.framework;version="[3.8.2,4.0.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/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java index db770f777..96b117881 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java @@ -59,11 +59,11 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectWriter; -import org.eclipse.jgit.lib.PackWriter; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryTestCase; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.pack.PackWriter; public class ConcurrentRepackTest extends RepositoryTestCase { public void setUp() throws Exception { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java index cf0d7eb6a..b12359cf6 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java @@ -60,12 +60,12 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.PackWriter; import org.eclipse.jgit.lib.SampleDataRepositoryTestCase; import org.eclipse.jgit.lib.TextProgressMonitor; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.PackIndex.MutableEntry; +import org.eclipse.jgit.storage.pack.PackWriter; import org.eclipse.jgit.transport.IndexPack; import org.eclipse.jgit.util.JGitTestUtil; diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index e96ba038d..23c61af8f 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -20,6 +20,7 @@ Export-Package: org.eclipse.jgit;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/src/org/eclipse/jgit/errors/StoredObjectRepresentationNotAvailableException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/StoredObjectRepresentationNotAvailableException.java index cff449927..e9e3f4d65 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/StoredObjectRepresentationNotAvailableException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/StoredObjectRepresentationNotAvailableException.java @@ -43,7 +43,7 @@ package org.eclipse.jgit.errors; -import org.eclipse.jgit.lib.ObjectToPack; +import org.eclipse.jgit.storage.pack.ObjectToPack; /** A previously selected representation is no longer available. */ public class StoredObjectRepresentationNotAvailableException extends Exception { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java index 670ead9ef..29d2eb6f3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java @@ -46,6 +46,7 @@ import java.io.IOException; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.storage.pack.ObjectReuseAsIs; /** * Reads an {@link ObjectDatabase} for a single thread. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java index 79730d055..194561238 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java @@ -54,8 +54,8 @@ import org.eclipse.jgit.lib.ObjectIdSubclassMap; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectLoader; -import org.eclipse.jgit.lib.ObjectToPack; -import org.eclipse.jgit.lib.PackWriter; +import org.eclipse.jgit.storage.pack.ObjectToPack; +import org.eclipse.jgit.storage.pack.PackWriter; /** * The cached instance of an {@link ObjectDirectory}. 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 index 6266a7c08..f0609897a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java @@ -50,8 +50,8 @@ import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; -import org.eclipse.jgit.lib.ObjectToPack; -import org.eclipse.jgit.lib.PackWriter; +import org.eclipse.jgit.storage.pack.ObjectToPack; +import org.eclipse.jgit.storage.pack.PackWriter; abstract class FileObjectDatabase extends ObjectDatabase { @Override diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java index 6ddd66e6d..08bb8e60d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectRepresentation.java @@ -46,7 +46,7 @@ import java.io.IOException; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.StoredObjectRepresentation; +import org.eclipse.jgit.storage.pack.StoredObjectRepresentation; class LocalObjectRepresentation extends StoredObjectRepresentation { static LocalObjectRepresentation newWhole(PackFile f, long p, long length) { 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 index e1b254267..b4bf37a5e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java @@ -43,9 +43,9 @@ package org.eclipse.jgit.storage.file; -import org.eclipse.jgit.lib.ObjectToPack; -import org.eclipse.jgit.lib.StoredObjectRepresentation; 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 { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java index 2936efd29..7020b7a67 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java @@ -68,10 +68,10 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectLoader; -import org.eclipse.jgit.lib.ObjectToPack; -import org.eclipse.jgit.lib.PackWriter; import org.eclipse.jgit.lib.RepositoryCache; import org.eclipse.jgit.lib.RepositoryCache.FileKey; +import org.eclipse.jgit.storage.pack.ObjectToPack; +import org.eclipse.jgit.storage.pack.PackWriter; import org.eclipse.jgit.util.FS; /** 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 index de9c8eca5..f68019c40 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java @@ -67,11 +67,11 @@ import org.eclipse.jgit.errors.PackMismatchException; import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; import org.eclipse.jgit.lib.AnyObjectId; -import org.eclipse.jgit.lib.BinaryDelta; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectToPack; -import org.eclipse.jgit.lib.PackOutputStream; +import org.eclipse.jgit.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; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java index d359de07e..d5c2fd79d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java @@ -55,11 +55,11 @@ import org.eclipse.jgit.lib.InflaterCache; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; -import org.eclipse.jgit.lib.ObjectReuseAsIs; -import org.eclipse.jgit.lib.ObjectToPack; -import org.eclipse.jgit.lib.PackOutputStream; -import org.eclipse.jgit.lib.PackWriter; import org.eclipse.jgit.revwalk.RevObject; +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. */ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs { 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 similarity index 99% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/BinaryDelta.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/BinaryDelta.java index a59b33533..027ffd62a 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,7 +42,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.pack; import org.eclipse.jgit.JGitText; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReuseAsIs.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectReuseAsIs.java similarity index 98% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReuseAsIs.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectReuseAsIs.java index f87b8301c..a815e9354 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReuseAsIs.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectReuseAsIs.java @@ -41,12 +41,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +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; /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java similarity index 98% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java index 34a969666..773ce44fd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectToPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java @@ -42,8 +42,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.pack; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.transport.PackedObjectInfo; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackOutputStream.java similarity index 98% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackOutputStream.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackOutputStream.java index 48ec2b5a1..bc20f89c7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackOutputStream.java @@ -42,13 +42,14 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +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.util.NB; /** Custom output stream to support {@link PackWriter}. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java similarity index 97% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java index a6f6b2507..031ea2b20 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -42,10 +42,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.pack; -import static org.eclipse.jgit.lib.StoredObjectRepresentation.PACK_DELTA; -import static org.eclipse.jgit.lib.StoredObjectRepresentation.PACK_WHOLE; +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.OutputStream; @@ -62,6 +62,16 @@ 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.CoreConfig; +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.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredObjectRepresentation.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/StoredObjectRepresentation.java similarity index 97% rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredObjectRepresentation.java rename to org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/StoredObjectRepresentation.java index 0eb05f5e0..334ea5ea1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredObjectRepresentation.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/StoredObjectRepresentation.java @@ -41,7 +41,9 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.storage.pack; + +import org.eclipse.jgit.lib.ObjectId; /** * An object representation {@link PackWriter} can consider for packing. 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 44ccd2d6a..72460c91c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java @@ -56,9 +56,9 @@ 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; /** 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 7e91557b0..c5e9baad5 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.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. 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 5a37825ce..ade9ffed8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java @@ -65,7 +65,6 @@ 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; @@ -80,6 +79,7 @@ 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. */ 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 712ad3ff7..17c37cf5b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -60,7 +60,6 @@ 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; @@ -70,6 +69,7 @@ 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; 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 0edf9678e..1ab6081c0 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.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; /** From 5aae041a81a88d54fcc16b6584236bd8bf7fd842 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Jun 2010 09:22:48 -0700 Subject: [PATCH 046/103] Commit: Use Repository.newObjectInserter Everyone else does. This must have been a spot I missed during some sort of squash while developing the series. Change-Id: I62eae50b618f47ee33ad7cf71fc05b724f603201 Signed-off-by: Shawn O. Pearce --- org.eclipse.jgit/src/org/eclipse/jgit/lib/Commit.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 be4818aa5..eeffb08e9 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,7 @@ public void setMessage(final String m) { public void commit() throws IOException { if (getCommitId() != null) throw new IllegalStateException(MessageFormat.format(JGitText.get().commitAlreadyExists, getCommitId())); - ObjectInserter odi = objdb.getObjectDatabase().newInserter(); + ObjectInserter odi = objdb.newObjectInserter(); try { ObjectId id = odi.insert(Constants.OBJ_COMMIT, odi.format(this)); odi.flush(); From b40f02eb1a2444e4a15ac4d5faef28ae8398f02d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Jun 2010 09:35:55 -0700 Subject: [PATCH 047/103] Release ObjectInserter in merge() not mergeImpl() By doing the release at the higher level class, we can ensure the release occurs if the inserter was allocated, even if the implementation forgets to do this. Since the higher level class is what allocated it, it makes sense to have it also do the release. Change-Id: Id617b2db864c3208ed68cba4eda80e51612359ad Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/merge/Merger.java | 7 ++++++- .../jgit/merge/StrategySimpleTwoWayInCore.java | 11 ++--------- 2 files changed, 8 insertions(+), 10 deletions(-) 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 38a8b8eae..d95b11159 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java @@ -148,7 +148,12 @@ public boolean merge(final AnyObjectId[] tips) throws IOException { 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(); + } } /** 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 aa2321b4a..891abe058 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java @@ -154,15 +154,8 @@ else if (tw.isSubtree()) { return false; try { ObjectInserter odi = getObjectInserter(); - try { - resultTree = cache.writeTree(odi); - odi.flush(); - } finally { - // We don't know if our caller will release the - // inserter, so make sure we do it ourselves. - // - odi.release(); - } + resultTree = cache.writeTree(odi); + odi.flush(); return true; } catch (UnmergedPathException upe) { resultTree = null; From e01abbd543e28f3162b24ade5586a9644dedf4e8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Jun 2010 09:47:20 -0700 Subject: [PATCH 048/103] Release ObjectReader before the cached ObjectDatabase I don't want to play games with the order of release here, its probably safer to release the reader before the database, just in case the one depends on the other. Change-Id: I2394c7d2477eaf7a7e1556fc3393c59d3b31e764 Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/transport/IndexPack.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 ade9ffed8..be82a2f5d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java @@ -424,13 +424,6 @@ public void index(final ProgressMonitor progress) throws IOException { writeIdx(); } finally { - try { - InflaterCache.release(inflater); - } finally { - inflater = null; - objectDatabase.close(); - } - try { if (readCurs != null) readCurs.release(); @@ -438,6 +431,13 @@ public void index(final ProgressMonitor progress) throws IOException { readCurs = null; } + try { + InflaterCache.release(inflater); + } finally { + inflater = null; + objectDatabase.close(); + } + progress.endTask(); if (packOut != null) packOut.close(); From b5aa52e98a8e1ee9b8530fc2c37a04df224aa0c1 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Jun 2010 10:16:27 -0700 Subject: [PATCH 049/103] Ensure PackWriter releases its ObjectReader Change-Id: I3f8af29066cc5a2132dc4a75c9654d97800f2f18 Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/storage/pack/PackWriter.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) 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 index 031ea2b20..a716845b4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -610,19 +610,22 @@ private List sortByName() { * stream. */ public void writePack(OutputStream packStream) throws IOException { - if ((reuseDeltas || reuseObjects) && reuseSupport != null) - searchForReuse(); + try { + if ((reuseDeltas || reuseObjects) && reuseSupport != null) + searchForReuse(); - out = new PackOutputStream(packStream, isDeltaBaseAsOffset()); + out = new PackOutputStream(packStream, isDeltaBaseAsOffset()); - writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber()); - out.writeFileHeader(PACK_VERSION_GENERATED, getObjectsNumber()); - writeObjects(); - writeChecksum(); - - out = null; - reader.release(); - writeMonitor.endTask(); + int cnt = getObjectsNumber(); + writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, cnt); + out.writeFileHeader(PACK_VERSION_GENERATED, cnt); + writeObjects(); + writeChecksum(); + writeMonitor.endTask(); + } finally { + out = null; + reader.release(); + } } private void searchForReuse() throws IOException { From a45728d7a4fe1b83953ba38ec980caa6a3f1d3c4 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Jun 2010 10:24:12 -0700 Subject: [PATCH 050/103] Ensure ObjectReader used by PackWriter is released The ObjectReader API demands that we release the reader when we are done with it. PackWriter contains a reader, which it uses for the entire packing session. Expose the release of the reader through a release method on the writer. This still doesn't address the RevWalk and TreeWalk users, who don't correctly release their reader. But its a small step in the right direction. Change-Id: I5cb0b5c1b432434a799fceb21b86479e09b84a0a Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/junit/TestRepository.java | 49 ++++++++------- .../storage/file/ConcurrentRepackTest.java | 2 + .../jgit/storage/file/PackWriterTest.java | 2 + .../eclipse/jgit/storage/pack/PackWriter.java | 30 +++++----- .../transport/BasePackPushConnection.java | 37 +++++++----- .../eclipse/jgit/transport/BundleWriter.java | 60 ++++++++++--------- .../eclipse/jgit/transport/UploadPack.java | 36 ++++++----- .../jgit/transport/WalkPushConnection.java | 4 +- 8 files changed, 123 insertions(+), 97 deletions(-) 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 18275ec71..5b0e74cac 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 @@ -600,33 +600,38 @@ private static void assertHash(RevObject id, byte[] bin) { public void packAndPrune() throws Exception { if (db.getObjectDatabase() instanceof ObjectDirectory) { ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase(); + + final File pack, idx; PackWriter pw = new PackWriter(db, NullProgressMonitor.INSTANCE); - - Set all = new HashSet(); - for (Ref r : db.getAllRefs().values()) - all.add(r.getObjectId()); - pw.preparePack(all, Collections. emptySet()); - - final ObjectId name = pw.computeName(); - OutputStream out; - - final File pack = nameFor(odb, name, ".pack"); - out = new BufferedOutputStream(new FileOutputStream(pack)); try { - pw.writePack(out); - } finally { - out.close(); - } - pack.setReadOnly(); + Set all = new HashSet(); + for (Ref r : db.getAllRefs().values()) + all.add(r.getObjectId()); + pw.preparePack(all, Collections. emptySet()); - final File idx = nameFor(odb, name, ".idx"); - out = new BufferedOutputStream(new FileOutputStream(idx)); - try { - pw.writeIndex(out); + final ObjectId name = pw.computeName(); + OutputStream out; + + pack = nameFor(odb, name, ".pack"); + out = new BufferedOutputStream(new FileOutputStream(pack)); + try { + pw.writePack(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 { - out.close(); + pw.release(); } - idx.setReadOnly(); odb.openPack(pack, idx); updateServerInfo(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java index 96b117881..d85903cac 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java @@ -138,6 +138,7 @@ public void testObjectMovedWithinPack() 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. @@ -208,6 +209,7 @@ private File[] pack(final Repository src, final RevObject... list) final File idxFile = fullPackFileName(name, ".idx"); final File[] files = new File[] { packFile, idxFile }; write(files, pw); + pw.release(); return files; } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java index b12359cf6..19eec5fab 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java @@ -484,6 +484,7 @@ private void createVerifyOpenPack(final Collection interestings, writer.setIgnoreMissingUninteresting(ignoreMissingUninteresting); writer.preparePack(interestings, uninterestings); writer.writePack(os); + writer.release(); verifyOpenPack(thin); } @@ -491,6 +492,7 @@ private void createVerifyOpenPack(final Iterator objectSource) throws MissingObjectException, IOException { writer.preparePack(objectSource); writer.writePack(os); + writer.release(); verifyOpenPack(false); } 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 index a716845b4..cf6e38265 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -610,22 +610,24 @@ private List sortByName() { * stream. */ public void writePack(OutputStream packStream) throws IOException { - try { - if ((reuseDeltas || reuseObjects) && reuseSupport != null) - searchForReuse(); + if ((reuseDeltas || reuseObjects) && reuseSupport != null) + searchForReuse(); - out = new PackOutputStream(packStream, isDeltaBaseAsOffset()); + out = new PackOutputStream(packStream, isDeltaBaseAsOffset()); - int cnt = getObjectsNumber(); - writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, cnt); - out.writeFileHeader(PACK_VERSION_GENERATED, cnt); - writeObjects(); - writeChecksum(); - writeMonitor.endTask(); - } finally { - out = null; - reader.release(); - } + writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber()); + out.writeFileHeader(PACK_VERSION_GENERATED, getObjectsNumber()); + writeObjects(); + writeChecksum(); + + out = null; + reader.release(); + writeMonitor.endTask(); + } + + /** Release all resources used by this writer. */ + public void release() { + reader.release(); } private void searchForReuse() throws IOException { 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 72460c91c..d1f7bfc04 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.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; import org.eclipse.jgit.JGitText; @@ -226,25 +227,29 @@ private String enableCapabilities(final ProgressMonitor monitor) { private void writePack(final Map refUpdates, final ProgressMonitor monitor) throws IOException { + List remoteObjects = new ArrayList(getRefs().size()); + List newObjects = new ArrayList(refUpdates.size()); + + final long start; final PackWriter writer = new PackWriter(local, monitor); - final ArrayList remoteObjects = new ArrayList( - getRefs().size()); - final ArrayList newObjects = new ArrayList( - refUpdates.size()); + try { - 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()); + 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(newObjects, remoteObjects); + start = System.currentTimeMillis(); + writer.writePack(out); + } finally { + writer.release(); } - - writer.setThin(thinPack); - writer.setDeltaBaseAsOffset(capableOfsDelta); - writer.preparePack(newObjects, remoteObjects); - final long start = System.currentTimeMillis(); - writer.writePack(out); out.flush(); packTransferTime = System.currentTimeMillis() - start; } 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 c5e9baad5..71d58e1cf 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java @@ -165,37 +165,41 @@ public void assume(final RevCommit c) { * stream. */ public void writeBundle(OutputStream os) throws IOException { - final HashSet inc = new HashSet(); - final HashSet exc = new HashSet(); - inc.addAll(include.values()); - for (final RevCommit r : assume) - exc.add(r.getId()); - packWriter.setThin(exc.size() > 0); - packWriter.preparePack(inc, exc); + try { + final HashSet inc = new HashSet(); + final HashSet exc = new HashSet(); + inc.addAll(include.values()); + for (final RevCommit r : assume) + exc.add(r.getId()); + packWriter.setThin(exc.size() > 0); + packWriter.preparePack(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) { - w.write(' '); - w.write(a.getShortMessage()); + 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 e : include.entrySet()) { + e.getValue().copyTo(tmp, w); + w.write(' '); + w.write(e.getKey()); + w.write('\n'); } - w.write('\n'); - } - for (final Map.Entry 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); + w.write('\n'); + w.flush(); + packWriter.writePack(os); + } finally { + packWriter.release(); + } } } 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 17c37cf5b..dfb4168c9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -569,25 +569,29 @@ private void sendPack() throws IOException { 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; + try { + 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; + } + 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(packOut); + } finally { + pw.release(); } - pw.writePack(packOut); packOut.flush(); if (sideband) 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 1ab6081c0..a9f9d60d0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java @@ -209,8 +209,8 @@ private void sendpack(final List updates, String pathPack = null; String pathIdx = null; + final PackWriter pw = new PackWriter(local, monitor); try { - final PackWriter pw = new PackWriter(local, monitor); final List need = new ArrayList(); final List have = new ArrayList(); for (final RemoteRefUpdate r : updates) @@ -281,6 +281,8 @@ private void sendpack(final List updates, safeDelete(pathPack); throw new TransportException(uri, JGitText.get().cannotStoreObjects, err); + } finally { + pw.release(); } } From 9ba7bd4df452a1d005fbf770141076e901ad61d6 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Jun 2010 10:37:08 -0700 Subject: [PATCH 051/103] Throw IncorrectObjectTypeException on bad type hints If the type hint isn't OBJ_ANY and it doesn't match the actual type observed from the object store, define the reader to throw back an IncorrectObjectTypeException. This way the caller doesn't have to perform this check itself before it evaluates the object data, and we can simplify quite a few call sites. Change-Id: I9f0dfa033857f439c94245361fcae515bc0a6533 Signed-off-by: Shawn O. Pearce --- org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java | 6 +++++- .../src/org/eclipse/jgit/storage/file/WindowCursor.java | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java index 29d2eb6f3..e8961bdce 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java @@ -45,6 +45,7 @@ import java.io.IOException; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.storage.pack.ObjectReuseAsIs; @@ -103,10 +104,13 @@ public ObjectLoader openObject(AnyObjectId objectId) * @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 */ public abstract ObjectLoader openObject(AnyObjectId objectId, int typeHint) - throws MissingObjectException, IOException; + throws MissingObjectException, IncorrectObjectTypeException, IOException; /** * Release any resources used by this reader. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java index d5c2fd79d..0272201e2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java @@ -48,6 +48,7 @@ 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; @@ -81,13 +82,16 @@ public boolean hasObject(AnyObjectId objectId) throws IOException { } public ObjectLoader openObject(AnyObjectId objectId, int typeHint) - throws MissingObjectException, IOException { + 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; } From 1ad2feb7b3d48d8bfedfdd03ee6ca4f599041476 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Jun 2010 10:42:43 -0700 Subject: [PATCH 052/103] Remove Repository.openObject(ObjectReader, AnyObjectId) Going through ObjectReader.openObject(AnyObjectId) is faster, but also produces cleaner application level code. The error checking is done inside of the openObject method, which means it can be removed from the application code. Change-Id: Ia927b448d128005e1640362281585023582b1a3a Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/iplog/IpLogGenerator.java | 9 ++------ .../src/org/eclipse/jgit/lib/Repository.java | 21 ------------------- .../org/eclipse/jgit/revwalk/RevObject.java | 9 +------- .../src/org/eclipse/jgit/revwalk/RevWalk.java | 4 +--- .../eclipse/jgit/storage/pack/PackWriter.java | 2 +- .../org/eclipse/jgit/transport/IndexPack.java | 8 ++++--- .../jgit/treewalk/CanonicalTreeParser.java | 15 ++----------- 7 files changed, 12 insertions(+), 56 deletions(-) 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 f40c3896a..0d98f2436 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.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.ObjectReader; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; @@ -418,10 +416,7 @@ private void scanProjectCommits(Project proj, RevCommit start) 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.openObject(idbuf, Constants.OBJ_BLOB).getCachedBytes(); } /** 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 65f75094e..bab349e6f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -238,27 +238,6 @@ public ObjectLoader openObject(final AnyObjectId id) } } - /** - * @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 - * @deprecated Use {code newObjectReader().open(id)}. - */ - @Deprecated - public ObjectLoader openObject(ObjectReader curs, AnyObjectId id) - throws IOException { - try { - return curs.openObject(id); - } catch (MissingObjectException notFound) { - return null; - } - } - /** * @param id * SHA'1 of a blob 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 5dde43b27..9fa79e15e 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.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 @@ void parseBody(final RevWalk walk) throws MissingObjectException, 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.curs.openObject(this, getType()).getCachedBytes(); } /** 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 122418296..2c849beb5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java @@ -720,9 +720,7 @@ public RevObject parseAny(final AnyObjectId id) 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 ObjectLoader ldr = curs.openObject(id); final byte[] data = ldr.getCachedBytes(); final int type = ldr.getType(); switch (type) { 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 index cf6e38265..08ea9625e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -730,7 +730,7 @@ private void redoSearchForReuse(final ObjectToPack otp) throws IOException, private void writeWholeObjectDeflate(final ObjectToPack otp) throws IOException { - final ObjectLoader loader = db.openObject(reader, otp); + final ObjectLoader loader = reader.openObject(otp, otp.getType()); final byte[] data = loader.getCachedBytes(); out.writeHeader(otp, data.length); deflater.reset(); 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 be82a2f5d..494c077e0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java @@ -593,8 +593,10 @@ private void fixThinPack(final ProgressMonitor progress) throws IOException { continue; if (needBaseObjectIds) baseObjectIds.add(baseId); - final ObjectLoader ldr = repo.openObject(readCurs, baseId); - if (ldr == null) { + final ObjectLoader ldr; + try { + ldr = readCurs.openObject(baseId); + } catch (MissingObjectException notFound) { missing.add(baseId); continue; } @@ -856,7 +858,7 @@ private void verifySafeObject(final AnyObjectId id, final int type, try { final ObjectLoader ldr = readCurs.openObject(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) { 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 b7f980cca..6ceb839bd 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,8 @@ 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.ObjectReader; +import org.eclipse.jgit.lib.Repository; /** Parses raw Git trees from the canonical semi-text/semi-binary format. */ public class CanonicalTreeParser extends AbstractTreeIterator { @@ -199,17 +198,7 @@ public CanonicalTreeParser next() { public void reset(final Repository repo, final AnyObjectId id, final ObjectReader curs) 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(curs.openObject(id, Constants.OBJ_TREE).getCachedBytes()); } @Override From f288c27e465a91e80b53c4100c0d9b2f2341a9aa Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Jun 2010 10:44:09 -0700 Subject: [PATCH 053/103] Pass the PackOutputStream down the call stack Rather than storing this in an instance member, pass it down the calling stack. Its cleaner, we don't have to poke the stream as a temporary field, and then unset it. Change-Id: I0fd323371bc12edb10f0493bf11885d7057aeb13 Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/storage/pack/PackWriter.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) 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 index 08ea9625e..1a636e84a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -183,8 +183,6 @@ public class PackWriter { private final Repository db; - private PackOutputStream out; - private final Deflater deflater; private ProgressMonitor initMonitor; @@ -613,14 +611,14 @@ public void writePack(OutputStream packStream) throws IOException { if ((reuseDeltas || reuseObjects) && reuseSupport != null) searchForReuse(); - out = new PackOutputStream(packStream, isDeltaBaseAsOffset()); + final PackOutputStream out = new PackOutputStream(packStream, + isDeltaBaseAsOffset()); writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber()); out.writeFileHeader(PACK_VERSION_GENERATED, getObjectsNumber()); - writeObjects(); - writeChecksum(); + writeObjects(out); + writeChecksum(out); - out = null; reader.release(); writeMonitor.endTask(); } @@ -644,25 +642,26 @@ private void searchForReuse() throws IOException { initMonitor.endTask(); } - private void writeObjects() throws IOException { + private void writeObjects(PackOutputStream out) throws IOException { for (List list : objectsLists) { for (ObjectToPack otp : list) { if (writeMonitor.isCancelled()) throw new IOException( JGitText.get().packingCancelledDuringObjectsWriting); if (!otp.isWritten()) - writeObject(otp); + writeObject(out, otp); } } } - private void writeObject(final ObjectToPack otp) throws IOException { + 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(otp); + writeBaseFirst(out, otp); out.resetCRC32(); otp.setOffset(out.length()); @@ -690,12 +689,13 @@ private void writeObject(final ObjectToPack otp) throws IOException { // If we reached here, reuse wasn't possible. // - writeWholeObjectDeflate(otp); + writeWholeObjectDeflate(out, otp); otp.setCRC(out.getCRC32()); writeMonitor.update(1); } - private void writeBaseFirst(final ObjectToPack otp) throws IOException { + private void writeBaseFirst(PackOutputStream out, final ObjectToPack otp) + throws IOException { ObjectToPack baseInPack = otp.getDeltaBase(); if (baseInPack != null) { if (!baseInPack.isWritten()) { @@ -708,7 +708,7 @@ private void writeBaseFirst(final ObjectToPack otp) throws IOException { redoSearchForReuse(otp); reuseDeltas = true; } else { - writeObject(baseInPack); + writeObject(out, baseInPack); } } } else if (!thin) { @@ -728,8 +728,8 @@ private void redoSearchForReuse(final ObjectToPack otp) throws IOException, reuseSupport.selectObjectRepresentation(this, otp); } - private void writeWholeObjectDeflate(final ObjectToPack otp) - throws IOException { + private void writeWholeObjectDeflate(PackOutputStream out, + final ObjectToPack otp) throws IOException { final ObjectLoader loader = reader.openObject(otp, otp.getType()); final byte[] data = loader.getCachedBytes(); out.writeHeader(otp, data.length); @@ -745,7 +745,7 @@ private void writeWholeObjectDeflate(final ObjectToPack otp) } while (!deflater.finished()); } - private void writeChecksum() throws IOException { + private void writeChecksum(PackOutputStream out) throws IOException { packcsum = out.getDigest(); out.write(packcsum); } From 6b62e53b607630b6c00411741972838ced552f4d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Jun 2010 11:16:50 -0700 Subject: [PATCH 054/103] Move PackWriter progress monitors onto the operations Rather than taking the ProgressMonitor objects in our constructor and carrying them around as instance fields, take them as arguments to the actual time consuming operations we need to run. Change-Id: I2b230d07e277de029b1061c807e67de5428cc1c4 Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/junit/TestRepository.java | 7 +- .../storage/file/ConcurrentRepackTest.java | 7 +- .../jgit/storage/file/PackWriterTest.java | 11 +- .../jgit/transport/BundleWriterTest.java | 4 +- .../jgit/storage/pack/PackOutputStream.java | 11 +- .../eclipse/jgit/storage/pack/PackWriter.java | 116 ++++++++---------- .../transport/BasePackPushConnection.java | 6 +- .../eclipse/jgit/transport/BundleWriter.java | 15 +-- .../eclipse/jgit/transport/UploadPack.java | 6 +- .../jgit/transport/WalkPushConnection.java | 6 +- 10 files changed, 94 insertions(+), 95 deletions(-) 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 5b0e74cac..daa959f11 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 @@ -600,14 +600,15 @@ private static void assertHash(RevObject id, byte[] bin) { public void packAndPrune() throws Exception { if (db.getObjectDatabase() instanceof ObjectDirectory) { ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase(); + NullProgressMonitor m = NullProgressMonitor.INSTANCE; final File pack, idx; - PackWriter pw = new PackWriter(db, NullProgressMonitor.INSTANCE); + PackWriter pw = new PackWriter(db); try { Set all = new HashSet(); for (Ref r : db.getAllRefs().values()) all.add(r.getObjectId()); - pw.preparePack(all, Collections. emptySet()); + pw.preparePack(m, all, Collections. emptySet()); final ObjectId name = pw.computeName(); OutputStream out; @@ -615,7 +616,7 @@ public void packAndPrune() throws Exception { pack = nameFor(odb, name, ".pack"); out = new BufferedOutputStream(new FileOutputStream(pack)); try { - pw.writePack(out); + pw.writePack(m, m, out); } finally { out.close(); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java index d85903cac..8e7df41a8 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java @@ -134,7 +134,7 @@ public void testObjectMovedWithinPack() // 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); @@ -199,7 +199,7 @@ private RevObject parse(final AnyObjectId id) 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); } @@ -216,11 +216,12 @@ private File[] pack(final Repository src, final RevObject... list) 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(); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java index 19eec5fab..9e663d7b4 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java @@ -59,6 +59,7 @@ import java.util.List; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.SampleDataRepositoryTestCase; import org.eclipse.jgit.lib.TextProgressMonitor; @@ -95,7 +96,7 @@ public void setUp() throws Exception { 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); } /** @@ -480,18 +481,20 @@ private void createVerifyOpenPack(final Collection interestings, final Collection 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 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/transport/BundleWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java index 2d6aa28d5..cc7056225 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 @@ private byte[] makeBundle(final String name, 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/src/org/eclipse/jgit/storage/pack/PackOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackOutputStream.java index bc20f89c7..a93ac0516 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackOutputStream.java @@ -50,10 +50,13 @@ 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; @@ -68,7 +71,9 @@ public final class PackOutputStream extends OutputStream { private byte[] copyBuffer; - PackOutputStream(final OutputStream out, final boolean ofsDelta) { + PackOutputStream(final ProgressMonitor writeMonitor, + final OutputStream out, final boolean ofsDelta) { + this.writeMonitor = writeMonitor; this.out = out; this.ofsDelta = ofsDelta; } @@ -168,6 +173,10 @@ public byte[] getCopyBuffer() { return copyBuffer; } + void endObject() { + writeMonitor.update(1); + } + /** @return total number of bytes written since stream start. */ long length() { return count; 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 index 1a636e84a..a0e711f27 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -96,8 +96,8 @@ * 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)}. + * {@link #preparePack(ProgressMonitor, Collection, Collection)}, and finally + * producing the stream with {@link #writePack(ProgressMonitor, ProgressMonitor, OutputStream)}. *

    *

    * Class provide set of configurable options and {@link ProgressMonitor} @@ -116,7 +116,7 @@ public class PackWriter { * Title of {@link ProgressMonitor} task used during counting objects to * pack. * - * @see #preparePack(Collection, Collection) + * @see #preparePack(ProgressMonitor, Collection, Collection) */ public static final String COUNTING_OBJECTS_PROGRESS = JGitText.get().countingObjects; @@ -124,7 +124,7 @@ public class PackWriter { * Title of {@link ProgressMonitor} task used during searching for objects * reuse or delta reuse. * - * @see #writePack(OutputStream) + * @see #writePack(ProgressMonitor, ProgressMonitor, OutputStream) */ public static final String SEARCHING_REUSE_PROGRESS = JGitText.get().compressingObjects; @@ -132,7 +132,7 @@ public class PackWriter { * Title of {@link ProgressMonitor} task used during writing out pack * (objects) * - * @see #writePack(OutputStream) + * @see #writePack(ProgressMonitor, ProgressMonitor, OutputStream) */ public static final String WRITING_OBJECTS_PROGRESS = JGitText.get().writingObjects; @@ -185,10 +185,6 @@ public class PackWriter { private final Deflater deflater; - private ProgressMonitor initMonitor; - - private ProgressMonitor writeMonitor; - private final ObjectReader reader; /** {@link #reader} recast to the reuse interface, if it supports it. */ @@ -216,38 +212,12 @@ public class PackWriter { * Create writer for specified repository. *

    * Objects for packing are specified in {@link #preparePack(Iterator)} or - * {@link #preparePack(Collection, Collection)}. + * {@link #preparePack(ProgressMonitor, 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. - *

    - * 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) { + public PackWriter(final Repository repo) { this.db = repo; reader = db.newObjectReader(); @@ -256,9 +226,6 @@ public PackWriter(final Repository repo, final ProgressMonitor imonitor, else reuseSupport = null; - initMonitor = imonitor == null ? NullProgressMonitor.INSTANCE : imonitor; - writeMonitor = wmonitor == null ? NullProgressMonitor.INSTANCE : wmonitor; - final CoreConfig coreConfig = db.getConfig().get(CoreConfig.KEY); this.deflater = new Deflater(coreConfig.getCompression()); outputVersion = coreConfig.getPackIndexVersion(); @@ -283,7 +250,7 @@ public boolean isReuseDeltas() { * 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(ProgressMonitor, Collection, Collection)} and * {@link #preparePack(Iterator)}) where base object must exist on other * side machine. *

    @@ -411,7 +378,7 @@ public void setThin(final boolean 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 + * 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. */ @@ -504,6 +471,8 @@ public void preparePack(final Iterator objectsSource) * recency, path and delta-base first. *

    * + * @param countingMonitor + * progress during object enumeration. * @param interestingObjects * collection of objects to be marked as interesting (start * points of graph traversal). @@ -513,13 +482,15 @@ public void preparePack(final Iterator objectsSource) * @throws IOException * when some I/O problem occur during reading objects. */ - public void preparePack( + public void preparePack(ProgressMonitor countingMonitor, final Collection interestingObjects, final Collection uninterestingObjects) throws IOException { + if (countingMonitor == null) + countingMonitor = NullProgressMonitor.INSTANCE; ObjectWalk walker = setUpWalker(interestingObjects, uninterestingObjects); - findObjectsToPack(walker); + findObjectsToPack(countingMonitor, walker); } /** @@ -553,7 +524,7 @@ public ObjectId computeName() { * Create an index file to match the pack file just written. *

    * This method can only be invoked after {@link #preparePack(Iterator)} or - * {@link #preparePack(Collection, Collection)} has been + * {@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. * @@ -599,6 +570,10 @@ private List sortByName() { * validated against existing checksum. *

    * + * @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. @@ -607,16 +582,23 @@ private List sortByName() { * the pack, or writing compressed object data to the output * stream. */ - public void writePack(OutputStream packStream) throws IOException { - if ((reuseDeltas || reuseObjects) && reuseSupport != null) - searchForReuse(); + public void writePack(ProgressMonitor compressMonitor, + ProgressMonitor writeMonitor, OutputStream packStream) + throws IOException { + if (compressMonitor == null) + compressMonitor = NullProgressMonitor.INSTANCE; + if (writeMonitor == null) + writeMonitor = NullProgressMonitor.INSTANCE; - final PackOutputStream out = new PackOutputStream(packStream, - isDeltaBaseAsOffset()); + if ((reuseDeltas || reuseObjects) && reuseSupport != null) + searchForReuse(compressMonitor); + + final PackOutputStream out = new PackOutputStream(writeMonitor, + packStream, isDeltaBaseAsOffset()); writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber()); out.writeFileHeader(PACK_VERSION_GENERATED, getObjectsNumber()); - writeObjects(out); + writeObjects(writeMonitor, out); writeChecksum(out); reader.release(); @@ -628,21 +610,23 @@ public void release() { reader.release(); } - private void searchForReuse() throws IOException { - initMonitor.beginTask(SEARCHING_REUSE_PROGRESS, getObjectsNumber()); + private void searchForReuse(ProgressMonitor compressMonitor) + throws IOException { + compressMonitor.beginTask(SEARCHING_REUSE_PROGRESS, getObjectsNumber()); for (List list : objectsLists) { for (ObjectToPack otp : list) { - if (initMonitor.isCancelled()) + if (compressMonitor.isCancelled()) throw new IOException( JGitText.get().packingCancelledDuringObjectsWriting); reuseSupport.selectObjectRepresentation(this, otp); - initMonitor.update(1); + compressMonitor.update(1); } } - initMonitor.endTask(); + compressMonitor.endTask(); } - private void writeObjects(PackOutputStream out) throws IOException { + private void writeObjects(ProgressMonitor writeMonitor, PackOutputStream out) + throws IOException { for (List list : objectsLists) { for (ObjectToPack otp : list) { if (writeMonitor.isCancelled()) @@ -669,8 +653,8 @@ private void writeObject(PackOutputStream out, final ObjectToPack otp) while (otp.isReuseAsIs()) { try { reuseSupport.copyObjectAsIs(out, otp); + out.endObject(); otp.setCRC(out.getCRC32()); - writeMonitor.update(1); return; } catch (StoredObjectRepresentationNotAvailableException gone) { if (otp.getOffset() == out.length()) { @@ -690,8 +674,8 @@ private void writeObject(PackOutputStream out, final ObjectToPack otp) // If we reached here, reuse wasn't possible. // writeWholeObjectDeflate(out, otp); + out.endObject(); otp.setCRC(out.getCRC32()); - writeMonitor.update(1); } private void writeBaseFirst(PackOutputStream out, final ObjectToPack otp) @@ -781,22 +765,22 @@ private ObjectWalk setUpWalker( return walker; } - private void findObjectsToPack(final ObjectWalk walker) - throws MissingObjectException, IncorrectObjectTypeException, - IOException { - initMonitor.beginTask(COUNTING_OBJECTS_PROGRESS, + 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); - initMonitor.update(1); + countingMonitor.update(1); } while ((o = walker.nextObject()) != null) { addObject(o); - initMonitor.update(1); + countingMonitor.update(1); } - initMonitor.endTask(); + countingMonitor.endTask(); } /** 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 d1f7bfc04..297105d46 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java @@ -231,7 +231,7 @@ private void writePack(final Map refUpdates, List newObjects = new ArrayList(refUpdates.size()); final long start; - final PackWriter writer = new PackWriter(local, monitor); + final PackWriter writer = new PackWriter(local); try { for (final Ref r : getRefs()) @@ -244,9 +244,9 @@ private void writePack(final Map refUpdates, writer.setThin(thinPack); writer.setDeltaBaseAsOffset(capableOfsDelta); - writer.preparePack(newObjects, remoteObjects); + writer.preparePack(monitor, newObjects, remoteObjects); start = System.currentTimeMillis(); - writer.writePack(out); + writer.writePack(monitor, monitor, out); } finally { writer.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 71d58e1cf..79fa58c36 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java @@ -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(); assume = new HashSet(); } @@ -155,6 +153,8 @@ public void assume(final RevCommit c) { *

    * 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,7 +164,8 @@ public void assume(final RevCommit c) { * the bundle, or writing compressed object data to the output * stream. */ - public void writeBundle(OutputStream os) throws IOException { + public void writeBundle(ProgressMonitor monitor, OutputStream os) + throws IOException { try { final HashSet inc = new HashSet(); final HashSet exc = new HashSet(); @@ -172,7 +173,7 @@ public void writeBundle(OutputStream os) throws IOException { for (final RevCommit r : assume) exc.add(r.getId()); packWriter.setThin(exc.size() > 0); - packWriter.preparePack(inc, exc); + packWriter.preparePack(monitor, inc, exc); final Writer w = new OutputStreamWriter(os, Constants.CHARSET); w.write(TransportBundle.V2_BUNDLE_SIGNATURE); @@ -197,7 +198,7 @@ public void writeBundle(OutputStream os) throws IOException { w.write('\n'); w.flush(); - packWriter.writePack(os); + packWriter.writePack(monitor, monitor, os); } finally { packWriter.release(); } 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 dfb4168c9..9f215d751 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -568,11 +568,11 @@ private void sendPack() throws IOException { } final PackWriter pw; - pw = new PackWriter(db, pm, NullProgressMonitor.INSTANCE); + pw = new PackWriter(db); try { pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA)); pw.setThin(thin); - pw.preparePack(wantAll, commonBase); + pw.preparePack(pm, wantAll, commonBase); if (options.contains(OPTION_INCLUDE_TAG)) { for (final Ref r : refs.values()) { final RevObject o; @@ -588,7 +588,7 @@ private void sendPack() throws IOException { pw.addObject(t); } } - pw.writePack(packOut); + pw.writePack(pm, NullProgressMonitor.INSTANCE, packOut); } finally { pw.release(); } 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 a9f9d60d0..bbc918f25 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java @@ -209,7 +209,7 @@ private void sendpack(final List updates, String pathPack = null; String pathIdx = null; - final PackWriter pw = new PackWriter(local, monitor); + final PackWriter pw = new PackWriter(local); try { final List need = new ArrayList(); final List have = new ArrayList(); @@ -220,7 +220,7 @@ private void sendpack(final List updates, 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 @@ private void sendpack(final List updates, OutputStream os = dest.writeFile(pathPack, monitor, wt + "..pack"); try { os = new BufferedOutputStream(os); - pw.writePack(os); + pw.writePack(monitor, monitor, os); } finally { os.close(); } From acb7be2c5adb270d21182d389ce93f3bca38cd29 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Jun 2010 11:54:58 -0700 Subject: [PATCH 055/103] Refactor Repository.openObject to be Repository.open We drop the "Object" suffix, because its pretty clear here that we want to open an object, given that we pass in AnyObjectId as the main parameter. We also fix the calling convention to throw a MissingObjectException or IncorrectObjectTypeException, so that callers don't have to do this error checking themselves. Change-Id: I72c43353cea8372278b032f5086d52082c1eee39 Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/junit/TestRepository.java | 4 +- .../src/org/eclipse/jgit/pgm/Diff.java | 8 +- .../src/org/eclipse/jgit/pgm/Tag.java | 9 +- .../storage/file/ConcurrentRepackTest.java | 4 +- .../jgit/storage/file/T0004_PackReader.java | 2 +- .../jgit/storage/file/WindowCacheGetTest.java | 2 +- .../transport/ReceivePackRefFilterTest.java | 10 +- .../org/eclipse/jgit/lib/BlobBasedConfig.java | 13 +- .../org/eclipse/jgit/lib/FileTreeEntry.java | 2 +- .../src/org/eclipse/jgit/lib/GitIndex.java | 4 +- .../org/eclipse/jgit/lib/ObjectDatabase.java | 30 ++++- .../org/eclipse/jgit/lib/ObjectReader.java | 7 +- .../src/org/eclipse/jgit/lib/Repository.java | 125 +++++++++--------- .../src/org/eclipse/jgit/lib/Tree.java | 6 +- 14 files changed, 124 insertions(+), 102 deletions(-) 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 daa959f11..a179773d1 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 @@ -563,7 +563,7 @@ public void fsck(RevObject... tips) throws MissingObjectException, 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); } @@ -573,7 +573,7 @@ public void fsck(RevObject... tips) throws MissingObjectException, 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); } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java index a5db45368..e1f78244b 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java @@ -50,12 +50,10 @@ import java.util.ArrayList; import java.util.List; -import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.Option; - import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.diff.MyersDiff; import org.eclipse.jgit.diff.RawText; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.pgm.opt.PathTreeFilterHandler; @@ -63,6 +61,8 @@ import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.AndTreeFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; @Command(common = true, usage = "usage_ShowDiffs") class Diff extends TextBuiltin { @@ -125,7 +125,7 @@ protected void outputDiff(PrintStream out, String path, private RawText getRawText(ObjectId id) throws IOException { if (id.equals(ObjectId.zeroId())) return new RawText(new byte[] { }); - return new RawText(db.openBlob(id).getCachedBytes()); + return new RawText(db.open(id, Constants.OBJ_BLOB).getCachedBytes()); } } 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 63d26eaca..c798950a2 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 @@ 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 @@ protected void run() throws Exception { , 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.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java index 8e7df41a8..c8f2aad75 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/ConcurrentRepackTest.java @@ -158,7 +158,7 @@ public void testObjectMovedToNewPack2() 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"); @@ -173,7 +173,7 @@ public void testObjectMovedToNewPack2() // 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); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java index 70016f9ea..b8bcca2e1 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java @@ -79,7 +79,7 @@ public void test004_lookupDeltifiedObject() throws IOException { 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()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java index 89b91558b..177a1d5cf 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java @@ -126,7 +126,7 @@ private void checkLimits(final WindowCacheConfig cfg) { 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()); 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 cb1ce6f38..0bcdbcea4 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 @@ -299,8 +299,8 @@ public void testUsingHiddenCommonBlobFails() throws Exception { // final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64); 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); @@ -341,8 +341,8 @@ public void testUsingUnknownBlobFails() throws Exception { // final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64); 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); @@ -381,7 +381,7 @@ public void testUsingUnknownTreeFails() throws Exception { // final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64); packHeader(pack, 1); - copy(pack, src.openObject(N)); + copy(pack, src.open(N)); digest(pack); final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(256); 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 b05942b02..b56966ff4 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 BlobBasedConfig(Config base, final byte[] blob) 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 BlobBasedConfig(Config base, final Commit commit, final String path) 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/FileTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java index 3da91dd2a..5bb3f62da 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java @@ -93,7 +93,7 @@ public void setExecutable(final boolean execute) { * @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 929cd2d2e..0495d38a1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java @@ -433,7 +433,7 @@ public class Entry { 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; @@ -873,7 +873,7 @@ public void checkout(File wd) throws IOException { * @throws IOException */ public void checkoutEntry(File wd, Entry e) throws IOException { - ObjectLoader ol = db.openBlob(e.sha1); + ObjectLoader ol = db.open(e.sha1, Constants.OBJ_BLOB); byte[] bytes = ol.getBytes(); File file = new File(wd, e.getName()); file.delete(); 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 c2d2beda9..a095663bf 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java @@ -45,6 +45,7 @@ import java.io.IOException; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; /** @@ -143,9 +144,36 @@ public boolean hasObject(final AnyObjectId objectId) throws IOException { */ public ObjectLoader openObject(final AnyObjectId objectId) throws IOException { + return openObject(objectId, ObjectReader.OBJ_ANY); + } + + /** + * Open an object from this database. + *

    + * This is a one-shot call interface which may be faster than allocating a + * {@link #newReader()} to perform the lookup. + * + * @param objectId + * 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 openObject(AnyObjectId objectId, int typeHint) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { final ObjectReader or = newReader(); try { - return or.openObject(objectId); + return or.openObject(objectId, typeHint); } finally { or.release(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java index e8961bdce..001b18c4c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java @@ -86,6 +86,7 @@ public boolean hasObject(AnyObjectId objectId) throws IOException { * @throws MissingObjectException * the object does not exist. * @throws IOException + * the object store cannot be accessed. */ public ObjectLoader openObject(AnyObjectId objectId) throws MissingObjectException, IOException { @@ -97,7 +98,7 @@ public ObjectLoader openObject(AnyObjectId objectId) * * @param objectId * identity of the object to open. - *@param typeHint + * @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. @@ -108,9 +109,11 @@ public ObjectLoader openObject(AnyObjectId objectId) * 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 openObject(AnyObjectId objectId, int typeHint) - throws MissingObjectException, IncorrectObjectTypeException, IOException; + throws MissingObjectException, IncorrectObjectTypeException, + IOException; /** * Release any resources used by this reader. 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 bab349e6f..a2d9f61e0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -221,41 +221,49 @@ public boolean hasObject(AnyObjectId objectId) { } /** - * @param id - * SHA-1 of an object. + * Open an object from this repository. + *

    + * This is a one-shot call interface which may be faster than allocating a + * {@link #newObjectReader()} to perform the lookup. * - * @return a {@link ObjectLoader} for accessing the data of the named - * object, or null if the object does not exist. + * @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 openObject(final AnyObjectId id) - throws IOException { - try { - return getObjectDatabase().openObject(id); - } catch (MissingObjectException notFound) { - // Legacy API, return null - return null; - } + public ObjectLoader open(final AnyObjectId objectId) + throws MissingObjectException, IOException { + return getObjectDatabase().openObject(objectId); } /** - * @param id - * SHA'1 of a blob - * @return an {@link ObjectLoader} for accessing the data of a named blob + * Open an object from this repository. + *

    + * This is a one-shot call interface which may be faster than allocating a + * {@link #newObjectReader()} to perform the lookup. + * + * @param objectId + * 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().openObject(objectId, typeHint); } /** @@ -284,19 +292,22 @@ public Commit mapCommit(final String revstr) throws IOException { * @throws IOException */ 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; @@ -314,18 +325,13 @@ public Object mapObject(final ObjectId id, final String refName) throws IOExcept * @throws IOException for I/O error or unexpected object type. */ 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()); } /** @@ -351,10 +357,13 @@ public Tree mapTree(final String revstr) throws IOException { * @throws IOException for I/O error or unexpected object type. */ 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); @@ -367,16 +376,6 @@ public Tree mapTree(final ObjectId id) throws IOException { } } - 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. * @@ -397,12 +396,14 @@ public Tag mapTag(String revstr) throws IOException { * @throws IOException for I/O error or unexpected object type. */ 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); } 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 2aa3098f1..f3b540991 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java @@ -530,10 +530,8 @@ public void accept(final TreeVisitor tv, final int flags) 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()); } } From aa4b06e08781f9f4e33259d92eb919b3c6da22ef Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Jun 2010 11:57:41 -0700 Subject: [PATCH 056/103] Rename openObject, hasObject to just open, has Similar to what we did on Repository, the openObject method already implied we wanted to open an object, given its main argument was of type AnyObjectId. Simplify the method name to just the action, has or open. Change-Id: If055e5e0d8de0e2424c18a773f6d2bc2f66054f4 Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/iplog/IpLogGenerator.java | 2 +- .../src/org/eclipse/jgit/lib/ObjectDatabase.java | 12 ++++++------ .../src/org/eclipse/jgit/lib/ObjectReader.java | 10 +++++----- .../src/org/eclipse/jgit/lib/Repository.java | 6 +++--- .../src/org/eclipse/jgit/revwalk/RevObject.java | 2 +- .../src/org/eclipse/jgit/revwalk/RevWalk.java | 2 +- .../jgit/storage/file/CachedObjectDirectory.java | 2 +- .../jgit/storage/file/FileObjectDatabase.java | 2 +- .../jgit/storage/file/ObjectDirectoryInserter.java | 4 ++-- .../org/eclipse/jgit/storage/file/WindowCursor.java | 6 +++--- .../org/eclipse/jgit/storage/pack/PackWriter.java | 2 +- .../src/org/eclipse/jgit/transport/IndexPack.java | 4 ++-- .../eclipse/jgit/treewalk/CanonicalTreeParser.java | 2 +- 13 files changed, 28 insertions(+), 28 deletions(-) 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 0d98f2436..fa2010d1f 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 @@ -416,7 +416,7 @@ private void scanProjectCommits(Project proj, RevCommit start) private byte[] openBlob(int side) throws IOException { tw.getObjectId(idbuf, side); - return curs.openObject(idbuf, Constants.OBJ_BLOB).getCachedBytes(); + return curs.open(idbuf, Constants.OBJ_BLOB).getCachedBytes(); } /** 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 a095663bf..15d118c0e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java @@ -119,10 +119,10 @@ public void create() throws IOException { * @throws IOException * the object store cannot be accessed. */ - public boolean hasObject(final AnyObjectId objectId) throws IOException { + public boolean has(final AnyObjectId objectId) throws IOException { final ObjectReader or = newReader(); try { - return or.hasObject(objectId); + return or.has(objectId); } finally { or.release(); } @@ -142,9 +142,9 @@ public boolean hasObject(final AnyObjectId objectId) throws IOException { * @throws IOException * the object store cannot be accessed. */ - public ObjectLoader openObject(final AnyObjectId objectId) + public ObjectLoader open(final AnyObjectId objectId) throws IOException { - return openObject(objectId, ObjectReader.OBJ_ANY); + return open(objectId, ObjectReader.OBJ_ANY); } /** @@ -168,12 +168,12 @@ public ObjectLoader openObject(final AnyObjectId objectId) * @throws IOException * the object store cannot be accessed. */ - public ObjectLoader openObject(AnyObjectId objectId, int typeHint) + public ObjectLoader open(AnyObjectId objectId, int typeHint) throws MissingObjectException, IncorrectObjectTypeException, IOException { final ObjectReader or = newReader(); try { - return or.openObject(objectId, typeHint); + return or.open(objectId, typeHint); } finally { or.release(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java index 001b18c4c..e9afc5043 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java @@ -68,9 +68,9 @@ public abstract class ObjectReader { * @throws IOException * the object store cannot be accessed. */ - public boolean hasObject(AnyObjectId objectId) throws IOException { + public boolean has(AnyObjectId objectId) throws IOException { try { - openObject(objectId); + open(objectId); return true; } catch (MissingObjectException notFound) { return false; @@ -88,9 +88,9 @@ public boolean hasObject(AnyObjectId objectId) throws IOException { * @throws IOException * the object store cannot be accessed. */ - public ObjectLoader openObject(AnyObjectId objectId) + public ObjectLoader open(AnyObjectId objectId) throws MissingObjectException, IOException { - return openObject(objectId, OBJ_ANY); + return open(objectId, OBJ_ANY); } /** @@ -111,7 +111,7 @@ public ObjectLoader openObject(AnyObjectId objectId) * @throws IOException * the object store cannot be accessed. */ - public abstract ObjectLoader openObject(AnyObjectId objectId, int typeHint) + public abstract ObjectLoader open(AnyObjectId objectId, int typeHint) throws MissingObjectException, IncorrectObjectTypeException, IOException; 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 a2d9f61e0..aeb160e52 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -213,7 +213,7 @@ public FS getFS() { */ public boolean hasObject(AnyObjectId objectId) { try { - return getObjectDatabase().hasObject(objectId); + return getObjectDatabase().has(objectId); } catch (IOException e) { // Legacy API, assume error means "no" return false; @@ -236,7 +236,7 @@ public boolean hasObject(AnyObjectId objectId) { */ public ObjectLoader open(final AnyObjectId objectId) throws MissingObjectException, IOException { - return getObjectDatabase().openObject(objectId); + return getObjectDatabase().open(objectId); } /** @@ -263,7 +263,7 @@ public ObjectLoader open(final AnyObjectId objectId) public ObjectLoader open(AnyObjectId objectId, int typeHint) throws MissingObjectException, IncorrectObjectTypeException, IOException { - return getObjectDatabase().openObject(objectId, typeHint); + return getObjectDatabase().open(objectId, typeHint); } /** 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 9fa79e15e..14f4836d7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java @@ -77,7 +77,7 @@ void parseBody(final RevWalk walk) throws MissingObjectException, final byte[] loadCanonical(final RevWalk walk) throws IOException, MissingObjectException, IncorrectObjectTypeException, CorruptObjectException { - return walk.curs.openObject(this, getType()).getCachedBytes(); + return walk.curs.open(this, getType()).getCachedBytes(); } /** 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 2c849beb5..60c8024b3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java @@ -720,7 +720,7 @@ public RevObject parseAny(final AnyObjectId id) throws MissingObjectException, IOException { RevObject r = objects.get(id); if (r == null) { - final ObjectLoader ldr = curs.openObject(id); + final ObjectLoader ldr = curs.open(id); final byte[] data = ldr.getCachedBytes(); final int type = ldr.getType(); switch (type) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java index 194561238..df62342d8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java @@ -149,7 +149,7 @@ boolean tryAgain1() { } @Override - public boolean hasObject(final AnyObjectId objectId) { + public boolean has(final AnyObjectId objectId) { return hasObjectImpl1(objectId); } 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 index f0609897a..021823488 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java @@ -69,7 +69,7 @@ public ObjectReader newReader() { * @return true if the specified object is stored in this database, or any * of the alternate databases. */ - public boolean hasObject(final AnyObjectId objectId) { + public boolean has(final AnyObjectId objectId) { return hasObjectImpl1(objectId) || hasObjectImpl2(objectId.name()); } 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 index e6ed54022..501667989 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectoryInserter.java @@ -83,7 +83,7 @@ public ObjectId insert(final int type, long len, final InputStream is) final MessageDigest md = digest(); final File tmp = toTemp(md, type, len, is); final ObjectId id = ObjectId.fromRaw(md.digest()); - if (db.hasObject(id)) { + if (db.has(id)) { // Object is already in the repository, remove temporary file. // tmp.delete(); @@ -102,7 +102,7 @@ public ObjectId insert(final int type, long len, final InputStream is) if (tmp.renameTo(dst)) return id; - if (db.hasObject(id)) { + if (db.has(id)) { tmp.delete(); return id; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java index 0272201e2..544498573 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java @@ -77,11 +77,11 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs { this.db = db; } - public boolean hasObject(AnyObjectId objectId) throws IOException { - return db.hasObject(objectId); + public boolean has(AnyObjectId objectId) throws IOException { + return db.has(objectId); } - public ObjectLoader openObject(AnyObjectId objectId, int typeHint) + public ObjectLoader open(AnyObjectId objectId, int typeHint) throws MissingObjectException, IncorrectObjectTypeException, IOException { final ObjectLoader ldr = db.openObject(this, objectId); 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 index a0e711f27..c7e3c0c9b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -714,7 +714,7 @@ private void redoSearchForReuse(final ObjectToPack otp) throws IOException, private void writeWholeObjectDeflate(PackOutputStream out, final ObjectToPack otp) throws IOException { - final ObjectLoader loader = reader.openObject(otp, otp.getType()); + final ObjectLoader loader = reader.open(otp, otp.getType()); final byte[] data = loader.getCachedBytes(); out.writeHeader(otp, data.length); deflater.reset(); 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 494c077e0..0699fc47a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java @@ -595,7 +595,7 @@ private void fixThinPack(final ProgressMonitor progress) throws IOException { baseObjectIds.add(baseId); final ObjectLoader ldr; try { - ldr = readCurs.openObject(baseId); + ldr = readCurs.open(baseId); } catch (MissingObjectException notFound) { missing.add(baseId); continue; @@ -856,7 +856,7 @@ private void verifySafeObject(final AnyObjectId id, final int type, } try { - final ObjectLoader ldr = readCurs.openObject(id, type); + final ObjectLoader ldr = readCurs.open(id, type); final byte[] existingData = ldr.getCachedBytes(); if (!Arrays.equals(data, existingData)) { throw new IOException(MessageFormat.format(JGitText.get().collisionOn, id.name())); 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 6ceb839bd..fc088d776 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java @@ -198,7 +198,7 @@ public CanonicalTreeParser next() { public void reset(final Repository repo, final AnyObjectId id, final ObjectReader curs) throws IncorrectObjectTypeException, IOException { - reset(curs.openObject(id, Constants.OBJ_TREE).getCachedBytes()); + reset(curs.open(id, Constants.OBJ_TREE).getCachedBytes()); } @Override From 242b4026d92c06c45cc10df0c5a8bf5634d85e70 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Jun 2010 12:46:18 -0700 Subject: [PATCH 057/103] Remove volatile keyword from RepositoryEvent We don't need this field to be volatile. Events are delivered by the same thread that created the RepositoryEvent object, and thus any cross-thread operations would need to be handled by some other type of synchronization in the listener, and that would protect both the repository field and any other per-event data. Change-Id: Iefe345959e1a2d4669709dbf82962bcc1b8913e3 Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/events/RepositoryEvent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java index fa1b1bd41..ba1c81d5d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java @@ -53,7 +53,7 @@ * type of listener this event dispatches to. */ public abstract class RepositoryEvent { - private volatile Repository repository; + private Repository repository; /** * Set the repository this event occurred on. From 06f635a4bc315a90d87ae07d0245424d62696373 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Jun 2010 18:43:43 -0700 Subject: [PATCH 058/103] Fix minor formatting issue in UploadPack Change-Id: Ifc0c3a94dc0e16126af6cf17e9c4a7cb96e8ffab Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/transport/UploadPack.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 9f215d751..7e7d4c892 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -567,8 +567,7 @@ private void sendPack() throws IOException { SideBandOutputStream.CH_PROGRESS, bufsz, rawOut)); } - final PackWriter pw; - pw = new PackWriter(db); + final PackWriter pw = new PackWriter(db); try { pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA)); pw.setThin(thin); From 121d009b9b7159ca0bd73a7c25b925516852a026 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Jun 2010 18:25:22 -0700 Subject: [PATCH 059/103] Use ObjectReader in RevWalk, TreeWalk We don't actually need a Repository object here, just an ObjectReader that can load content for us. So change the API to depend on that. However, this breaks the asCommit and asTag legacy translation methods on RevCommit and RevTag, so we still have to keep the Repository inside of RevWalk for those two types. Hopefully we can drop those in the future, and then drop the Repository off the RevWalk. Change-Id: Iba983e48b663790061c43ae9ffbb77dfe6f4818e Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/junit/TestRepository.java | 2 +- .../pgm/opt/AbstractTreeIteratorHandler.java | 2 +- .../treewalk/AbstractTreeIteratorTest.java | 4 +- .../jgit/treewalk/EmptyTreeIteratorTest.java | 7 +- .../jgit/treewalk/FileTreeIteratorTest.java | 4 +- .../jgit/dircache/DirCacheBuildIterator.java | 4 +- .../jgit/dircache/DirCacheBuilder.java | 28 ++++---- .../jgit/dircache/DirCacheIterator.java | 4 +- .../org/eclipse/jgit/lib/ObjectReader.java | 22 ++++++- .../src/org/eclipse/jgit/merge/Merger.java | 14 ++-- .../merge/StrategySimpleTwoWayInCore.java | 2 +- .../jgit/revwalk/MergeBaseGenerator.java | 2 +- .../org/eclipse/jgit/revwalk/ObjectWalk.java | 25 +++++-- .../jgit/revwalk/PendingGenerator.java | 4 +- .../org/eclipse/jgit/revwalk/RevCommit.java | 3 +- .../org/eclipse/jgit/revwalk/RevObject.java | 2 +- .../src/org/eclipse/jgit/revwalk/RevTag.java | 2 +- .../src/org/eclipse/jgit/revwalk/RevWalk.java | 52 +++++++++++---- .../jgit/revwalk/RewriteTreeFilter.java | 2 +- .../eclipse/jgit/storage/pack/PackWriter.java | 2 +- .../eclipse/jgit/transport/ReceivePack.java | 2 +- .../eclipse/jgit/transport/RefAdvertiser.java | 11 ++-- .../jgit/treewalk/AbstractTreeIterator.java | 22 +++---- .../jgit/treewalk/CanonicalTreeParser.java | 65 +++++++------------ .../jgit/treewalk/EmptyTreeIterator.java | 4 +- .../jgit/treewalk/FileTreeIterator.java | 4 +- .../jgit/treewalk/NameConflictTreeWalk.java | 13 +++- .../org/eclipse/jgit/treewalk/TreeWalk.java | 55 ++++++++++------ 28 files changed, 221 insertions(+), 142 deletions(-) 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 a179773d1..96b616889 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 @@ -272,7 +272,7 @@ public RevTree tree(final DirCacheEntry... entries) throws Exception { */ 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); 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 d7b98c509..01981600d 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 @@ -123,7 +123,7 @@ public int parseArguments(final Parameters params) throws CmdLineException { final CanonicalTreeParser p = new CanonicalTreeParser(); 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/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java index e96445a30..12c11482a 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 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 FakeTreeIterator(String pathName, FileMode fileMode) { } @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 111264b1c..1ea2dc625 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 void testAtEOF() throws Exception { 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 void stopWalk() { 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 eb08e495b..f939c90d8 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 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 void testSimpleIterate() throws Exception { 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/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java index 181192d14..1eb95c4be 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 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 DirCacheBuildIterator(final DirCacheBuilder dcb) { } @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 adb8a8d77..ab7513843 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java @@ -164,22 +164,22 @@ public void keep(final int pos, int cnt) { */ public void addTree(final byte[] pathPrefix, final int stage, final Repository db, final AnyObjectId tree) throws IOException { - final TreeWalk tw = new TreeWalk(db); - tw.reset(); - final ObjectReader curs = db.newObjectReader(); + final ObjectReader reader = db.newObjectReader(); try { - tw.addTree(new CanonicalTreeParser(pathPrefix, db, tree - .toObjectId(), curs)); + final TreeWalk tw = new TreeWalk(reader); + tw.reset(); + tw.addTree(new CanonicalTreeParser(pathPrefix, reader, tree + .toObjectId())); + tw.setRecursive(true); + if (tw.next()) { + final DirCacheEntry newEntry = toEntry(stage, tw); + beforeAdd(newEntry); + fastAdd(newEntry); + while (tw.next()) + fastAdd(toEntry(stage, tw)); + } } finally { - curs.release(); - } - tw.setRecursive(true); - if (tw.next()) { - final DirCacheEntry newEntry = toEntry(stage, tw); - beforeAdd(newEntry); - fastAdd(newEntry); - while (tw.next()) - fastAdd(toEntry(stage, tw)); + reader.release(); } } 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 9c4718782..b4e2d2c2d 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 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 DirCacheIterator(final DirCache dc) { } @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/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java index e9afc5043..1af3cb2de 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java @@ -69,8 +69,28 @@ public abstract class ObjectReader { * 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); + open(objectId, typeHint); return true; } catch (MissingObjectException notFound) { return false; 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 d95b11159..68d60c007 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java @@ -70,6 +70,9 @@ 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; @@ -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); } /** @@ -153,6 +157,7 @@ public boolean merge(final AnyObjectId[] tips) throws IOException { } finally { if (inserter != null) inserter.release(); + reader.release(); } } @@ -207,12 +212,7 @@ protected AbstractTreeIterator mergeBase(final int aIdx, final int bIdx) */ protected AbstractTreeIterator openTree(final AnyObjectId treeId) throws IncorrectObjectTypeException, IOException { - final ObjectReader curs = db.newObjectReader(); - 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 891abe058..86ba72474 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java @@ -100,7 +100,7 @@ private static class InCoreMerger extends ThreeWayMerger { InCoreMerger(final Repository local) { super(local); - tw = new NameConflictTreeWalk(db); + tw = new NameConflictTreeWalk(reader); cache = DirCache.newInCore(); } 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 edb883714..76510ce38 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 @@ RevCommit next() throws MissingObjectException, 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 11d40012c..9393e2d17 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.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 RevObject nextObject() throws MissingObjectException, 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 void checkConnectivity() throws MissingObjectException, 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); } } @@ -403,7 +416,7 @@ private void markTreeUninteresting(final RevTree tree) 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 +432,7 @@ private void markTreeUninteresting(final RevTree tree) 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 e723bce51..0e2bb9832 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 @@ RevCommit next() throws MissingObjectException, for (;;) { final RevCommit c = pending.next(); if (c == null) { - walker.curs.release(); + walker.reader.release(); return null; } @@ -174,7 +174,7 @@ else if (canDispose) 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 2d96bbf67..84cc704c3 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 final int getCommitTime() { * @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 14f4836d7..a19f4d83e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java @@ -77,7 +77,7 @@ void parseBody(final RevWalk walk) throws MissingObjectException, final byte[] loadCanonical(final RevWalk walk) throws IOException, MissingObjectException, IncorrectObjectTypeException, CorruptObjectException { - return walk.curs.open(this, getType()).getCachedBytes(); + 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 d2a665e07..a04ea7154 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 final String getShortMessage() { * @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 60c8024b3..51de7c4a5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java @@ -157,9 +157,10 @@ public class RevWalk implements Iterable { private static final int APP_FLAGS = -1 & ~((1 << RESERVED_FLAGS) - 1); - final Repository db; + /** Exists ONLY to support legacy Tag and Commit objects. */ + final Repository repository; - final ObjectReader curs; + final ObjectReader reader; final MutableObjectId idBuffer; @@ -189,11 +190,29 @@ public class RevWalk implements Iterable { * 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 = db.newObjectReader(); + 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(); roots = new ArrayList(); @@ -205,13 +224,19 @@ public RevWalk(final Repository repo) { 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. + *

    + * 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(); } /** @@ -720,7 +745,7 @@ public RevObject parseAny(final AnyObjectId id) throws MissingObjectException, IOException { RevObject r = objects.get(id); if (r == null) { - final ObjectLoader ldr = curs.open(id); + final ObjectLoader ldr = reader.open(id); final byte[] data = ldr.getCachedBytes(); final int type = ldr.getType(); switch (type) { @@ -991,7 +1016,7 @@ protected void reset(int retainFlags) { } } - curs.release(); + reader.release(); roots.clear(); queue = new DateRevQueue(); pending = new StartGenerator(this); @@ -1006,11 +1031,12 @@ protected void reset(int retainFlags) { * 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 d7e5c8028..023ed45fd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java @@ -77,7 +77,7 @@ class RewriteTreeFilter extends RevFilter { private final TreeWalk pathFilter; RewriteTreeFilter(final RevWalk walker, final TreeFilter t) { - pathFilter = new TreeWalk(walker.db); + pathFilter = new TreeWalk(walker.reader); pathFilter.setFilter(t); pathFilter.setRecursive(t.shouldBeRecursive()); } 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 index c7e3c0c9b..3fab3f747 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -739,7 +739,7 @@ private ObjectWalk setUpWalker( final Collection uninterestingObjects) throws MissingObjectException, IOException, IncorrectObjectTypeException { - final ObjectWalk walker = new ObjectWalk(db); + final ObjectWalk walker = new ObjectWalk(reader); walker.setRetainBody(false); walker.sort(RevSort.COMMIT_TIME_DESC); if (thin) 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 1597a35fe..2475b9acf 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java @@ -696,7 +696,7 @@ public void sendAdvertisedRefs(final RefAdvertiser adv) throws IOException { 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(); 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 532cc11a7..df0afe73f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java @@ -54,6 +54,7 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefComparator; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; @@ -124,7 +125,7 @@ public void init(final RevWalk protoWalk, final RevFlag advertisedFlag) { *

      *
    • {@link #send(Map)} *
    • {@link #advertiseHave(AnyObjectId)} - *
    • {@link #includeAdditionalHaves()} + *
    • {@link #includeAdditionalHaves(Repository)} *
    * * @param deref @@ -142,7 +143,7 @@ public void setDerefTags(final boolean deref) { *
      *
    • {@link #send(Map)} *
    • {@link #advertiseHave(AnyObjectId)} - *
    • {@link #includeAdditionalHaves()} + *
    • {@link #includeAdditionalHaves(Repository)} *
    * * @param name @@ -210,12 +211,14 @@ public void advertiseHave(AnyObjectId id) throws IOException { /** * 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 { - for (ObjectId id : walk.getRepository().getAdditionalHaves()) + public void includeAdditionalHaves(Repository src) throws IOException { + for (ObjectId id : src.getAdditionalHaves()) advertiseHave(id); } 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 73357a4df..e74f13e85 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java @@ -55,7 +55,6 @@ 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.ObjectReader; import org.eclipse.jgit.treewalk.filter.TreeFilter; @@ -432,8 +431,8 @@ public String getEntryPathString() { * otherwise the caller would not be able to exit out of the subtree * iterator correctly and return to continue walking this. * - * @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 @@ -441,8 +440,9 @@ public String getEntryPathString() { * @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. @@ -460,12 +460,10 @@ public EmptyTreeIterator createEmptyTreeIterator() { * the caller would not be able to exit out of the subtree iterator * correctly and return to continue walking this. * - * @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 @@ -473,10 +471,10 @@ public EmptyTreeIterator createEmptyTreeIterator() { * @throws IOException * a loose object or pack file could not be read. */ - public AbstractTreeIterator createSubtreeIterator(final Repository repo, - final MutableObjectId idBuffer, final ObjectReader 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 fc088d776..8e4094a05 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java @@ -55,7 +55,6 @@ import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectReader; -import org.eclipse.jgit.lib.Repository; /** Parses raw Git trees from the canonical semi-text/semi-binary format. */ public class CanonicalTreeParser extends AbstractTreeIterator { @@ -85,13 +84,11 @@ public CanonicalTreeParser() { * 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 @@ -100,11 +97,11 @@ public CanonicalTreeParser() { * @throws IOException * a loose object or pack file could not be read. */ - public CanonicalTreeParser(final byte[] prefix, final Repository repo, - final AnyObjectId treeId, final ObjectReader 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) { @@ -130,13 +127,11 @@ public void reset(final byte[] treeData) { /** * 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. @@ -146,13 +141,13 @@ public void reset(final byte[] treeData) { * @throws IOException * a loose object or pack file could not be read. */ - public CanonicalTreeParser resetRoot(final Repository repo, - final AnyObjectId id, final ObjectReader 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; } @@ -180,13 +175,11 @@ public CanonicalTreeParser next() { /** * 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 @@ -195,22 +188,21 @@ public CanonicalTreeParser next() { * @throws IOException * a loose object or pack file could not be read. */ - public void reset(final Repository repo, final AnyObjectId id, - final ObjectReader curs) + public void reset(final ObjectReader reader, final AnyObjectId id) throws IncorrectObjectTypeException, IOException { - reset(curs.open(id, Constants.OBJ_TREE).getCachedBytes()); + reset(reader.open(id, Constants.OBJ_TREE).getCachedBytes()); } @Override - public CanonicalTreeParser createSubtreeIterator(final Repository repo, - final MutableObjectId idBuffer, final ObjectReader 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); } /** @@ -220,32 +212,25 @@ public CanonicalTreeParser createSubtreeIterator(final Repository repo, * 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 ObjectReader 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 ObjectReader curs = repo.newObjectReader(); - 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 1776b5088..7d4ee6d2b 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 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 EmptyTreeIterator(final AbstractTreeIterator p, } @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 8dfab8aa5..7f63646b5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java @@ -54,7 +54,7 @@ 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.util.FS; /** @@ -103,7 +103,7 @@ protected FileTreeIterator(final FileTreeIterator p, final File root, FS fs) { } @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 b569174bd..99126e861 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 @@ 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 aefa79c3a..2ebabcb94 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java @@ -113,11 +113,15 @@ public static TreeWalk forPath(final Repository db, final String path, 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 static TreeWalk forPath(final Repository db, final String path, return forPath(db, path, new ObjectId[] { tree }); } - private final Repository db; + private final ObjectReader reader; private final MutableObjectId idBuffer = new MutableObjectId(); - private final ObjectReader curs; - private TreeFilter filter; AbstractTreeIterator[] trees; @@ -180,19 +182,34 @@ public static TreeWalk forPath(final Repository db, final String path, * the repository the walker will obtain data from. */ public TreeWalk(final Repository repo) { - db = repo; - curs = repo.newObjectReader(); + 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. + *

    + * 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(); } /** @@ -320,7 +337,7 @@ public void reset(final AnyObjectId id) throws MissingObjectException, 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); @@ -367,7 +384,7 @@ public void reset(final AnyObjectId[] ids) throws MissingObjectException, 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; } @@ -837,7 +854,7 @@ public void enterSubtree() throws MissingObjectException, 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; @@ -912,7 +929,7 @@ private void exitSubtree() { 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; } From d6e975f71ba366466f456b9988f1241bef18c3dc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 28 Jun 2010 18:31:29 -0700 Subject: [PATCH 060/103] Use one ObjectReader for WalkFetchConnection Instead of creating new ObjectReader for each walker, use one for the entire connection and delegate reads through it. Change-Id: I7f0a2ec8c9fe60b095a7be77dc423a2ff8b443a3 Signed-off-by: Shawn O. Pearce --- .../jgit/transport/WalkFetchConnection.java | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) 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 89dd6b490..faea378e9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java @@ -70,6 +70,7 @@ import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; @@ -181,11 +182,15 @@ class WalkFetchConnection extends BaseFetchConnection { /** 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(); remotes.add(w); @@ -202,9 +207,9 @@ class WalkFetchConnection extends BaseFetchConnection { fetchErrors = new HashMap>(); packLocks = new ArrayList(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"); @@ -243,6 +248,7 @@ public void setPackLockMessage(final String message) { @Override public void close() { inserter.release(); + reader.release(); for (final RemotePack p : unfetchedPacks) { if (p.tmpIdx != null) p.tmpIdx.delete(); @@ -314,10 +320,17 @@ private void process(final ObjectId id) throws TransportException { } 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 { @@ -374,7 +387,7 @@ private void needs(final RevObject obj) { private void downloadObject(final ProgressMonitor pm, final AnyObjectId id) throws TransportException { - if (local.hasObject(id)) + if (alreadyHave(id)) return; for (;;) { @@ -461,6 +474,15 @@ private void downloadObject(final ProgressMonitor pm, final AnyObjectId id) } } + 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, @@ -522,7 +544,7 @@ private boolean downloadPackedObject(final ProgressMonitor monitor, 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. From 94228bde22debe6ffeb5a374690f023290fc6fd2 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 29 Jun 2010 09:30:29 -0700 Subject: [PATCH 061/103] Use ObjectReader in DirCacheBuilder.addTree Rather than building a custom reader, have the caller supply us one. Change-Id: Ief2b5a6b1b75f05c8a6bc732a60d4d1041dd8254 Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/junit/TestRepository.java | 3 +- .../jgit/dircache/DirCacheBuilder.java | 35 ++++++++----------- .../merge/StrategySimpleTwoWayInCore.java | 2 +- 3 files changed, 18 insertions(+), 22 deletions(-) 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 96b616889..3c5827125 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 @@ -753,7 +753,8 @@ public CommitBuilder parent(RevCommit p) throws Exception { 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); 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 ab7513843..5665002dc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java @@ -50,7 +50,6 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.lib.AnyObjectId; -import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.CanonicalTreeParser; @@ -149,11 +148,12 @@ public void keep(final int pos, int cnt) { * 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 pathPrefix. The ObjectId must be that of a @@ -163,23 +163,18 @@ public void keep(final int pos, int cnt) { * 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 ObjectReader reader = db.newObjectReader(); - try { - final TreeWalk tw = new TreeWalk(reader); - tw.reset(); - tw.addTree(new CanonicalTreeParser(pathPrefix, reader, tree - .toObjectId())); - tw.setRecursive(true); - if (tw.next()) { - final DirCacheEntry newEntry = toEntry(stage, tw); - beforeAdd(newEntry); - fastAdd(newEntry); - while (tw.next()) - fastAdd(toEntry(stage, tw)); - } - } finally { - reader.release(); + final ObjectReader reader, final AnyObjectId tree) throws IOException { + final TreeWalk tw = new TreeWalk(reader); + tw.reset(); + tw.addTree(new CanonicalTreeParser(pathPrefix, reader, tree + .toObjectId())); + tw.setRecursive(true); + if (tw.next()) { + final DirCacheEntry newEntry = toEntry(stage, tw); + beforeAdd(newEntry); + fastAdd(newEntry); + while (tw.next()) + fastAdd(toEntry(stage, tw)); } } 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 86ba72474..29342a730 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java @@ -171,7 +171,7 @@ private void add(final int tree, final int stage) throws IOException { 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; From 4913ad57fce7f9c3ab9600a55ba02f589b3088c1 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 29 Jun 2010 09:32:53 -0700 Subject: [PATCH 062/103] Use a single ObjectReader in IpLogGenerator This way we can be ensured its released when the generator is done running. Change-Id: I6be48d26b9bd5ac176c1316a9aabdf3a897e1696 Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/iplog/IpLogGenerator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 fa2010d1f..433d4338d 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 @@ -183,8 +183,8 @@ public void scan(Repository repo, RevCommit startCommit, String version) try { db = repo; curs = db.newObjectReader(); - rw = new RevWalk(db); - tw = new NameConflictTreeWalk(db); + rw = new RevWalk(curs); + tw = new NameConflictTreeWalk(curs); RevCommit c = rw.parseCommit(startCommit); From 515deaf7e503738b4c53c3c2dfd6d7acab3bef18 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 29 Jun 2010 15:12:51 -0700 Subject: [PATCH 063/103] Ensure RevWalk is released when done Update a number of calling sites of RevWalk to ensure the walker's internal ObjectReader is released after the walk is no longer used. Because the ObjectReader is likely to hold onto a native resource like an Inflater, we don't want to leak them outside of their useful scope. Where possible we also try to share ObjectReaders across several walk pools, or between a walker and a PackWriter. This permits the ObjectReader to actually do some caching if it felt inclined to do so. Not everything was updated, we'll probably need to come back and update even more call sites, but these are some of the biggest offenders. Test cases in particular aren't updated. My plan is to move most storage-agnostic tests onto some purely in-memory storage solution that doesn't do compression. Change-Id: I04087ec79faeea208b19848939898ad7172b6672 Signed-off-by: Shawn O. Pearce --- .../jgit/http/server/InfoRefsServlet.java | 49 +++++---- .../jgit/http/server/ReceivePackServlet.java | 7 +- .../jgit/http/server/UploadPackServlet.java | 7 +- .../jgit/pgm/debug/RebuildCommitGraph.java | 1 + .../org/eclipse/jgit/api/CommitCommand.java | 64 +++++------ .../org/eclipse/jgit/api/MergeCommand.java | 58 +++++----- .../src/org/eclipse/jgit/lib/RefUpdate.java | 14 ++- .../src/org/eclipse/jgit/lib/Repository.java | 10 +- .../jgit/storage/file/RefDirectory.java | 29 +++-- .../jgit/storage/file/RefDirectoryRename.java | 3 +- .../eclipse/jgit/storage/pack/PackWriter.java | 43 ++++++-- .../transport/BasePackFetchConnection.java | 6 ++ .../jgit/transport/BundleFetchConnection.java | 100 ++++++++++-------- .../eclipse/jgit/transport/FetchProcess.java | 37 ++++--- .../eclipse/jgit/transport/PushProcess.java | 50 +++++---- .../eclipse/jgit/transport/ReceivePack.java | 1 + .../eclipse/jgit/transport/UploadPack.java | 3 +- 17 files changed, 298 insertions(+), 184 deletions(-) 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 f667ce95a..647919e06 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 @@ public void doGet(final HttpServletRequest req, 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 refs = db.getAllRefs(); - refs.remove(Constants.HEAD); - adv.send(refs); - out.close(); + Map 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/ReceivePackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java index 49fd535a7..4bc05c188 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 @@ static class InfoRefs extends SmartServiceInfoRefs { 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/UploadPackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java index 6d0d64fc6..602d66a90 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 @@ static class InfoRefs extends SmartServiceInfoRefs { 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(); + } } } 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 8ba3e4b90..5b75c1b5c 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 @@ -297,6 +297,7 @@ private Map computeNewRefs() throws IOException { name, id)); } } finally { + rw.release(); br.close(); } return refs; 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 1cf2fe669..c2db140b0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -160,38 +160,42 @@ public RevCommit call() throws NoHeadException, NoMessageException, .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); - 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(); + 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; } - 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)); + 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(); } } finally { odi.release(); 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 76a3bc479..972aa618a 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 MergeResult call() throws NoHeadException, // Check for FAST_FORWARD, ALREADY_UP_TO_DATE RevWalk revWalk = new RevWalk(repo); - RevCommit headCommit = revWalk.lookupCommit(head.getObjectId()); + try { + RevCommit headCommit = revWalk.lookupCommit(head.getObjectId()); - Ref ref = commits.get(0); + Ref ref = commits.get(0); - refLogMessage.append(ref.getName()); + refLogMessage.append(ref.getName()); - // handle annotated tags - ObjectId objectId = ref.getPeeledObjectId(); - if (objectId == null) - objectId = ref.getObjectId(); + // 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); + 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( 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 e04a587ac..e6f893338 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 Result forceUpdate() throws IOException { * 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 @@ Result execute(Result status) throws IOException { * @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/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index aeb160e52..cb663aee3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -484,9 +484,17 @@ public RefRename renameRef(final String fromRef, final String toRef) throws IOEx * 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(); RevObject ref = null; - RevWalk rw = new RevWalk(this); for (int i = 0; i < rev.length; ++i) { switch (rev[i]) { case '^': diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java index f7ffa3e39..68b0270df 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java @@ -74,6 +74,7 @@ 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; @@ -418,16 +419,7 @@ public Ref peel(final Ref ref) throws IOException { if (leaf.isPeeled() || leaf.getObjectId() == null) return ref; - RevWalk rw = new RevWalk(getRepository()); - RevObject obj = rw.parseAny(leaf.getObjectId()); - ObjectIdRef newLeaf; - if (obj instanceof RevTag) { - newLeaf = new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf - .getName(), leaf.getObjectId(), rw.peel(obj).copy()); - } else { - newLeaf = new ObjectIdRef.PeeledNonTag(leaf.getStorage(), leaf - .getName(), leaf.getObjectId()); - } + 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. @@ -444,6 +436,23 @@ public Ref peel(final Ref ref) throws IOException { 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); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java index b43b70f1e..4f3efe343 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectoryRename.java @@ -92,10 +92,10 @@ protected Result doRename() throws IOException { if (source.getRef().isSymbolic()) return Result.IO_FAILURE; // not supported - final RevWalk rw = new RevWalk(refdb.getRepository()); objId = source.getOldObjectId(); updateHEAD = needToUpdateHEAD(); tmp = refdb.newTemporaryUpdate(); + final RevWalk rw = new RevWalk(refdb.getRepository()); try { // First backup the source so its never unreachable. tmp.setNewObjectId(objId); @@ -177,6 +177,7 @@ protected Result doRename() throws IOException { } catch (IOException err) { refdb.fileFor(tmp.getName()).delete(); } + rw.release(); } } 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 index 3fab3f747..c851238c9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -63,6 +63,7 @@ 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.CoreConfig; import org.eclipse.jgit.lib.NullProgressMonitor; @@ -181,8 +182,6 @@ public class PackWriter { // edge objects for thin packs private final ObjectIdSubclassMap edgeObjects = new ObjectIdSubclassMap(); - private final Repository db; - private final Deflater deflater; private final ObjectReader reader; @@ -218,19 +217,51 @@ public class PackWriter { * repository where objects are stored. */ public PackWriter(final Repository repo) { - this.db = repo; + this(repo, repo.newObjectReader()); + } - reader = db.newObjectReader(); + /** + * Create a writer to load objects from the specified reader. + *

    + * 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. + *

    + * 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 CoreConfig coreConfig = db.getConfig().get(CoreConfig.KEY); - this.deflater = new Deflater(coreConfig.getCompression()); + final CoreConfig coreConfig = configOf(repo).get(CoreConfig.KEY); + deflater = new Deflater(coreConfig.getCompression()); outputVersion = coreConfig.getPackIndexVersion(); } + 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. 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 8c336c525..af18f18d8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java @@ -270,6 +270,12 @@ protected void doFetch(final ProgressMonitor monitor, } } + @Override + public void close() { + walk.release(); + super.close(); + } + private int maxTimeWanted(final Collection wants) { int maxTime = 0; for (final Ref r : wants) { 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 98ecc5540..126acab48 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java @@ -213,57 +213,65 @@ private void verifyPrerequisites() throws TransportException { return; final RevWalk rw = new RevWalk(transport.local); - final RevFlag PREREQ = rw.newFlag("PREREQ"); - final RevFlag SEEN = rw.newFlag("SEEN"); - - final Map missing = new HashMap(); - final List commits = new ArrayList(); - for (final Map.Entry 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); - } - } - 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. - } - } - - int remaining = commits.size(); try { - RevCommit c; - while ((c = rw.next()) != null) { - if (c.has(PREREQ)) { - c.add(SEEN); - if (--remaining == 0) - break; + final RevFlag PREREQ = rw.newFlag("PREREQ"); + final RevFlag SEEN = rw.newFlag("SEEN"); + + final Map missing = new HashMap(); + final List commits = new ArrayList(); + for (final Map.Entry 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 (IOException err) { - throw new TransportException(transport.uri, JGitText.get().cannotReadObject, err); - } + if (!missing.isEmpty()) + throw new MissingBundlePrerequisiteException(transport.uri, + missing); - if (remaining > 0) { - for (final RevObject o : commits) { - if (!o.has(SEEN)) - missing.put(o, prereqs.get(o)); + 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. + } } - throw new MissingBundlePrerequisiteException(transport.uri, missing); + + 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); + } + + if (remaining > 0) { + for (final RevObject o : commits) { + if (!o.has(SEEN)) + missing.put(o, prereqs.get(o)); + } + throw new MissingBundlePrerequisiteException(transport.uri, + missing); + } + } finally { + rw.release(); } } 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 72d73eb59..ca6885805 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java @@ -176,16 +176,21 @@ else if (tagopt == TagOpt.FETCH_TAGS) } 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()) { @@ -296,11 +301,15 @@ private void updateFETCH_HEAD(final FetchResult result) throws IOException { 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/PushProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java index 02497cb06..6cd796a0d 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(); + monitor.beginTask(PROGRESS_OPENING_CONNECTION, + ProgressMonitor.UNKNOWN); - final Map preprocessed = prepareRemoteUpdates(); - if (transport.isDryRun()) - modifyUpdatesForDryRun(); - else if (!preprocessed.isEmpty()) - connection.push(monitor, preprocessed); + final PushResult res = new PushResult(); + connection = transport.openPush(); + try { + res.setAdvertisedRefs(transport.getURI(), connection + .getRefsMap()); + res.setRemoteUpdates(toPush); + monitor.endTask(); + + final Map 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()); + walker.release(); } - if (!transport.isDryRun()) - updateTrackingRefs(); - for (final RemoteRefUpdate rru : toPush.values()) { - final TrackingRefUpdate tru = rru.getTrackingRefUpdate(); - if (tru != null) - res.add(tru); - } - return res; } private Map 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 2475b9acf..6b0a9b6cf 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java @@ -574,6 +574,7 @@ public void receive(final InputStream input, final OutputStream output, service(); } finally { + walk.release(); try { if (pckOut != null) pckOut.flush(); 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 7e7d4c892..02ce251be 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -296,6 +296,7 @@ public void upload(final InputStream input, final OutputStream output, pckOut = new PacketLineOut(rawOut); service(); } finally { + walk.release(); if (timer != null) { try { timer.terminate(); @@ -567,7 +568,7 @@ private void sendPack() throws IOException { SideBandOutputStream.CH_PROGRESS, bufsz, rawOut)); } - final PackWriter pw = new PackWriter(db); + final PackWriter pw = new PackWriter(db, walk.getObjectReader()); try { pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA)); pw.setThin(thin); From cb9d8285bacf315e323d41f7bdc42acc8124fe98 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 30 Jun 2010 09:48:36 -0700 Subject: [PATCH 064/103] Create NoWorkTreeException for bare repositories Using a custom exception type makes it easire for an application developer to understand why an exception was thrown out of a method we declare. To remain compatiable with existing callers, we still extend off IllegalStateException. Change-Id: Ideeef2399b11ca460a2dbb3cd80eb76aa0a025ba Signed-off-by: Shawn O. Pearce --- .../file/RepositorySetupWorkDirTest.java | 13 ++-- .../org/eclipse/jgit/dircache/DirCache.java | 9 +-- .../jgit/errors/NoWorkTreeException.java | 59 +++++++++++++++++++ .../src/org/eclipse/jgit/lib/Repository.java | 36 +++++------ 4 files changed, 87 insertions(+), 30 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/NoWorkTreeException.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java index caaeda2b0..4f6d5b3bd 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RepositorySetupWorkDirTest.java @@ -48,6 +48,7 @@ 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; @@ -136,8 +137,8 @@ public void testExceptionThrown_BareRepoGetWorkDir() throws Exception { File gitDir = getFile("workdir"); try { new FileRepository(gitDir).getWorkTree(); - fail("Expected IllegalStateException missing"); - } catch (IllegalStateException e) { + fail("Expected NoWorkTreeException missing"); + } catch (NoWorkTreeException e) { // expected } } @@ -146,8 +147,8 @@ public void testExceptionThrown_BareRepoGetIndex() throws Exception { File gitDir = getFile("workdir"); try { new FileRepository(gitDir).getIndex(); - fail("Expected IllegalStateException missing"); - } catch (IllegalStateException e) { + fail("Expected NoWorkTreeException missing"); + } catch (NoWorkTreeException e) { // expected } } @@ -156,8 +157,8 @@ public void testExceptionThrown_BareRepoGetIndexFile() throws Exception { File gitDir = getFile("workdir"); try { new FileRepository(gitDir).getIndexFile(); - fail("Expected Exception missing"); - } catch (IllegalStateException e) { + fail("Expected NoWorkTreeException missing"); + } catch (NoWorkTreeException e) { // expected } } 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 6b1349ade..e5b5771d7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java @@ -62,6 +62,7 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.errors.UnmergedPathException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; @@ -168,7 +169,7 @@ public static DirCache read(final File indexLocation) * 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 IllegalStateException + * @throws NoWorkTreeException * if the repository is bare (lacks a working directory). * @throws IOException * the index file is present but could not be read. @@ -177,7 +178,7 @@ public static DirCache read(final File indexLocation) * library does not support. */ public static DirCache read(final Repository db) - throws CorruptObjectException, IOException { + throws NoWorkTreeException, CorruptObjectException, IOException { return read(db.getIndexFile()); } @@ -233,7 +234,7 @@ public static DirCache lock(final File indexLocation) * 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 IllegalStateException + * @throws NoWorkTreeException * if the repository is bare (lacks a working directory). * @throws IOException * the index file is present but could not be read, or the lock @@ -243,7 +244,7 @@ public static DirCache lock(final File indexLocation) * library does not support. */ public static DirCache lock(final Repository db) - throws CorruptObjectException, IOException { + throws NoWorkTreeException, CorruptObjectException, IOException { return lock(db.getIndexFile()); } 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 000000000..f2980efe6 --- /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/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index cb663aee3..70550a832 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -62,6 +62,7 @@ import org.eclipse.jgit.dircache.DirCache; 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; @@ -860,13 +861,12 @@ public Map> getAllRefsByPeeledObjectId() { * {@link Repository} * @throws IOException * if the index can not be read - * @throws IllegalStateException + * @throws NoWorkTreeException * if this is bare (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(); @@ -878,13 +878,12 @@ public GitIndex getIndex() throws IOException, IllegalStateException { /** * @return the index file location - * @throws IllegalStateException + * @throws NoWorkTreeException * if this is bare (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; } @@ -1036,13 +1035,12 @@ public boolean isBare() { /** * @return the root directory of the working tree, where files are checked * out for viewing and editing. - * @throws IllegalStateException + * @throws NoWorkTreeException * if the repository is bare and has no working directory. */ - public File getWorkTree() throws IllegalStateException { + public File getWorkTree() throws NoWorkTreeException { if (isBare()) - throw new IllegalStateException( - JGitText.get().bareRepositoryNoWorkdirAndIndex); + throw new NoWorkTreeException(); return workTree; } @@ -1085,13 +1083,12 @@ public abstract ReflogReader getReflogReader(String refName) * @return a String containing the content of the MERGE_MSG file or * {@code null} if this file doesn't exist * @throws IOException - * @throws IllegalStateException + * @throws NoWorkTreeException * if the repository is "bare" */ - public String readMergeCommitMsg() throws IOException { + public String readMergeCommitMsg() throws IOException, NoWorkTreeException { if (isBare() || getDirectory() == null) - throw new IllegalStateException( - JGitText.get().bareRepositoryNoWorkdirAndIndex); + throw new NoWorkTreeException(); File mergeMsgFile = new File(getDirectory(), Constants.MERGE_MSG); try { @@ -1112,13 +1109,12 @@ public String readMergeCommitMsg() throws IOException { * 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 IllegalStateException + * @throws NoWorkTreeException * if the repository is "bare" */ - public List readMergeHeads() throws IOException { + public List readMergeHeads() throws IOException, NoWorkTreeException { if (isBare() || getDirectory() == null) - throw new IllegalStateException( - JGitText.get().bareRepositoryNoWorkdirAndIndex); + throw new NoWorkTreeException(); File mergeHeadFile = new File(getDirectory(), Constants.MERGE_HEAD); byte[] raw; From a1d5f5b6b526d086a0963c634a38edb6789a4594 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 30 Jun 2010 10:39:00 -0700 Subject: [PATCH 065/103] Move DirCache factory methods to Repository Instead of creating the DirCache from a static factory method, use an instance method on Repository, permitting the implementation to override the method with a completely different type of DirCache reading and writing. This would better support a repository in the cloud strategy, or even just an in-memory unit test environment. Change-Id: I6399894b12d6480c4b3ac84d10775dfd1b8d13e7 Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/pgm/Rm.java | 2 +- .../eclipse/jgit/pgm/debug/MakeCacheTree.java | 2 +- .../eclipse/jgit/pgm/debug/ReadDirCache.java | 3 +- .../eclipse/jgit/pgm/debug/ShowCacheTree.java | 2 +- .../eclipse/jgit/pgm/debug/ShowDirCache.java | 2 +- .../eclipse/jgit/pgm/debug/WriteDirCache.java | 2 +- .../jgit/dircache/DirCacheBasicTest.java | 20 ++++---- .../dircache/DirCacheBuilderIteratorTest.java | 2 +- .../jgit/dircache/DirCacheBuilderTest.java | 20 ++++---- .../jgit/dircache/DirCacheFindTest.java | 2 +- .../jgit/dircache/DirCacheIteratorTest.java | 16 +++--- .../jgit/dircache/DirCacheLargePathTest.java | 4 +- .../jgit/dircache/DirCacheTreeTest.java | 14 ++--- .../eclipse/jgit/merge/CherryPickTest.java | 8 +-- .../eclipse/jgit/merge/SimpleMergeTest.java | 42 +++++++-------- .../treewalk/NameConflictTreeWalkTest.java | 16 +++--- .../jgit/treewalk/PostOrderTreeWalkTest.java | 6 +-- .../filter/PathSuffixFilterTestCase.java | 4 +- .../org/eclipse/jgit/api/CommitCommand.java | 2 +- .../org/eclipse/jgit/dircache/DirCache.java | 51 ------------------- .../src/org/eclipse/jgit/lib/Repository.java | 48 ++++++++++++++++- 21 files changed, 131 insertions(+), 137 deletions(-) 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 b0cc5248c..9f577ff05 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 @@ -69,7 +69,7 @@ class Rm extends TextBuiltin { protected void run() throws Exception { 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/debug/MakeCacheTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/MakeCacheTree.java index d772ffe23..709b45a17 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 @@ 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 2a1079b31..0ca050880 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 @@ 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 @@ protected void run() throws Exception { 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/ShowCacheTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCacheTree.java index 09796edb3..c49aefbf2 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 @@ 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 854596c97..a94d37ff8 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 @@ protected void run() throws Exception { 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/WriteDirCache.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteDirCache.java index cee5966a0..142dbeecc 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 @@ 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.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java index f4692b168..c3ac952a1 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 void testReadMissing_RealIndex() throws Exception { 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 void testLockMissing_RealIndex() throws Exception { 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 void testWriteEmptyUnlock_RealIndex() throws Exception { 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 void testWriteEmptyCommit_RealIndex() throws Exception { 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 void testWriteEmptyReadEmpty_RealIndex() throws Exception { 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 void testWriteEmptyLockEmpty_RealIndex() throws Exception { 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 void testWriteEmptyLockEmpty_RealIndex() throws Exception { } 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 void testBuildThenClear() throws Exception { } 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 03bb7f5e8..a09f8e86c 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 @@ 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 e919e41f4..81ffab914 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 @@ 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 void testBuildEmpty() throws Exception { assertTrue(dc.commit()); } { - final DirCache dc = DirCache.read(db); + final DirCache dc = db.readDirCache(); assertEquals(0, dc.getEntryCount()); } } @@ -86,7 +86,7 @@ public void testBuildOneFile_FinishWriteCommit() throws Exception { 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 void testBuildOneFile_FinishWriteCommit() throws Exception { 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 void testBuildOneFile_Commit() throws Exception { 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 void testBuildOneFile_Commit() throws Exception { 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 void testBuildOneFile_Commit() throws Exception { 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 void testFindSingleFile() throws Exception { } 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 void testAdd_InGitSortOrder() throws Exception { } 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 void testAdd_ReverseGitSortOrder() throws Exception { } 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 d5a632c48..5533fe358 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 @@ 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 efea11738..24e3c34dd 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 @@ 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 void testEmptyTree_NoTreeWalk() throws Exception { } 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 void testEmptyTree_WithTreeWalk() throws Exception { } 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 void testNoSubtree_NoTreeWalk() throws Exception { } 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 void testNoSubtree_WithTreeWalk() throws Exception { } 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 void testSingleSubtree_NoRecursion() throws Exception { } 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 void testSingleSubtree_Recursive() throws Exception { } 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 void testTwoLevelSubtree_Recursive() throws Exception { } 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 0926ab989..a6d7e39eb 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 @@ private void testLongPath(final int len) throws CorruptObjectException, 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 @@ private void testLongPath(final int len) throws CorruptObjectException, 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 8345c5d83..dfca2fb29 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 @@ 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 void testEmptyCache_CreateEmptyCacheTree() throws Exception { } 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 void testEmptyCache_Clear_NoCacheTree() throws Exception { } 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 void testSingleSubtree() throws Exception { } 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 void testTwoLevelSubtree() throws Exception { * @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 void testWriteReadTree() throws CorruptObjectException, IOException { 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/merge/CherryPickTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CherryPickTest.java index 1b4a11db5..1cd126163 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 @@ -67,10 +67,10 @@ public void testPick() throws Exception { // 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(); 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 a4ef2cd64..8657c52b1 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 @@ -105,9 +105,9 @@ public void testTrivialTwoWay_conflict() throws IOException { } 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(); @@ -157,9 +157,9 @@ public void testTrivialTwoWay_validSubtreeSort() throws Exception { } 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(); @@ -204,9 +204,9 @@ public void testTrivialTwoWay_concurrentSubtreeChange() throws Exception { } 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(); @@ -237,9 +237,9 @@ public void testTrivialTwoWay_conflictSubtreeChange() throws Exception { } 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(); @@ -269,9 +269,9 @@ public void testTrivialTwoWay_leftDFconflict1() throws Exception { } 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(); @@ -301,9 +301,9 @@ public void testTrivialTwoWay_rightDFconflict1() throws Exception { } 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(); @@ -331,9 +331,9 @@ public void testTrivialTwoWay_leftDFconflict2() throws Exception { } 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(); 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 35298b803..675331baf 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 void testNoDF_NoGap() throws Exception { } 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 void testDF_NoGap() throws Exception { } 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 void testDF_GapByOne() throws Exception { } 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 d136b8f29..274df5bec 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 void testResetDoesNotAffectPostOrder() throws Exception { } 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 void testNoPostOrder() throws Exception { } 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 void testWithPostOrder_EnterSubtree() throws Exception { } 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 ad51ac2dd..302eada99 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 @@ -64,7 +64,7 @@ public void testNonRecursiveFiltering() throws IOException { 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 = DirCache.read(db); + final DirCache dc = db.readDirCache(); final DirCacheBuilder builder = dc.builder(); final DirCacheEntry aSthEntry = new DirCacheEntry("a.sth"); aSthEntry.setFileMode(FileMode.REGULAR_FILE); @@ -100,7 +100,7 @@ public void testRecursiveFiltering() throws IOException { 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 = DirCache.read(db); + final DirCache dc = db.readDirCache(); final DirCacheBuilder builder = dc.builder(); final DirCacheEntry aSthEntry = new DirCacheEntry("a.sth"); aSthEntry.setFileMode(FileMode.REGULAR_FILE); 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 c2db140b0..17b711347 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -139,7 +139,7 @@ public RevCommit call() throws NoHeadException, NoMessageException, parents.add(0, headId); // lock the index - DirCache index = DirCache.lock(repo); + DirCache index = repo.lockDirCache(); try { ObjectInserter odi = repo.newObjectInserter(); try { 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 e5b5771d7..cc10fad2b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java @@ -62,12 +62,10 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.errors.UnmergedPathException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.storage.file.LockFile; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.MutableInteger; @@ -158,30 +156,6 @@ public static DirCache read(final File indexLocation) return c; } - /** - * Create a new in-core index representation and read an index from disk. - *

    - * 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 NoWorkTreeException - * if the repository is bare (lacks a working directory). - * @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 NoWorkTreeException, CorruptObjectException, IOException { - return read(db.getIndexFile()); - } - /** * Create a new in-core index representation, lock it, and read from disk. *

    @@ -223,31 +197,6 @@ public static DirCache lock(final File indexLocation) return c; } - /** - * Create a new in-core index representation, lock it, and read from disk. - *

    - * 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 NoWorkTreeException - * if the repository is bare (lacks a working directory). - * @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 NoWorkTreeException, CorruptObjectException, IOException { - return lock(db.getIndexFile()); - } - /** Location of the current version of the index file. */ private final File liveFile; 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 70550a832..a60cba56f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -60,6 +60,7 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.NoWorkTreeException; @@ -887,6 +888,51 @@ public File getIndexFile() throws NoWorkTreeException { return indexFile; } + /** + * Create a new in-core index representation and read an index from disk. + *

    + * 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 the repository is bare (lacks a working directory). + * @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. + *

    + * 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 the repository is bare (lacks a working directory). + * @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; @@ -926,7 +972,7 @@ public RepositoryState getRepositoryState() { 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; } From 23e7f6376ada54b1be232b15366d850d3038b95f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 30 Jun 2010 18:36:10 -0700 Subject: [PATCH 066/103] Add openStream to ObjectLoader for big blobs Blobs that are too large to read as a single byte array should be accessed through an InputStream based interface instead, allowing the application to walk through the data stream incrementally. Define the basic interface to support streaming contents, but don't implement it yet for the file based backend. Change-Id: If9e4442e9ef4ed52c3e0f1af9398199a73145516 Signed-off-by: Shawn O. Pearce --- .../jgit/errors/LargeObjectException.java | 67 +++++++++ .../org/eclipse/jgit/lib/ObjectLoader.java | 88 ++++++++++- .../org/eclipse/jgit/lib/ObjectStream.java | 137 ++++++++++++++++++ .../jgit/storage/file/PackedObjectLoader.java | 15 ++ .../storage/file/UnpackedObjectLoader.java | 13 ++ 5 files changed, 318 insertions(+), 2 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/LargeObjectException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectStream.java 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 000000000..d897c51de --- /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/lib/ObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java index 1a8d1ba9b..e7be11a13 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java @@ -47,12 +47,20 @@ 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. * New loaders are constructed for every object. */ public abstract class ObjectLoader { + private static final int LARGE_OBJECT = 1024 * 1024; + /** * @return Git in pack object type, see {@link Constants}. */ @@ -63,6 +71,15 @@ 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() { + return LARGE_OBJECT <= getSize(); + } + /** * Obtain a copy of the bytes of this object. *

    @@ -70,8 +87,12 @@ public abstract class ObjectLoader { * 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,6 +108,69 @@ public final byte[] getBytes() { * 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(); + 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 ObjectStream openStream() throws MissingObjectException, + IOException; + + /** + * Copy this object to the output stream. + *

    + * 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. + *

    + * 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 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()); + } + } } 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 000000000..ec2e8f099 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectStream.java @@ -0,0 +1,137 @@ +/* + * 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.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. + *

    + * 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; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java index ad4042e17..f056c7413 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java @@ -46,7 +46,11 @@ package org.eclipse.jgit.storage.file; +import java.io.IOException; + +import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectStream; /** Object loaded in from a {@link PackFile}. */ final class PackedObjectLoader extends ObjectLoader { @@ -71,4 +75,15 @@ public final long getSize() { public final byte[] getCachedBytes() { return data; } + + @Override + public final boolean isLarge() { + return false; + } + + @Override + public final ObjectStream openStream() throws MissingObjectException, + IOException { + return new ObjectStream.SmallStream(this); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java index b85ec149e..4a70793d0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java @@ -52,10 +52,12 @@ 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.Constants; import org.eclipse.jgit.lib.InflaterCache; 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; @@ -214,4 +216,15 @@ public long getSize() { public byte[] getCachedBytes() { return bytes; } + + @Override + public final boolean isLarge() { + return false; + } + + @Override + public final ObjectStream openStream() throws MissingObjectException, + IOException { + return new ObjectStream.SmallStream(this); + } } From ad0383734ebdbfb919ba14ca53f7e40501f65cee Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 30 Jun 2010 18:40:54 -0700 Subject: [PATCH 067/103] Lazily allocate Deflater in PackWriter Only allocate the Deflater if we can't reuse everything, but also make sure we release it when we release the PackWriter's resources. Change-Id: I16a32b94647af0778658eda87acbafc9a25b314a Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/storage/pack/PackWriter.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) 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 index c851238c9..d39d57481 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -182,7 +182,9 @@ public class PackWriter { // edge objects for thin packs private final ObjectIdSubclassMap edgeObjects = new ObjectIdSubclassMap(); - private final Deflater deflater; + private int compressionLevel; + + private Deflater myDeflater; private final ObjectReader reader; @@ -252,7 +254,7 @@ public PackWriter(final Repository repo, final ObjectReader reader) { reuseSupport = null; final CoreConfig coreConfig = configOf(repo).get(CoreConfig.KEY); - deflater = new Deflater(coreConfig.getCompression()); + compressionLevel = coreConfig.getCompression(); outputVersion = coreConfig.getPackIndexVersion(); } @@ -639,6 +641,10 @@ public void writePack(ProgressMonitor compressMonitor, /** Release all resources used by this writer. */ public void release() { reader.release(); + if (myDeflater != null) { + myDeflater.end(); + myDeflater = null; + } } private void searchForReuse(ProgressMonitor compressMonitor) @@ -745,6 +751,7 @@ private void redoSearchForReuse(final ObjectToPack otp) throws IOException, private void writeWholeObjectDeflate(PackOutputStream out, final ObjectToPack otp) throws IOException { + final Deflater deflater = deflater(); final ObjectLoader loader = reader.open(otp, otp.getType()); final byte[] data = loader.getCachedBytes(); out.writeHeader(otp, data.length); @@ -760,6 +767,12 @@ private void writeWholeObjectDeflate(PackOutputStream out, } while (!deflater.finished()); } + 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); From a0fd06e5c2696cc6bed396fd513ec8e4465e399c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 30 Jun 2010 18:50:50 -0700 Subject: [PATCH 068/103] Stream whole deflated objects in PackWriter Instead of loading the entire object as a byte array and passing that into the deflater, let the ObjectLoader copy the object onto the DeflaterOutputStream. This has the nice side effect of using some sort of stride hack in the Sun implementation that may improve compression performance. Change-Id: I3f3d681b06af0da93ab96c75468e00e183ff32fe Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/storage/pack/PackWriter.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) 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 index d39d57481..2fecc6875 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -56,6 +56,7 @@ import java.util.Iterator; import java.util.List; import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.CorruptObjectException; @@ -752,19 +753,14 @@ private void redoSearchForReuse(final ObjectToPack otp) throws IOException, private void writeWholeObjectDeflate(PackOutputStream out, final ObjectToPack otp) throws IOException { final Deflater deflater = deflater(); - final ObjectLoader loader = reader.open(otp, otp.getType()); - final byte[] data = loader.getCachedBytes(); - out.writeHeader(otp, data.length); - deflater.reset(); - deflater.setInput(data, 0, data.length); - deflater.finish(); + final ObjectLoader ldr = reader.open(otp, otp.getType()); - byte[] buf = out.getCopyBuffer(); - do { - final int n = deflater.deflate(buf, 0, buf.length); - if (n > 0) - out.write(buf, 0, n); - } while (!deflater.finished()); + out.writeHeader(otp, ldr.getSize()); + + deflater.reset(); + DeflaterOutputStream dst = new DeflaterOutputStream(out, deflater); + ldr.copyTo(dst); + dst.finish(); } private Deflater deflater() { From d04b7972d891a51b4b4c09128431961fece52e75 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 30 Jun 2010 18:56:20 -0700 Subject: [PATCH 069/103] Use copyTo during checkout of files to working tree This way we can stream a large file through memory, rather than loading the entire thing into a single contiguous byte array. Change-Id: I3ada2856af2bf518f072edec242667a486fb0df1 Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/lib/GitIndex.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) 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 0495d38a1..1df25ca70 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java @@ -874,16 +874,15 @@ public void checkout(File wd) throws IOException { */ public void checkoutEntry(File wd, Entry e) throws IOException { ObjectLoader ol = db.open(e.sha1, Constants.OBJ_BLOB); - byte[] bytes = ol.getBytes(); 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)) From 2489088235543732ee0874a090a98b344255a9a7 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 30 Jun 2010 19:07:36 -0700 Subject: [PATCH 070/103] Permit AnyObjectTo to compareTo AnyObjectId Assume that the argument of compareTo won't be mutated while we are doing the compare, and support the wider AnyObjectId type so MutableObjectId is suitable on either side of the compareTo call. Change-Id: I2a63a496c0a7b04f0e5f27d588689c6d5e149d98 Signed-off-by: Shawn O. Pearce --- org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 7d08f3d4c..ecaa82b75 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 final int getFirstByte() { * @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 int compareTo(final ObjectId other) { } public int compareTo(final Object other) { - return compareTo(((ObjectId) other)); + return compareTo(((AnyObjectId) other)); } /** From fa23482ca71314ef412ca7b2a4a5ae6886507dcf Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 1 Jul 2010 18:26:17 -0700 Subject: [PATCH 071/103] Support large loose objects as streams Big loose objects can now be streamed if they are over the large object size threshold. This prevents the JVM heap from exploding with a very large byte array to hold the slurped file, and then again with its uncompressed copy. We may have slightly slowed down the simple case for small loose objects, as the loader no longer slurps the entire thing and decompresses in memory. To try and keep good performance for the very common small objects that are below 8 KiB in size, buffers are set to 8 KiB, causing the reader to slurp most of the file anyway. However the data has to be copied at least once, from the BufferedInputStream into the InflaterInputStream. New unit tests are supplied to get nearly 100% code coverage on the unpacked code paths, for both standard and pack style loose objects. We tested a fair chunk of the code elsewhere, but these new tests are better isolated to the specific branches in the code path. Change-Id: I87b764ab1b84225e9b5619a2a55fd8eaa640e1fe Signed-off-by: Shawn O. Pearce --- .../jgit/storage/file/UnpackedObjectTest.java | 430 ++++++++++++++++++ .../org/eclipse/jgit/lib/ObjectLoader.java | 51 +++ .../org/eclipse/jgit/lib/ObjectStream.java | 83 ++++ .../jgit/storage/file/ObjectDirectory.java | 9 +- .../jgit/storage/file/UnpackedObject.java | 321 +++++++++++++ .../storage/file/UnpackedObjectLoader.java | 230 ---------- .../jgit/storage/file/WindowCursor.java | 2 +- .../jgit/transport/WalkFetchConnection.java | 7 +- 8 files changed, 898 insertions(+), 235 deletions(-) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/UnpackedObjectTest.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java 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 000000000..4ca64d3eb --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/UnpackedObjectTest.java @@ -0,0 +1,430 @@ +/* + * 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(UnpackedObject.LARGE_OBJECT + 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_LargeObject_CorruptZLibStream() + throws Exception { + final int type = Constants.OBJ_BLOB; + byte[] data = rng.nextBytes(UnpackedObject.LARGE_OBJECT + 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 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(UnpackedObject.LARGE_OBJECT + 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/src/org/eclipse/jgit/lib/ObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java index e7be11a13..312fa958c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java @@ -173,4 +173,55 @@ public void copyTo(OutputStream out) throws MissingObjectException, out.write(getCachedBytes()); } } + + /** + * Simple loader around the cached byte array. + *

    + * ObjectReader implementations can use this stream type when the object's + * content is small enough to be accessed as a single byte array. + */ + 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/ObjectStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectStream.java index ec2e8f099..86d66439d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectStream.java @@ -43,6 +43,7 @@ package org.eclipse.jgit.lib; +import java.io.IOException; import java.io.InputStream; /** Stream of data coming from an object loaded by {@link ObjectLoader}. */ @@ -134,4 +135,86 @@ public void reset() { ptr = mark; } } + + /** + * Simple filter stream around another stream. + *

    + * 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/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java index 7020b7a67..49a69a536 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java @@ -45,6 +45,7 @@ import java.io.BufferedReader; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; @@ -326,7 +327,13 @@ 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; } 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 000000000..9072fcd2d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * 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.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; + + static final int LARGE_OBJECT = 1024 * 1024; + + /** + * 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(); + in = inflate(in, wc); + int avail = readSome(in, 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 < LARGE_OBJECT || 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(in, data, n, data.length - n); + 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 < LARGE_OBJECT || path == null) { + in.reset(); + IO.skipFully(in, p); + in = inflate(in, wc); + byte[] data = new byte[(int) size]; + IO.readFully(in, data, 0, data.length); + 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); + } + } + + 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(InputStream in, final ObjectId id) { + final Inflater inf = InflaterCache.get(); + return new InflaterInputStream(in, inf) { + @Override + public int read(byte[] b, int off, int cnt) throws IOException { + try { + return super.read(b, off, cnt); + } catch (ZipException badStream) { + throw new CorruptObjectException(id, + JGitText.get().corruptObjectBadStream); + } + } + + @Override + public void close() throws IOException { + super.close(); + InflaterCache.release(inf); + } + }; + } + + private static InputStream inflate(InputStream in, WindowCursor wc) { + return new InflaterInputStream(in, wc.inflater(), 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, 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, 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/storage/file/UnpackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java deleted file mode 100644 index 4a70793d0..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObjectLoader.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.storage.file; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.zip.DataFormatException; -import java.util.zip.Inflater; - -import org.eclipse.jgit.JGitText; -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.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.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 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 final boolean isLarge() { - return false; - } - - @Override - public final ObjectStream openStream() throws MissingObjectException, - IOException { - return new ObjectStream.SmallStream(this); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java index 544498573..3527eb08c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java @@ -71,7 +71,7 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs { private ByteWindow window; - private final FileObjectDatabase db; + final FileObjectDatabase db; WindowCursor(FileObjectDatabase db) { this.db = db; 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 faea378e9..237b4314c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java @@ -70,6 +70,7 @@ import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectId; 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; @@ -84,7 +85,7 @@ import org.eclipse.jgit.storage.file.ObjectDirectory; import org.eclipse.jgit.storage.file.PackIndex; import org.eclipse.jgit.storage.file.PackLock; -import org.eclipse.jgit.storage.file.UnpackedObjectLoader; +import org.eclipse.jgit.storage.file.UnpackedObject; import org.eclipse.jgit.treewalk.TreeWalk; /** @@ -598,9 +599,9 @@ private boolean downloadLooseObject(final AnyObjectId id, private void verifyAndInsertLooseObject(final AnyObjectId id, final byte[] compressed) throws IOException { - final UnpackedObjectLoader uol; + 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. From 13e0218a2577debcfe8757e135af1cf900601814 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 1 Jul 2010 18:27:51 -0700 Subject: [PATCH 072/103] Replace PackedObjectLoader with ObjectLoader.SmallObject The class is identical, but ObjectLoader.SmallObject is part of our public API for storage implementations to build on top of. Change-Id: I381a3953b14870b6d3d74a9c295769ace78869dc Signed-off-by: Shawn O. Pearce --- .../jgit/storage/file/T0004_PackReader.java | 3 +- .../jgit/storage/file/WindowCacheGetTest.java | 1 - .../jgit/storage/file/ObjectDirectory.java | 2 +- .../eclipse/jgit/storage/file/PackFile.java | 22 ++--- .../jgit/storage/file/PackedObjectLoader.java | 89 ------------------- 5 files changed, 9 insertions(+), 108 deletions(-) delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java index b8bcca2e1..472d6956e 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0004_PackReader.java @@ -63,7 +63,7 @@ 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); @@ -81,7 +81,6 @@ public void test004_lookupDeltifiedObject() throws IOException { id = ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"); or = db.open(id); assertNotNull(or); - assertTrue(or instanceof PackedObjectLoader); assertEquals(Constants.OBJ_BLOB, or.getType()); assertEquals(18009, or.getSize()); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java index 177a1d5cf..d8c682999 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/WindowCacheGetTest.java @@ -128,7 +128,6 @@ private void doCacheTests() throws IOException { for (final TestObject o : toLoad) { final ObjectLoader or = db.open(o.id, o.type); assertNotNull(or); - assertTrue(or instanceof PackedObjectLoader); assertEquals(o.type, or.getType()); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java index 49a69a536..0ddcf04dd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java @@ -273,7 +273,7 @@ ObjectLoader openObject1(final WindowCursor curs, SEARCH: for (;;) { for (final PackFile p : pList.packs) { try { - final PackedObjectLoader ldr = p.get(curs, objectId); + final ObjectLoader ldr = p.get(curs, objectId); if (ldr != null) return ldr; } catch (PackMismatchException e) { 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 index f68019c40..9f642042c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java @@ -69,6 +69,7 @@ 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; @@ -167,15 +168,6 @@ else if (!Arrays.equals(packChecksum, idx.packChecksum)) return loadedIdx; } - final PackedObjectLoader resolveBase(final WindowCursor curs, final long ofs) - throws IOException { - if (isCorrupt(ofs)) { - throw new CorruptObjectException(MessageFormat.format(JGitText - .get().objectAtHasBadZlibStream, ofs, getPackFile())); - } - return load(curs, ofs); - } - /** @return the File object which locates this pack on disk. */ public File getPackFile() { return packFile; @@ -211,7 +203,7 @@ public boolean hasObject(final AnyObjectId id) throws IOException { * @throws IOException * the pack file or the index could not be read. */ - PackedObjectLoader get(final WindowCursor curs, final AnyObjectId id) + 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; @@ -619,7 +611,7 @@ private void onOpenPack() throws IOException { , getPackFile())); } - private PackedObjectLoader load(final WindowCursor curs, final long pos) + private ObjectLoader load(final WindowCursor curs, final long pos) throws IOException { final byte[] ib = curs.tempId; readFully(pos, ib, 0, 20, curs); @@ -641,7 +633,7 @@ private PackedObjectLoader load(final WindowCursor curs, final long pos) case Constants.OBJ_BLOB: case Constants.OBJ_TAG: { byte[] data = decompress(pos + p, sz, curs); - return new PackedObjectLoader(type, data); + return new ObjectLoader.SmallObject(type, data); } case Constants.OBJ_OFS_DELTA: { @@ -685,7 +677,7 @@ private long findDeltaBase(ObjectId baseId) throws IOException, return ofs; } - private PackedObjectLoader loadDelta(final long posData, long sz, + private ObjectLoader loadDelta(final long posData, long sz, final long posBase, final WindowCursor curs) throws IOException, DataFormatException { byte[] data; @@ -696,14 +688,14 @@ private PackedObjectLoader loadDelta(final long posData, long sz, data = e.data; type = e.type; } else { - PackedObjectLoader p = load(curs, posBase); + ObjectLoader p = load(curs, posBase); data = p.getCachedBytes(); type = p.getType(); saveCache(posBase, data, type); } data = BinaryDelta.apply(data, decompress(posData, sz, curs)); - return new PackedObjectLoader(type, data); + return new ObjectLoader.SmallObject(type, data); } LocalObjectRepresentation representation(final WindowCursor curs, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java deleted file mode 100644 index f056c7413..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackedObjectLoader.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2009, Google Inc. - * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.storage.file; - -import java.io.IOException; - -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.lib.ObjectLoader; -import org.eclipse.jgit.lib.ObjectStream; - -/** Object loaded in from a {@link PackFile}. */ -final class PackedObjectLoader extends ObjectLoader { - private final int type; - - private final byte[] data; - - PackedObjectLoader(int type, byte[] data) { - this.type = type; - this.data = data; - } - - public final int getType() { - return type; - } - - public final long getSize() { - return getCachedBytes().length; - } - - @Override - public final byte[] getCachedBytes() { - return data; - } - - @Override - public final boolean isLarge() { - return false; - } - - @Override - public final ObjectStream openStream() throws MissingObjectException, - IOException { - return new ObjectStream.SmallStream(this); - } -} From ded8f6c72150253c86b9586025da3a3997ccad6c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 1 Jul 2010 19:34:21 -0700 Subject: [PATCH 073/103] Support large whole packed objects as streams Similar to the loose object support, whole packed objects can now be streamed back to the caller. The streaming is less efficient as we copy the data from the cached window array into the InflaterInputStream's internal buffer, then inflate it there before returning to the application. Like with unpacked objects, there is plenty of room for some optimization, especially for the copyTo method, where we don't necessarily need so much buffering to exist. Change-Id: Ie23be81289e37e24b91d17b0891e47b9da988008 Signed-off-by: Shawn O. Pearce --- .../jgit/storage/file/PackFileTest.java | 137 ++++++++++++++++++ .../storage/file/LargePackedWholeObject.java | 130 +++++++++++++++++ .../eclipse/jgit/storage/file/PackFile.java | 7 +- .../jgit/storage/file/PackInputStream.java | 85 +++++++++++ .../jgit/storage/file/WindowCursor.java | 2 +- 5 files changed, 358 insertions(+), 3 deletions(-) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackFileTest.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedWholeObject.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackInputStream.java 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 000000000..55459ac26 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackFileTest.java @@ -0,0 +1,137 @@ +/* + * 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.util.Arrays; + +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.ObjectLoader; +import org.eclipse.jgit.lib.ObjectStream; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.util.IO; + +public class PackFileTest extends LocalDiskRepositoryTestCase { + private TestRng rng; + + private FileRepository repo; + + private TestRepository tr; + + private WindowCursor wc; + + protected void setUp() throws Exception { + super.setUp(); + rng = new TestRng(getName()); + repo = createBareRepository(); + tr = new TestRepository(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(UnpackedObject.LARGE_OBJECT + 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(); + } +} 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 000000000..9f5b804ce --- /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/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java index 9f642042c..f955c8af9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java @@ -632,8 +632,11 @@ private ObjectLoader load(final WindowCursor curs, final long pos) case Constants.OBJ_TREE: case Constants.OBJ_BLOB: case Constants.OBJ_TAG: { - byte[] data = decompress(pos + p, sz, curs); - return new ObjectLoader.SmallObject(type, data); + if (sz < UnpackedObject.LARGE_OBJECT) { + 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: { 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 000000000..5425eedbe --- /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/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java index 3527eb08c..7ede751d0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java @@ -200,7 +200,7 @@ private void prepareInflater() { 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)) { From ad68553be4417ec7ac636c3d823fdddced46ecfb Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 2 Jul 2010 02:19:12 -0700 Subject: [PATCH 074/103] Support large delta packed objects as streams Very large delta instruction streams, or deltas which use very large base objects, are now streamed through as large objects rather than being inflated into a byte array. This isn't the most efficient way to access delta encoded content, as we may need to rewind and reprocess the base object when there was a block moved within the file, but it will at least prevent the JVM from having its heap explode. When streaming a delta we have an inflater open for each level in the delta chain, to inflate the instruction set of the delta, as well as an inflater for the base level object. The base object is buffered, as is the top level delta requested by the application, but we do not buffer the intermediate delta streams. This keeps memory usage lower, so its closer to 1024 bytes per level in the chain, without having an adverse impact on raw throughput as the top-level buffer gets pushed down to the lowest stream that has the next region. Delta instructions transparently collapse here, if the top level does not copy a region from its base, the base won't materialize that part from its own base, etc. This allows us to avoid copying around a lot of segments which have been deleted from the final version. Change-Id: I724d45245cebb4bad2deeae7b896fc55b2dd49b3 Signed-off-by: Shawn O. Pearce --- .../jgit/storage/file/PackFileTest.java | 250 +++++++++++++ .../jgit/storage/pack/DeltaStreamTest.java | 273 ++++++++++++++ .../jgit/storage/file/ByteArrayWindow.java | 17 +- .../jgit/storage/file/ByteBufferWindow.java | 20 +- .../eclipse/jgit/storage/file/ByteWindow.java | 57 +-- .../storage/file/LargePackedDeltaObject.java | 153 ++++++++ .../eclipse/jgit/storage/file/PackFile.java | 97 ++++- .../jgit/storage/file/WindowCursor.java | 25 +- .../jgit/storage/pack/BinaryDelta.java | 45 +++ .../jgit/storage/pack/DeltaEncoder.java | 202 +++++++++++ .../jgit/storage/pack/DeltaStream.java | 341 ++++++++++++++++++ 11 files changed, 1387 insertions(+), 93 deletions(-) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/pack/DeltaStreamTest.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedDeltaObject.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaEncoder.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaStream.java 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 index 55459ac26..1b6e3bff9 100644 --- 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 @@ -43,17 +43,29 @@ 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; @@ -134,4 +146,242 @@ public void testWhole_LargeObject() throws Exception { 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[UnpackedObject.LARGE_OBJECT + 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(UnpackedObject.LARGE_OBJECT + 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 > UnpackedObject.LARGE_OBJECT); + + 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/storage/pack/DeltaStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/pack/DeltaStreamTest.java new file mode 100644 index 000000000..9b34ad5e0 --- /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/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java index 840244b77..0e85c58dc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java @@ -70,18 +70,11 @@ protected int copy(final int p, final byte[] b, final int o, int n) { } @Override - protected int inflate(final int pos, final byte[] b, int o, - final Inflater inf) throws DataFormatException { - while (!inf.finished()) { - if (inf.needsInput()) { - inf.setInput(array, pos, array.length - pos); - break; - } - o += inf.inflate(b, o, b.length - o); - } - while (!inf.finished() && !inf.needsInput()) - o += inf.inflate(b, o, b.length - o); - return o; + protected int inflate(final int pos, final Inflater inf) + throws DataFormatException { + int n = array.length - pos; + inf.setInput(array, pos, n); + return n; } void crc32(CRC32 out, long pos, int cnt) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java index 52bc00f35..c6308cc1d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java @@ -72,21 +72,13 @@ protected int copy(final int p, final byte[] b, final int o, int n) { } @Override - protected int inflate(final int pos, final byte[] b, int o, - final Inflater inf) throws DataFormatException { - final byte[] tmp = new byte[512]; + protected int inflate(final int pos, final Inflater inf) + throws DataFormatException { 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; + 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/storage/file/ByteWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java index 5c77cff01..e95dfd30f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java @@ -117,59 +117,10 @@ final int copy(long pos, byte[] dstbuf, int dstoff, int cnt) { */ protected abstract int copy(int pos, byte[] dstbuf, int dstoff, int cnt); - /** - * Pump bytes into the supplied inflater as input. - * - * @param pos - * offset within the file to start supplying input from. - * @param dstbuf - * destination buffer the inflater should output decompressed - * data to. - * @param dstoff - * current offset within dstbuf to inflate into. - * @param inf - * the inflater to feed input to. The caller is responsible for - * initializing the inflater as multiple windows may need to - * supply data to the same inflater to completely decompress - * something. - * @return updated dstoff based on the number of bytes - * successfully copied into dstbuf by - * inf. If the inflater is not yet finished then - * another window's data must still be supplied as input to finish - * decompression. - * @throws DataFormatException - * the inflater encountered an invalid chunk of data. Data - * stream corruption is likely. - */ - final int inflate(long pos, byte[] dstbuf, int dstoff, Inflater inf) - throws DataFormatException { - return inflate((int) (pos - start), dstbuf, dstoff, inf); + final int inflate(long pos, Inflater inf) throws DataFormatException { + return inflate((int) (pos - start), inf); } - /** - * Pump bytes into the supplied inflater as input. - * - * @param pos - * offset within the window to start supplying input from. - * @param dstbuf - * destination buffer the inflater should output decompressed - * data to. - * @param dstoff - * current offset within dstbuf to inflate into. - * @param inf - * the inflater to feed input to. The caller is responsible for - * initializing the inflater as multiple windows may need to - * supply data to the same inflater to completely decompress - * something. - * @return updated dstoff based on the number of bytes - * successfully copied into dstbuf by - * inf. If the inflater is not yet finished then - * another window's data must still be supplied as input to finish - * decompression. - * @throws DataFormatException - * the inflater encountered an invalid chunk of data. Data - * stream corruption is likely. - */ - protected abstract int inflate(int pos, byte[] dstbuf, int dstoff, - Inflater inf) throws DataFormatException; + protected abstract int inflate(int pos, Inflater inf) + throws DataFormatException; } 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 000000000..f46f988bc --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedDeltaObject.java @@ -0,0 +1,153 @@ +/* + * 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.IncorrectObjectTypeException; +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; +import org.eclipse.jgit.storage.pack.DeltaStream; + +class LargePackedDeltaObject extends ObjectLoader { + private final int type; + + private final long size; + + private final long objectOffset; + + private final long baseOffset; + + private final int headerLength; + + private final PackFile pack; + + private final FileObjectDatabase db; + + LargePackedDeltaObject(int type, long size, long objectOffset, + long baseOffset, int headerLength, PackFile pack, + FileObjectDatabase db) { + this.type = type; + this.size = size; + this.objectOffset = objectOffset; + this.baseOffset = baseOffset; + this.headerLength = headerLength; + this.pack = pack; + this.db = db; + } + + @Override + public int getType() { + return type; + } + + @Override + public long getSize() { + return size; + } + + @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(type, 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(), type).openStream(); + } + delta = new InflaterInputStream(delta); + + final ObjectLoader base = pack.load(wc, baseOffset); + return new DeltaStream(delta) { + @Override + protected InputStream openBase() throws IOException { + if (base instanceof LargePackedDeltaObject) + return ((LargePackedDeltaObject) base).open(wc); + return base.openStream(); + } + + @Override + protected long getBaseSize() throws IOException { + return base.getSize(); + } + }; + } + + private ObjectId getObjectId() throws IOException { + return pack.findObjectForOffset(objectOffset); + } +} 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 index f955c8af9..3fa1c1eeb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java @@ -611,7 +611,7 @@ private void onOpenPack() throws IOException { , getPackFile())); } - private ObjectLoader load(final WindowCursor curs, final long pos) + ObjectLoader load(final WindowCursor curs, final long pos) throws IOException { final byte[] ib = curs.tempId; readFully(pos, ib, 0, 20, curs); @@ -648,13 +648,13 @@ private ObjectLoader load(final WindowCursor curs, final long pos) ofs <<= 7; ofs += (c & 127); } - return loadDelta(pos + p, sz, pos - ofs, curs); + 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); + return loadDelta(pos, p + 20, sz, ofs, curs); } default: @@ -680,9 +680,20 @@ private long findDeltaBase(ObjectId baseId) throws IOException, return ofs; } - private ObjectLoader loadDelta(final long posData, long sz, - final long posBase, final WindowCursor curs) throws IOException, + private ObjectLoader loadDelta(long posSelf, int hdrLen, long sz, + long posBase, WindowCursor curs) throws IOException, DataFormatException { + if (UnpackedObject.LARGE_OBJECT <= 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. + // + byte[] hdr = getDeltaHeader(posSelf + hdrLen, curs); + return new LargePackedDeltaObject(getObjectType(curs, posBase), // + BinaryDelta.getResultSize(hdr), // + posSelf, posBase, hdrLen, this, curs.db); + } + byte[] data; int type; @@ -692,15 +703,89 @@ private ObjectLoader loadDelta(final long posData, long sz, 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. + // + byte[] hdr = getDeltaHeader(posSelf + hdrLen, curs); + return new LargePackedDeltaObject(getObjectType(curs, posBase), + BinaryDelta.getResultSize(hdr), // + posSelf, posBase, hdrLen, this, curs.db); + } data = p.getCachedBytes(); type = p.getType(); saveCache(posBase, data, type); } - data = BinaryDelta.apply(data, decompress(posData, sz, curs)); + // 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); } + private byte[] getDeltaHeader(long pos, WindowCursor wc) + 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; + } + + private 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)); + } + } + } + LocalObjectRepresentation representation(final WindowCursor curs, final AnyObjectId objectId) throws IOException { final long pos = idx().findOffset(objectId); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java index 7ede751d0..e2fecca78 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java @@ -147,7 +147,7 @@ int copy(final PackFile pack, long position, final byte[] dstbuf, } /** - * 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. @@ -170,13 +170,22 @@ int copy(final PackFile pack, long position, final byte[] dstbuf, 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.inflate(position, inf); + do { + int n = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff); + if (n == 0) { + if (inf.needsInput()) { + pin(pack, position); + position += window.inflate(position, inf); + } else if (inf.finished()) + return dstoff; + else + throw new DataFormatException(); + } + dstoff += n; + } while (dstoff < dstbuf.length); + return dstoff; } ByteArrayWindow quickCopy(PackFile p, long pos, long cnt) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/BinaryDelta.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/BinaryDelta.java index 027ffd62a..494623df2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/BinaryDelta.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/BinaryDelta.java @@ -55,6 +55,51 @@ *

    */ 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 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 000000000..7d4f62fc1 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaEncoder.java @@ -0,0 +1,202 @@ +/* + * 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 { + private static final int MAX_COPY = (0xff << 16) + (0xff << 8) + 0xff; + + private final OutputStream out; + + private final byte[] buf = new byte[16]; + + private int size; + + /** + * Create an encoder. + * + * @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 = out; + 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); + out.write(buf, 0, p); + size += 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. + * @throws IOException + * the instruction buffer can't store the instructions. + */ + public void insert(String text) throws IOException { + insert(Constants.encode(text)); + } + + /** + * Insert a literal binary sequence. + * + * @param text + * the binary to insert. + * @throws IOException + * the instruction buffer can't store the instructions. + */ + public void insert(byte[] text) throws IOException { + 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. + * @throws IOException + * the instruction buffer can't store the instructions. + */ + public void insert(byte[] text, int off, int cnt) throws IOException { + while (0 < cnt) { + int n = Math.min(127, cnt); + out.write((byte) n); + out.write(text, off, n); + off += n; + cnt -= n; + size += 1 + n; + } + } + + /** + * 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. + * @throws IOException + * the instruction buffer cannot store the instructions. + */ + public void copy(long offset, int cnt) throws IOException { + if (cnt > MAX_COPY) { + copy(offset, MAX_COPY); + offset += MAX_COPY; + cnt -= MAX_COPY; + } + + int cmd = 0x80; + int p = 1; + + 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 != 0x10000) { + 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[0] = (byte) cmd; + out.write(buf, 0, p); + size += p; + } +} \ 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 000000000..6f479eb90 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaStream.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * 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. + *

    + * 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. + *

    + * 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; + } + } +} From 113577617bc4b2b31f2a44290cc607431c8357a7 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 2 Jul 2010 12:41:39 -0700 Subject: [PATCH 075/103] Use core.streamFileThreshold to set our streaming limit We default this to 1 MiB for now, but we allow users to modify it through the Repository's configuration file to be a different value. A new repository listener is used to identify when the setting has been updated and trigger a reconfiguration of any active ObjectReaders. To prevent a horrible explosion we cap core.streamFileThreshold at no more than 1/4 of the maximum JVM heap size. We do this because we need at least 2 byte arrays equal in size to the stream threshold for the worst case delta inflation scenario, and our host application probably also needs some amount of the heap for their working set size. Change-Id: I103b3a541dc970bbf1a6d92917a12c5a1ee34d6c Signed-off-by: Shawn O. Pearce --- .../jgit/storage/file/PackFileTest.java | 8 +-- .../jgit/storage/file/UnpackedObjectTest.java | 6 +- .../jgit/events/ConfigChangedEvent.java | 57 +++++++++++++++++++ .../jgit/events/ConfigChangedListener.java | 55 ++++++++++++++++++ .../org/eclipse/jgit/events/ListenerList.java | 12 ++++ .../src/org/eclipse/jgit/lib/CoreConfig.java | 14 +++++ .../org/eclipse/jgit/lib/ObjectLoader.java | 15 ++++- .../storage/file/CachedObjectDirectory.java | 5 ++ .../jgit/storage/file/FileObjectDatabase.java | 2 + .../jgit/storage/file/FileRepository.java | 1 + .../storage/file/LargePackedDeltaObject.java | 5 ++ .../jgit/storage/file/ObjectDirectory.java | 20 ++++++- .../eclipse/jgit/storage/file/PackFile.java | 4 +- .../jgit/storage/file/UnpackedObject.java | 6 +- .../jgit/storage/file/WindowCursor.java | 6 ++ 15 files changed, 200 insertions(+), 16 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedEvent.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedListener.java 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 index 1b6e3bff9..1a40b8e79 100644 --- 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 @@ -118,7 +118,7 @@ public void testWhole_SmallObject() throws Exception { public void testWhole_LargeObject() throws Exception { final int type = Constants.OBJ_BLOB; - byte[] data = rng.nextBytes(UnpackedObject.LARGE_OBJECT + 5); + byte[] data = rng.nextBytes(ObjectLoader.STREAM_THRESHOLD + 5); RevBlob id = tr.blob(data); tr.branch("master").commit().add("A", id).create(); tr.packAndPrune(); @@ -209,7 +209,7 @@ public void testDelta_SmallObjectChain() throws Exception { public void testDelta_LargeObjectChain() throws Exception { ObjectInserter.Formatter fmt = new ObjectInserter.Formatter(); - byte[] data0 = new byte[UnpackedObject.LARGE_OBJECT + 5]; + byte[] data0 = new byte[ObjectLoader.STREAM_THRESHOLD + 5]; Arrays.fill(data0, (byte) 0xf3); ObjectId id0 = fmt.idFor(Constants.OBJ_BLOB, data0); @@ -277,12 +277,12 @@ public void testDelta_LargeInstructionStream() throws Exception { Arrays.fill(data0, (byte) 0xf3); ObjectId id0 = fmt.idFor(Constants.OBJ_BLOB, data0); - byte[] data3 = rng.nextBytes(UnpackedObject.LARGE_OBJECT + 5); + 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 > UnpackedObject.LARGE_OBJECT); + assertTrue(delta3.length > ObjectLoader.STREAM_THRESHOLD); TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64 * 1024); packHeader(pack, 2); 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 index 4ca64d3eb..d77a96220 100644 --- 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 @@ -113,7 +113,7 @@ public void testStandardFormat_SmallObject() throws Exception { public void testStandardFormat_LargeObject() throws Exception { final int type = Constants.OBJ_BLOB; - byte[] data = rng.nextBytes(UnpackedObject.LARGE_OBJECT + 5); + byte[] data = rng.nextBytes(ObjectLoader.STREAM_THRESHOLD + 5); ObjectId id = new ObjectInserter.Formatter().idFor(type, data); write(id, compressStandardFormat(type, data)); @@ -230,7 +230,7 @@ public void testStandardFormat_SmallObject_CorruptZLibStream() public void testStandardFormat_LargeObject_CorruptZLibStream() throws Exception { final int type = Constants.OBJ_BLOB; - byte[] data = rng.nextBytes(UnpackedObject.LARGE_OBJECT + 5); + 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; @@ -290,7 +290,7 @@ public void testPackFormat_SmallObject() throws Exception { public void testPackFormat_LargeObject() throws Exception { final int type = Constants.OBJ_BLOB; - byte[] data = rng.nextBytes(UnpackedObject.LARGE_OBJECT + 5); + byte[] data = rng.nextBytes(ObjectLoader.STREAM_THRESHOLD + 5); ObjectId id = new ObjectInserter.Formatter().idFor(type, data); write(id, compressPackFormat(type, data)); 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 000000000..79598eacb --- /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 { + @Override + public Class getListenerType() { + return ConfigChangedListener.class; + } + + @Override + public void dispatch(ConfigChangedListener listener) { + listener.onConfigChanged(this); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedListener.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedListener.java new file mode 100644 index 000000000..322cf7f6d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedListener.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.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/ListenerList.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java index 24e2d4da0..6ac4b0f8b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java @@ -74,6 +74,18 @@ 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. * 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 ab024c790..5ad7910be 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,10 +68,18 @@ public CoreConfig parse(final Config cfg) { 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; } /** @@ -94,4 +103,9 @@ public int getPackIndexVersion() { 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/ObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java index 312fa958c..e19bfc4fb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java @@ -59,7 +59,13 @@ * New loaders are constructed for every object. */ public abstract class ObjectLoader { - private static final int LARGE_OBJECT = 1024 * 1024; + /** + * Default setting for the large object threshold. + *

    + * 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}. @@ -77,7 +83,12 @@ public abstract class ObjectLoader { * {@link #openStream()} to prevent overflowing the JVM heap. */ public boolean isLarge() { - return LARGE_OBJECT <= getSize(); + try { + getCachedBytes(); + return false; + } catch (LargeObjectException tooBig) { + return true; + } } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java index df62342d8..505850c42 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java @@ -191,4 +191,9 @@ void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, WindowCursor curs) throws IOException { wrapped.selectObjectRepresentation(packer, otp, curs); } + + @Override + int getStreamFileThreshold() { + return wrapped.getStreamFileThreshold(); + } } 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 index 021823488..444fd809b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java @@ -187,6 +187,8 @@ abstract ObjectLoader openObject2(WindowCursor curs, String objectName, abstract FileObjectDatabase newCachedFileObjectDatabase(); + abstract int getStreamFileThreshold(); + static class AlternateHandle { final FileObjectDatabase db; 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 index c86549b2e..15aafbdae 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileRepository.java @@ -146,6 +146,7 @@ public FileRepository(final BaseRepositoryBuilder options) throws IOException { options.getObjectDirectory(), // options.getAlternateObjectDirectories(), // getFS()); + getListenerList().addConfigChangedListener(objectDatabase); if (objectDatabase.exists()) { final String repositoryFormatVersion = getConfig().getString( 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 index f46f988bc..a6d7d8946 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedDeltaObject.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedDeltaObject.java @@ -93,6 +93,11 @@ public long getSize() { return size; } + @Override + public boolean isLarge() { + return true; + } + @Override public byte[] getCachedBytes() throws LargeObjectException { try { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java index 0ddcf04dd..8177155f4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java @@ -63,8 +63,11 @@ 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; @@ -93,7 +96,8 @@ * searched (recursively through all alternates) before the slow half is * considered. */ -public class ObjectDirectory extends FileObjectDatabase { +public class ObjectDirectory extends FileObjectDatabase implements + ConfigChangedListener { private static final PackList NO_PACKS = new PackList(-1, -1, new PackFile[0]); private final Config config; @@ -112,6 +116,8 @@ public class ObjectDirectory extends FileObjectDatabase { private final AtomicReference alternates; + private int streamFileThreshold; + /** * Initialize a reference to an on-disk object directory. * @@ -146,6 +152,13 @@ public ObjectDirectory(final Config cfg, final File dir, 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(); } /** @@ -632,4 +645,9 @@ public ObjectDatabase newCachedDatabase() { FileObjectDatabase newCachedFileObjectDatabase() { return new CachedObjectDirectory(this); } + + @Override + int getStreamFileThreshold() { + return streamFileThreshold; + } } 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 index 3fa1c1eeb..ec68b8600 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java @@ -632,7 +632,7 @@ ObjectLoader load(final WindowCursor curs, final long pos) case Constants.OBJ_TREE: case Constants.OBJ_BLOB: case Constants.OBJ_TAG: { - if (sz < UnpackedObject.LARGE_OBJECT) { + if (sz < curs.getStreamFileThreshold()) { byte[] data = decompress(pos + p, sz, curs); return new ObjectLoader.SmallObject(type, data); } @@ -683,7 +683,7 @@ private long findDeltaBase(ObjectId baseId) throws IOException, private ObjectLoader loadDelta(long posSelf, int hdrLen, long sz, long posBase, WindowCursor curs) throws IOException, DataFormatException { - if (UnpackedObject.LARGE_OBJECT <= sz) { + 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. 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 index 9072fcd2d..55ee3b78b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java @@ -76,8 +76,6 @@ public class UnpackedObject { private static final int BUFFER_SIZE = 8192; - static final int LARGE_OBJECT = 1024 * 1024; - /** * Parse an object from the unpacked object format. * @@ -127,7 +125,7 @@ static ObjectLoader open(InputStream in, File path, AnyObjectId id, JGitText.get().corruptObjectGarbageAfterSize); if (path == null && Integer.MAX_VALUE < size) throw new LargeObjectException(id.copy()); - if (size < LARGE_OBJECT || path == null) { + if (size < wc.getStreamFileThreshold() || path == null) { byte[] data = new byte[(int) size]; int n = avail - p.value; if (n > 0) @@ -164,7 +162,7 @@ static ObjectLoader open(InputStream in, File path, AnyObjectId id, if (path == null && Integer.MAX_VALUE < size) throw new LargeObjectException(id.copy()); - if (size < LARGE_OBJECT || path == null) { + if (size < wc.getStreamFileThreshold() || path == null) { in.reset(); IO.skipFully(in, p); in = inflate(in, wc); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java index e2fecca78..33ca006e7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java @@ -223,6 +223,12 @@ void pin(final PackFile pack, final long position) } } + int getStreamFileThreshold() { + if (db == null) + return ObjectLoader.STREAM_THRESHOLD; + return db.getStreamFileThreshold(); + } + /** Release the current window cursor. */ public void release() { window = null; From e4a480f658c165cb3d4a2ba4ed2ba9b3de4b1bf2 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 2 Jul 2010 13:12:06 -0700 Subject: [PATCH 076/103] Make type and size lazy for large delta objects Callers don't necessarily need the getSize() result from a large delta. They instead should be always using openStream() or copyTo() for blobs going to local files, or they should be checking the result of the constant-time isLarge() method to determine the type of access they can use on the ObjectLoader. Avoid inflating the delta instruction stream twice by delaying the decoding of the size until after we have created the DeltaStream and decoded the header. Likewise with the type, callers don't necessarily always need it to be present in an ObjectLoader. Delay looking at it as late as we can, thereby avoiding an ugly O(N^2) loop looking up the type for every single object in the entire delta chain. Change-Id: I6487b75b52a5d201d811a8baed2fb4fcd6431320 Signed-off-by: Shawn O. Pearce --- .../storage/file/LargePackedDeltaObject.java | 94 ++++++++++++++++--- .../eclipse/jgit/storage/file/PackFile.java | 17 ++-- 2 files changed, 89 insertions(+), 22 deletions(-) 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 index a6d7d8946..53a0e617f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedDeltaObject.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LargePackedDeltaObject.java @@ -46,20 +46,25 @@ 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 final int type; + private static final long SIZE_UNKNOWN = -1; - private final long size; + private int type; + + private long size; private final long objectOffset; @@ -71,11 +76,11 @@ class LargePackedDeltaObject extends ObjectLoader { private final FileObjectDatabase db; - LargePackedDeltaObject(int type, long size, long objectOffset, + LargePackedDeltaObject(long objectOffset, long baseOffset, int headerLength, PackFile pack, FileObjectDatabase db) { - this.type = type; - this.size = size; + this.type = Constants.OBJ_BAD; + this.size = SIZE_UNKNOWN; this.objectOffset = objectOffset; this.baseOffset = baseOffset; this.headerLength = headerLength; @@ -85,11 +90,58 @@ class LargePackedDeltaObject extends ObjectLoader { @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; } @@ -112,7 +164,7 @@ 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(type, size, in) { + return new ObjectStream.Filter(getType(), size, in) { @Override public void close() throws IOException { wc.release(); @@ -132,24 +184,44 @@ private InputStream open(final WindowCursor wc) // probably was repacked recently. Go find the object // again and open the stream from that location instead. // - return wc.open(getObjectId(), type).openStream(); + return wc.open(getObjectId()).openStream(); } delta = new InflaterInputStream(delta); final ObjectLoader base = pack.load(wc, baseOffset); - return new DeltaStream(delta) { + DeltaStream ds = new DeltaStream(delta) { + private long baseSize = SIZE_UNKNOWN; + @Override protected InputStream openBase() throws IOException { + InputStream in; if (base instanceof LargePackedDeltaObject) - return ((LargePackedDeltaObject) base).open(wc); - return base.openStream(); + 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 { - return base.getSize(); + 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 { 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 index ec68b8600..dc1fff4bc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java @@ -688,10 +688,8 @@ private ObjectLoader loadDelta(long posSelf, int hdrLen, long sz, // that implies the resulting object is going to be massive. // Use only the large delta format here. // - byte[] hdr = getDeltaHeader(posSelf + hdrLen, curs); - return new LargePackedDeltaObject(getObjectType(curs, posBase), // - BinaryDelta.getResultSize(hdr), // - posSelf, posBase, hdrLen, this, curs.db); + return new LargePackedDeltaObject(posSelf, posBase, hdrLen, // + this, curs.db); } byte[] data; @@ -707,10 +705,8 @@ private ObjectLoader loadDelta(long posSelf, int hdrLen, long sz, // The base itself is large. We have to produce a large // delta stream as we don't want to build the whole base. // - byte[] hdr = getDeltaHeader(posSelf + hdrLen, curs); - return new LargePackedDeltaObject(getObjectType(curs, posBase), - BinaryDelta.getResultSize(hdr), // - posSelf, posBase, hdrLen, this, curs.db); + return new LargePackedDeltaObject(posSelf, posBase, hdrLen, + this, curs.db); } data = p.getCachedBytes(); type = p.getType(); @@ -727,7 +723,7 @@ private ObjectLoader loadDelta(long posSelf, int hdrLen, long sz, return new ObjectLoader.SmallObject(type, data); } - private byte[] getDeltaHeader(long pos, WindowCursor wc) + 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, @@ -739,8 +735,7 @@ private byte[] getDeltaHeader(long pos, WindowCursor wc) return hdr; } - private int getObjectType(final WindowCursor curs, long pos) - throws IOException { + int getObjectType(final WindowCursor curs, long pos) throws IOException { final byte[] ib = curs.tempId; for (;;) { readFully(pos, ib, 0, 20, curs); From 412ca65bd57f6ac3e86aba0f01533f0e1a5fd321 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 2 Jul 2010 15:05:32 -0700 Subject: [PATCH 077/103] Avoid unbounded getCachedBytes during parseAny Since we don't know the type of object we are parsing, we don't know if its a massive blob, or some small commit or annotated tag. Avoid pulling the cached bytes until we have checked the type and decided if we actually need them to continue parsing right now. This way large blobs which won't fit in memory and would throw a LargeObjectException don't abort parsing. Change-Id: Ifb70df5d1c59f616aa20ee88898cb69524541636 Signed-off-by: Shawn O. Pearce --- org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 51de7c4a5..1236ac648 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java @@ -746,12 +746,11 @@ public RevObject parseAny(final AnyObjectId id) RevObject r = objects.get(id); if (r == null) { final ObjectLoader ldr = reader.open(id); - final byte[] data = ldr.getCachedBytes(); 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; } @@ -767,7 +766,7 @@ public RevObject parseAny(final AnyObjectId id) } case Constants.OBJ_TAG: { final RevTag t = new RevTag(id); - t.parseCanonical(this, data); + t.parseCanonical(this, ldr.getCachedBytes()); r = t; break; } From fe9860a4441d606eb6d56da24197eb1797f275dc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 2 Jul 2010 16:56:31 -0700 Subject: [PATCH 078/103] Remove pointless size test in PackFile decompress Now that any large objects are forced through a streaming loader when its bigger than getStreamFileThreshold(), and that threshold is pegged at Integer.MAX_VALUE as its largest size, we will never be able to reach this code path where we threw OutOfMemoryError. Robin pointed out that we probably should include a message here, but the code is effectively unreachable, so there isn't any value in adding a message at this point. So remove it. Change-Id: Ie611d005622e38a75537f1350246df0ab89dd500 Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/storage/file/PackFile.java | 2 -- 1 file changed, 2 deletions(-) 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 index dc1fff4bc..40bb07100 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java @@ -277,8 +277,6 @@ private final void saveCache(final long position, final byte[] data, final int t private final byte[] decompress(final long position, final long totalSize, final WindowCursor curs) throws IOException, DataFormatException { - if (totalSize > Integer.MAX_VALUE) - throw new OutOfMemoryError(); final byte[] dstbuf = new byte[(int) totalSize]; if (curs.inflate(this, position, dstbuf, 0) != totalSize) throw new EOFException(MessageFormat.format(JGitText.get().shortCompressedStreamAt, position)); From 08d349a27bbff4b0d3ee67eb6ddd4818e070c86e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 2 Jul 2010 17:01:40 -0700 Subject: [PATCH 079/103] amend commit: Refactor repository construction to builder class During code review, Alex raised a few comments about commit 532421d98925 ("Refactor repository construction to builder class"). Due to the size of the related series we aren't going to go back and rebase in something this minor, so resolve them as a follow-up commit instead. Change-Id: Ied52f7a8f7252743353c58d20bfc3ec498933e00 Signed-off-by: Shawn O. Pearce --- org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java | 6 +++--- .../tst/org/eclipse/jgit/storage/file/T0003_Basic.java | 3 ++- .../resources/org/eclipse/jgit/JGitText.properties | 2 +- org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java | 2 +- .../src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) 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 2da210d7e..ab11062cc 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 @@ -163,17 +163,17 @@ private void execute(final String[] argv) throws Exception { final TextBuiltin cmd = subcommand; if (cmd.requiresRepository()) { - RepositoryBuilder frb = new RepositoryBuilder() // + RepositoryBuilder rb = new RepositoryBuilder() // .setGitDir(gitdir) // .readEnvironment() // .findGitDir(); - if (frb.getGitDir() == null) { + if (rb.getGitDir() == null) { writer.println(CLIText.get().cantFindGitDirectory); writer.flush(); System.exit(1); } - cmd.init(frb.build(), null); + cmd.init(rb.build(), null); } else { cmd.init(null, gitdir); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0003_Basic.java index c9013a6a0..477b0dfb5 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0003_Basic.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/T0003_Basic.java @@ -53,6 +53,7 @@ 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; @@ -99,7 +100,7 @@ public void test000_openRepoBadArgs() throws IOException { 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()); } } diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties index aece51874..999a11a69 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties @@ -125,7 +125,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 diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java index 301e411ee..707b39102 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java @@ -185,7 +185,7 @@ public static JGitText get() { /***/ 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; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java index 90929e721..92edb0325 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java @@ -467,7 +467,7 @@ public R build() throws IOException { protected void requireGitDirOrWorkTree() { if (getGitDir() == null && getWorkTree() == null) throw new IllegalArgumentException( - JGitText.get().eitherGIT_DIRorGIT_WORK_TREEmustBePassed); + JGitText.get().eitherGitDirOrWorkTreeRequired); } /** From 4dd7b35b26db0b017113f37138b55a814a6df6fd Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 2 Jul 2010 17:12:30 -0700 Subject: [PATCH 080/103] Improve description of isBare and NoWorkTreeException Alex pointed out that my description of a bare repository might be confusing for some readers. Reword the description of the error, and make it consistent throughout the Repository class's API. Change-Id: I87929ddd3005f578a7022f363270952d1f7f8664 Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/lib/Repository.java | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) 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 a60cba56f..d1e6cae6e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -161,8 +161,8 @@ public void create() throws IOException { * 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 */ @@ -863,7 +863,8 @@ public Map> getAllRefsByPeeledObjectId() { * @throws IOException * if the index can not be read * @throws NoWorkTreeException - * if this is bare (see {@link #isBare()}) + * if this is bare, which implies it has no working directory. + * See {@link #isBare()}. */ public GitIndex getIndex() throws IOException, NoWorkTreeException { if (isBare()) @@ -880,7 +881,8 @@ public GitIndex getIndex() throws IOException, NoWorkTreeException { /** * @return the index file location * @throws NoWorkTreeException - * if this is bare (see {@link #isBare()}) + * if this is bare, which implies it has no working directory. + * See {@link #isBare()}. */ public File getIndexFile() throws NoWorkTreeException { if (isBare()) @@ -898,7 +900,8 @@ public File getIndexFile() throws NoWorkTreeException { * @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 the repository is bare (lacks a working directory). + * 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 @@ -920,7 +923,8 @@ public DirCache readDirCache() throws NoWorkTreeException, * @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 the repository is bare (lacks a working directory). + * 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. @@ -1072,7 +1076,7 @@ public static String stripWorkDir(File workDir, File file) { } /** - * @return the "bare"-ness of this Repository + * @return true if this is bare, which implies it has no working directory. */ public boolean isBare() { return workTree == null; @@ -1082,7 +1086,8 @@ public boolean isBare() { * @return the root directory of the working tree, where files are checked * out for viewing and editing. * @throws NoWorkTreeException - * if the repository is bare and has no working directory. + * if this is bare, which implies it has no working directory. + * See {@link #isBare()}. */ public File getWorkTree() throws NoWorkTreeException { if (isBare()) @@ -1130,7 +1135,8 @@ public abstract ReflogReader getReflogReader(String refName) * {@code null} if this file doesn't exist * @throws IOException * @throws NoWorkTreeException - * if the repository is "bare" + * if this is bare, which implies it has no working directory. + * See {@link #isBare()}. */ public String readMergeCommitMsg() throws IOException, NoWorkTreeException { if (isBare() || getDirectory() == null) @@ -1156,7 +1162,8 @@ public String readMergeCommitMsg() throws IOException, NoWorkTreeException { * exists but is empty {@code null} will be returned * @throws IOException * @throws NoWorkTreeException - * if the repository is "bare" + * if this is bare, which implies it has no working directory. + * See {@link #isBare()}. */ public List readMergeHeads() throws IOException, NoWorkTreeException { if (isBare() || getDirectory() == null) From ab3c68c512035a87071da3269ce8810b55b52b3f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 6 Jul 2010 17:29:27 -0700 Subject: [PATCH 081/103] amend commit: Support large loose objects as streams We need to validate the stream state after the InflaterInputStream thinks the stream is done. Git expects a higher level of service from the Inflater than the InflaterInputStream usually gives, we need to ensure the embedded CRC is valid, and that there isn't trailing garbage at the end of the file. Change-Id: I1c9642a82dbd76b69e607dceccf8b85dc869a3c1 Signed-off-by: Shawn O. Pearce --- .../jgit/storage/file/UnpackedObjectTest.java | 104 ++++++++++++++++++ .../jgit/storage/file/UnpackedObject.java | 76 ++++++++++--- 2 files changed, 167 insertions(+), 13 deletions(-) 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 index d77a96220..25dfe4c23 100644 --- 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 @@ -227,6 +227,42 @@ public void testStandardFormat_SmallObject_CorruptZLibStream() } } + 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; @@ -264,6 +300,74 @@ public void testStandardFormat_LargeObject_CorruptZLibStream() } } + 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); 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 index 55ee3b78b..0325c5711 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java @@ -52,6 +52,7 @@ 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; @@ -108,8 +109,9 @@ static ObjectLoader open(InputStream in, File path, AnyObjectId id, if (isStandardFormat(hdr)) { in.reset(); - in = inflate(in, wc); - int avail = readSome(in, hdr, 0, 64); + 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); @@ -130,7 +132,8 @@ static ObjectLoader open(InputStream in, File path, AnyObjectId id, int n = avail - p.value; if (n > 0) System.arraycopy(hdr, p.value, data, 0, n); - IO.readFully(in, data, n, data.length - 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); @@ -165,9 +168,11 @@ static ObjectLoader open(InputStream in, File path, AnyObjectId id, if (size < wc.getStreamFileThreshold() || path == null) { in.reset(); IO.skipFully(in, p); - in = inflate(in, wc); + Inflater inf = wc.inflater(); + InputStream zIn = inflate(in, inf); byte[] data = new byte[(int) size]; - IO.readFully(in, data, 0, data.length); + 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); @@ -178,6 +183,40 @@ static ObjectLoader open(InputStream in, File path, AnyObjectId id, } } + 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 @@ -189,13 +228,19 @@ private static boolean isStandardFormat(final byte[] hdr) { return fb == 0x78 && (((fb << 8) | hdr[1] & 0xff) % 31) == 0; } - private static InputStream inflate(InputStream in, final ObjectId id) { + 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 { - return super.read(b, off, cnt); + int r = super.read(b, off, cnt); + if (r > 0) + remaining -= r; + return r; } catch (ZipException badStream) { throw new CorruptObjectException(id, JGitText.get().corruptObjectBadStream); @@ -204,14 +249,19 @@ public int read(byte[] b, int off, int cnt) throws IOException { @Override public void close() throws IOException { - super.close(); - InflaterCache.release(inf); + try { + if (remaining <= 0) + checkValidEndOfStream(in, inf, id, new byte[64]); + super.close(); + } finally { + InflaterCache.release(inf); + } } }; } - private static InputStream inflate(InputStream in, WindowCursor wc) { - return new InflaterInputStream(in, wc.inflater(), BUFFER_SIZE); + private static InflaterInputStream inflate(InputStream in, Inflater inf) { + return new InflaterInputStream(in, inf, BUFFER_SIZE); } private static BufferedInputStream buffer(InputStream in) { @@ -293,7 +343,7 @@ public ObjectStream openStream() throws MissingObjectException, if (isStandardFormat(hdr)) { in.reset(); - in = buffer(inflate(in, id)); + in = buffer(inflate(in, size, id)); while (0 < in.read()) continue; } else { @@ -305,7 +355,7 @@ public ObjectStream openStream() throws MissingObjectException, in.reset(); IO.skipFully(in, p); - in = buffer(inflate(in, id)); + in = buffer(inflate(in, size, id)); } ok = true; From f29741d1d86ea9baa7a37545da29be38a9d1af02 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 6 Jul 2010 19:38:39 -0700 Subject: [PATCH 082/103] amend commit: Support large delta packed objects as streams Rename the ByteWindow's inflate() method to setInput. We have completely refactored the purpose of this method to be feeding part (or all) of the window as input to the Inflater, and the actual inflate activity happens in the caller. Change-Id: Ie93a5bae0e9e637b5e822d56993ce6b562c6ad15 Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/storage/file/ByteArrayWindow.java | 2 +- .../src/org/eclipse/jgit/storage/file/ByteBufferWindow.java | 2 +- .../src/org/eclipse/jgit/storage/file/ByteWindow.java | 6 +++--- .../src/org/eclipse/jgit/storage/file/WindowCursor.java | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java index 0e85c58dc..457c8dc90 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java @@ -70,7 +70,7 @@ protected int copy(final int p, final byte[] b, final int o, int n) { } @Override - protected int inflate(final int pos, final Inflater inf) + protected int setInput(final int pos, final Inflater inf) throws DataFormatException { int n = array.length - pos; inf.setInput(array, pos, n); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java index c6308cc1d..29a015995 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java @@ -72,7 +72,7 @@ protected int copy(final int p, final byte[] b, final int o, int n) { } @Override - protected int inflate(final int pos, final Inflater inf) + protected int setInput(final int pos, final Inflater inf) throws DataFormatException { final ByteBuffer s = buffer.slice(); s.position(pos); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java index e95dfd30f..f92efb4ac 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java @@ -117,10 +117,10 @@ final int copy(long pos, byte[] dstbuf, int dstoff, int cnt) { */ protected abstract int copy(int pos, byte[] dstbuf, int dstoff, int cnt); - final int inflate(long pos, Inflater inf) throws DataFormatException { - return inflate((int) (pos - start), inf); + final int setInput(long pos, Inflater inf) throws DataFormatException { + return setInput((int) (pos - start), inf); } - protected abstract int inflate(int pos, Inflater inf) + protected abstract int setInput(int pos, Inflater inf) throws DataFormatException; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java index 33ca006e7..6f4e72a82 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java @@ -171,13 +171,13 @@ int inflate(final PackFile pack, long position, final byte[] dstbuf, int dstoff) throws IOException, DataFormatException { prepareInflater(); pin(pack, position); - position += window.inflate(position, inf); + 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.inflate(position, inf); + position += window.setInput(position, inf); } else if (inf.finished()) return dstoff; else From a215914a5638726ab4533332e97d6abe6e7e9657 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 7 Jul 2010 08:34:57 -0700 Subject: [PATCH 083/103] Fix DeltaEncoder header for objects 128 bytes long The encode loop had the wrong condition, objects that are 128 bytes in size need to have their length encoded as two bytes, not one. Change-Id: I3bef85f2b774871ba6104042b341749eb8e7595c Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/storage/pack/DeltaEncoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 7d4f62fc1..7bf0bace7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaEncoder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaEncoder.java @@ -81,7 +81,7 @@ public DeltaEncoder(OutputStream out, long baseSize, long resultSize) private void writeVarint(long sz) throws IOException { int p = 0; - while (sz > 0x80) { + while (sz >= 0x80) { buf[p++] = (byte) (0x80 | (((int) sz) & 0x7f)); sz >>>= 7; } From cd7dd8591eccd093707c98bc68743dfae4f37b80 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 7 Jul 2010 08:51:04 -0700 Subject: [PATCH 084/103] Cap delta copy instructions at 64k Although all modern delta decoders can process copy instructions with a count as large as 0xffffff (~15.9 MiB), pack version 2 streams are only supposed to use delta copy instructions up to 64 KiB. Rewrite our copy instruction encode loop to use the lower 64 KiB limit, even though modern decoders would support longer copies. To improve encoding performance we now try to encode up to four full copy commands in our buffer before we flush it to the stream, but we don't try to implement full buffering here. We are just trying to amortize the virtual method call to the destination stream when we have to do a large copy. Change-Id: I9410a16e6912faa83180a9788dc05f11e33fabae Signed-off-by: Shawn O. Pearce --- .../jgit/storage/pack/DeltaEncoder.java | 62 +++++++++++++++---- 1 file changed, 50 insertions(+), 12 deletions(-) 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 index 7bf0bace7..9254acc1b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaEncoder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaEncoder.java @@ -50,11 +50,29 @@ /** Encodes an instruction stream for {@link BinaryDelta}. */ public class DeltaEncoder { - private static final int MAX_COPY = (0xff << 16) + (0xff << 8) + 0xff; + /** + * Maximum number of bytes to be copied in pack v2 format. + *

    + * 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; private final OutputStream out; - private final byte[] buf = new byte[16]; + private final byte[] buf = new byte[MAX_COPY_CMD_SIZE * 4]; private int size; @@ -154,14 +172,35 @@ public void insert(byte[] text, int off, int cnt) throws IOException { * the instruction buffer cannot store the instructions. */ public void copy(long offset, int cnt) throws IOException { - if (cnt > MAX_COPY) { - copy(offset, MAX_COPY); - offset += MAX_COPY; - cnt -= MAX_COPY; + if (cnt == 0) + return; + + 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) { + out.write(buf, 0, p); + size += p; + p = 0; + } } + p = encodeCopy(p, offset, cnt); + out.write(buf, 0, p); + size += p; + } + + private int encodeCopy(int p, long offset, int cnt) { int cmd = 0x80; - int p = 1; + final int cmdPtr = p++; // save room for the command if ((offset & 0xff) != 0) { cmd |= 0x01; @@ -180,7 +219,7 @@ public void copy(long offset, int cnt) throws IOException { buf[p++] = (byte) ((offset >>> 24) & 0xff); } - if (cnt != 0x10000) { + if (cnt != MAX_V2_COPY) { if ((cnt & 0xff) != 0) { cmd |= 0x10; buf[p++] = (byte) (cnt & 0xff); @@ -195,8 +234,7 @@ public void copy(long offset, int cnt) throws IOException { } } - buf[0] = (byte) cmd; - out.write(buf, 0, p); - size += p; + buf[cmdPtr] = (byte) cmd; + return p; } -} \ No newline at end of file +} From 711bd3e3d035b22e41bda32a9463cef3e2dca7a7 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 7 Jul 2010 08:52:46 -0700 Subject: [PATCH 085/103] Define a constant for 127 in DeltaEncoder The special value 127 here means how many bytes we can put into a single insert command. Rather than use the magical value 127, lets name it to better document the code. Change-Id: I5a326f4380f6ac87987fa833e9477700e984a88e Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/storage/pack/DeltaEncoder.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 index 9254acc1b..9984eb1ff 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaEncoder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaEncoder.java @@ -70,6 +70,9 @@ public class DeltaEncoder { /** 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]; @@ -151,7 +154,7 @@ public void insert(byte[] text) throws IOException { */ public void insert(byte[] text, int off, int cnt) throws IOException { while (0 < cnt) { - int n = Math.min(127, cnt); + int n = Math.min(MAX_INSERT_DATA_SIZE, cnt); out.write((byte) n); out.write(text, off, n); off += n; From 97311cd3e016679178cb37b32f1d913af57e6320 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 9 Jul 2010 10:10:12 -0700 Subject: [PATCH 086/103] Allow TemporaryBuffer.Heap to allocate smaller than 8 KiB If the heap limit was set to something smaller than 8 KiB, we were still allocating the full 8 KiB block size, and accepting up to the amount we allocated by. Instead actually put a hard cap on the limit. Change-Id: Id1da26fde2102e76510b1da4ede8493928a981cc Signed-off-by: Shawn O. Pearce --- .../transport/ReceivePackRefFilterTest.java | 14 ++++---- .../eclipse/jgit/util/TemporaryBuffer.java | 33 ++++++++++++++----- 2 files changed, 32 insertions(+), 15 deletions(-) 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 0bcdbcea4..b331f9cf5 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 @@ -255,7 +255,7 @@ public void testCreateBranchAtHiddenCommitFails() throws Exception { } 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); @@ -297,13 +297,13 @@ public void testUsingHiddenCommonBlobFails() throws Exception { // 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.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' @@ -339,13 +339,13 @@ public void testUsingUnknownBlobFails() throws Exception { // 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.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' @@ -379,12 +379,12 @@ public void testUsingUnknownTreeFails() throws Exception { // 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.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/src/org/eclipse/jgit/util/TemporaryBuffer.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java index 101b6056b..baa45c5c6 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 void write(final byte[] b, int off, int len) throws IOException { 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; @@ -171,7 +171,7 @@ public void copy(final InputStream in) throws IOException { 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; @@ -192,8 +192,12 @@ public void copy(final InputStream in) throws IOException { * @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; } /** @@ -251,8 +255,13 @@ public void reset() { if (overflow != null) { destroy(); } - blocks = new ArrayList(inCoreLimit / Block.SZ); - blocks.add(new Block()); + if (inCoreLimit < Block.SZ) { + blocks = new ArrayList(1); + blocks.add(new Block(inCoreLimit)); + } else { + blocks = new ArrayList(inCoreLimit / Block.SZ); + blocks.add(new Block()); + } } /** @@ -270,7 +279,7 @@ private Block last() { } private boolean reachedInCoreLimit() throws IOException { - if (blocks.size() * Block.SZ < inCoreLimit) + if (inCoreLength() < inCoreLimit) return false; switchToOverflow(); @@ -444,12 +453,20 @@ protected OutputStream overflow() throws IOException { 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; } } } From b584cb8754796bb96526b37b26b517741b9c9d1c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 8 Jul 2010 17:08:55 -0700 Subject: [PATCH 087/103] Add getObjectSize to ObjectReader This is an informational function used by PackWriter to help it better organize objects for delta compression. Storage systems can implement it to provide up more detailed size information, or they can simply rely on the default behavior that uses the ObjectLoader obtained from open. For local file storage, we can obtain this information faster through specialized routines that parse a pack object header. Change-Id: I13a09b4effb71ea5151b51547f7d091564531e58 Signed-off-by: Shawn O. Pearce --- .../org/eclipse/jgit/lib/ObjectReader.java | 28 ++++++++++ .../storage/file/CachedObjectDirectory.java | 14 +++++ .../jgit/storage/file/FileObjectDatabase.java | 55 +++++++++++++++++++ .../jgit/storage/file/ObjectDirectory.java | 40 ++++++++++++++ .../eclipse/jgit/storage/file/PackFile.java | 53 ++++++++++++++++++ .../jgit/storage/file/UnpackedObject.java | 46 +++++++++++++++- .../jgit/storage/file/WindowCursor.java | 12 ++++ 7 files changed, 247 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java index 1af3cb2de..d0351f867 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java @@ -135,6 +135,34 @@ public abstract ObjectLoader open(AnyObjectId objectId, int typeHint) throws MissingObjectException, IncorrectObjectTypeException, IOException; + /** + * Get only the size of an object. + *

    + * 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. *

    diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java index 505850c42..8ea0b854c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java @@ -186,6 +186,20 @@ ObjectLoader openObject2(WindowCursor curs, String objectName, 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 void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, WindowCursor curs) throws IOException { 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 index 444fd809b..250c7cac0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java @@ -166,6 +166,55 @@ final ObjectLoader openObjectImpl2(final WindowCursor curs, 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; @@ -185,6 +234,12 @@ abstract ObjectLoader openObject1(WindowCursor curs, AnyObjectId objectId) 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(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java index 8177155f4..6fe4fd754 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java @@ -304,6 +304,46 @@ ObjectLoader openObject1(final WindowCursor curs, } } + long getObjectSize1(final WindowCursor curs, final AnyObjectId objectId) + throws IOException { + PackList pList = packList.get(); + SEARCH: for (;;) { + for (final PackFile p : pList.packs) { + try { + long sz = p.getObjectSize(curs, objectId); + if (0 <= sz) + return sz; + } catch (PackMismatchException e) { + // Pack was modified; refresh the entire pack list. + // + pList = scanPacks(pList); + continue SEARCH; + } catch (IOException e) { + // Assume the pack is corrupted. + // + removePack(p); + } + } + return -1; + } + } + + @Override + 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 void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, WindowCursor curs) throws IOException { 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 index 40bb07100..e74a7c014 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java @@ -779,6 +779,59 @@ int getObjectType(final WindowCursor curs, long pos) throws IOException { } } + 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); 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 index 0325c5711..59f9c8267 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UnpackedObject.java @@ -109,7 +109,7 @@ static ObjectLoader open(InputStream in, File path, AnyObjectId id, if (isStandardFormat(hdr)) { in.reset(); - Inflater inf = wc.inflater(); + Inflater inf = wc.inflater(); InputStream zIn = inflate(in, inf); int avail = readSome(zIn, hdr, 0, 64); if (avail < 5) @@ -183,6 +183,50 @@ static ObjectLoader open(InputStream in, File path, AnyObjectId id, } } + 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 { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java index 6f4e72a82..04ee8b2c4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java @@ -95,6 +95,18 @@ public ObjectLoader open(AnyObjectId objectId, int 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); } From c20daa73146a3c385f4fed237708c4a7d28d8745 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 8 Jul 2010 17:11:38 -0700 Subject: [PATCH 088/103] Add path hash code to ObjectWalk PackWriter wants to categorize objects that are similar in path name, so blobs that are probably from the same file (or same sort of file) can be delta compressed against each other. Avoid converting into a string by performing the hashing directly against the path buffer in the tree iterator. We only hash the last 16 bytes of the path, and we try avoid any spaces, as we want the suffix of a file such as ".java" to be more important than the directory it is in, like "src". Change-Id: I31770ee711526306769a6f534afb19f937e0ba85 Signed-off-by: Shawn O. Pearce --- .../org/eclipse/jgit/revwalk/ObjectWalk.java | 12 ++++++++++++ .../jgit/treewalk/AbstractTreeIterator.java | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) 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 9393e2d17..a6ecfe219 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java @@ -384,6 +384,18 @@ public String getPathString() { return last != null ? treeWalk.getEntryPathString() : null; } + /** + * Get the current object's path hash code. + *

    + * 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(); 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 e74f13e85..23773862a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java @@ -402,6 +402,24 @@ public String getEntryPathString() { return TreeWalk.pathOf(this); } + /** + * Get the current entry's path hash code. + *

    + * 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. *

    From 2f93a09dd10696b6388a0fcb4099341ccef05169 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 8 Jul 2010 17:14:45 -0700 Subject: [PATCH 089/103] Save object path hash codes during packing We need to remember these so we can later cluster objects that have similar file paths near each other as we search for deltas between them. Change-Id: I52cb1e4ca15c9c267a2dbf51dd0d795f885f4cf8 Signed-off-by: Shawn O. Pearce --- .../jgit/storage/pack/ObjectToPack.java | 11 +++++++++ .../eclipse/jgit/storage/pack/PackWriter.java | 24 +++++++++++++++---- 2 files changed, 30 insertions(+), 5 deletions(-) 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 index 773ce44fd..cad3aaeec 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java @@ -83,6 +83,9 @@ public class ObjectToPack extends PackedObjectInfo { */ private int flags; + /** Hash of the object's tree path. */ + private int pathHash; + /** * Construct for the specified object id. * @@ -222,6 +225,14 @@ void setWeight(int weight) { setCRC(weight); } + int getPathHash() { + return pathHash; + } + + void setPathHash(int hc) { + pathHash = hc; + } + /** * Remember a specific representation for reuse at a later time. *

    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 index 2fecc6875..5ecef832a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -181,7 +181,7 @@ public class PackWriter { private final ObjectIdSubclassMap objectsMap = new ObjectIdSubclassMap(); // edge objects for thin packs - private final ObjectIdSubclassMap edgeObjects = new ObjectIdSubclassMap(); + private final ObjectIdSubclassMap edgeObjects = new ObjectIdSubclassMap(); private int compressionLevel; @@ -813,11 +813,11 @@ private void findObjectsToPack(final ProgressMonitor countingMonitor, RevObject o; while ((o = walker.next()) != null) { - addObject(o); + addObject(o, 0); countingMonitor.update(1); } while ((o = walker.nextObject()) != null) { - addObject(o); + addObject(o, walker.getPathHashCode()); countingMonitor.update(1); } countingMonitor.endTask(); @@ -837,9 +837,21 @@ private void findObjectsToPack(final ProgressMonitor countingMonitor, */ 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)) { - edgeObjects.add(object); - thin = true; + switch (object.getType()) { + case Constants.OBJ_TREE: + case Constants.OBJ_BLOB: + ObjectToPack otp = new ObjectToPack(object); + otp.setPathHash(pathHashCode); + edgeObjects.add(otp); + thin = true; + break; + } return; } @@ -848,6 +860,8 @@ public void addObject(final RevObject object) otp = reuseSupport.newObjectToPack(object); else otp = new ObjectToPack(object); + otp.setPathHash(pathHashCode); + try { objectsLists[object.getType()].add(otp); } catch (ArrayIndexOutOfBoundsException x) { From 616bc74cf7f888b3bf9c091e13c954ea45b8d68c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 9 Jul 2010 19:00:46 -0700 Subject: [PATCH 090/103] Add more configuration options to PackWriter We now at least import other pack settings like pack.window, which means we can later use these to control how we search for deltas. The compression level was fixed to use pack.compression rather than the loose object core.compression setting. Change-Id: I72ff6d481c936153ceb6a9e485fa731faf075a9a Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/storage/pack/PackConfig.java | 81 +++++++++++++++++++ .../eclipse/jgit/storage/pack/PackWriter.java | 79 ++++++++++++++++-- 2 files changed, 152 insertions(+), 8 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java 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 000000000..b3d140a8e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java @@ -0,0 +1,81 @@ +/* + * 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 KEY = new SectionParser() { + public PackConfig parse(final Config cfg) { + return new PackConfig(cfg); + } + }; + + final int deltaWindow; + + final long deltaWindowMemory; + + final int deltaDepth; + + final int compression; + + final int indexVersion; + + private PackConfig(Config rc) { + deltaWindow = rc.getInt("pack", "window", PackWriter.DEFAULT_DELTA_SEARCH_WINDOW_SIZE); + deltaWindowMemory = rc.getLong("pack", null, "windowmemory", 0); + deltaDepth = rc.getInt("pack", "depth", PackWriter.DEFAULT_MAX_DELTA_DEPTH); + compression = compression(rc); + indexVersion = rc.getInt("pack", "indexversion", 2); + } + + 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/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java index 5ecef832a..05e863e55 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -66,7 +66,6 @@ import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.CoreConfig; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdSubclassMap; @@ -166,6 +165,13 @@ public class PackWriter { */ 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; + private static final int PACK_VERSION_GENERATED = 2; @SuppressWarnings("unchecked") @@ -202,9 +208,13 @@ public class PackWriter { private boolean deltaBaseAsOffset = DEFAULT_DELTA_BASE_AS_OFFSET; + private boolean deltaCompress = true; + private int maxDeltaDepth = DEFAULT_MAX_DELTA_DEPTH; - private int outputVersion; + private int deltaSearchWindowSize = DEFAULT_DELTA_SEARCH_WINDOW_SIZE; + + private int indexVersion; private boolean thin; @@ -254,9 +264,11 @@ public PackWriter(final Repository repo, final ObjectReader reader) { else reuseSupport = null; - final CoreConfig coreConfig = configOf(repo).get(CoreConfig.KEY); - compressionLevel = coreConfig.getCompression(); - outputVersion = coreConfig.getPackIndexVersion(); + final PackConfig pc = configOf(repo).get(PackConfig.KEY); + deltaSearchWindowSize = pc.deltaWindow; + maxDeltaDepth = pc.deltaDepth; + compressionLevel = pc.compression; + indexVersion = pc.indexVersion; } private static Config configOf(final Repository repo) { @@ -364,6 +376,32 @@ public void setDeltaBaseAsOffset(boolean deltaBaseAsOffset) { this.deltaBaseAsOffset = deltaBaseAsOffset; } + /** + * Check whether the writer will create new deltas on the fly. + *

    + * Default setting: true + *

    + * + * @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. @@ -392,6 +430,31 @@ public void setMaxDeltaDepth(int maxDeltaDepth) { this.maxDeltaDepth = maxDeltaDepth; } + /** + * Get the number of objects to try when looking for a delta base. + * + * @return the object count to be searched. + */ + public int getDeltaSearchWindowSize() { + return deltaSearchWindowSize; + } + + /** + * Set the number of objects considered when searching for a delta base. + *

    + * Default setting: {@value #DEFAULT_DELTA_SEARCH_WINDOW_SIZE} + *

    + * + * @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; + } + /** @return true if this writer is producing a thin pack. */ public boolean isThin() { return thin; @@ -440,7 +503,7 @@ public void setIgnoreMissingUninteresting(final boolean ignore) { * @see PackIndexWriter */ public void setIndexVersion(final int version) { - outputVersion = version; + indexVersion = version; } /** @@ -571,10 +634,10 @@ public ObjectId computeName() { public void writeIndex(final OutputStream indexStream) throws IOException { final List list = sortByName(); final PackIndexWriter iw; - if (outputVersion <= 0) + if (indexVersion <= 0) iw = PackIndexWriter.createOldestPossible(indexStream, list); else - iw = PackIndexWriter.createVersion(indexStream, outputVersion); + iw = PackIndexWriter.createVersion(indexStream, indexVersion); iw.write(list, packcsum); } From 823e9a972100ad197fcb40efb37e1ca33f4bdb01 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 8 Jul 2010 17:19:20 -0700 Subject: [PATCH 091/103] Add doNotDelta flag to ObjectToPack This flag will later control whether or not PackWriter search for a delta base for this object. Edge objects will never get searched, as the writer won't be outputting them, so they should always have this flag set on. Sometime in the future this flag should also be set for file blobs on file paths that have the "-delta" gitattribute set in the repository's attributes file. Change-Id: I6e518e1a6996c8ce00b523727f1b605e400e82c6 Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/storage/pack/ObjectToPack.java | 16 +++++++++++++++- .../eclipse/jgit/storage/pack/PackWriter.java | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) 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 index cad3aaeec..4016e92c9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java @@ -61,6 +61,8 @@ public class ObjectToPack extends PackedObjectInfo { 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; @@ -75,7 +77,8 @@ public class ObjectToPack extends PackedObjectInfo { *
      *
    • 1 bit: wantWrite
    • *
    • 1 bit: canReuseAsIs
    • - *
    • 3 bits: unused
    • + *
    • 1 bit: doNotDelta
    • + *
    • 2 bits: unused
    • *
    • 3 bits: type
    • *
    • --
    • *
    • 24 bits: deltaDepth
    • @@ -207,6 +210,17 @@ 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()) 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 index 05e863e55..25e2bea1a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -911,6 +911,7 @@ private void addObject(final RevObject object, final int pathHashCode) case Constants.OBJ_BLOB: ObjectToPack otp = new ObjectToPack(object); otp.setPathHash(pathHashCode); + otp.setDoNotDelta(true); edgeObjects.add(otp); thin = true; break; From 6730f9e3c830d997d1731bf36414e626bda42ad8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 9 Jul 2010 07:57:47 -0700 Subject: [PATCH 092/103] Configure core.bigFileThreshold into PackWriter C Git's fast-import uses this to determine the maximum file size that it tries to delta compress, anything equal to or above this setting is stored with as a whole object with simple deflate. Define the configuration so we can use it later. Change-Id: Iea46e787d019a1b6c51135cc73d7688a02e207f5 Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/storage/pack/PackConfig.java | 3 ++ .../eclipse/jgit/storage/pack/PackWriter.java | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+) 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 index b3d140a8e..bd506a759 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java @@ -65,12 +65,15 @@ public PackConfig parse(final Config cfg) { final int indexVersion; + final long bigFileThreshold; + private PackConfig(Config rc) { deltaWindow = rc.getInt("pack", "window", PackWriter.DEFAULT_DELTA_SEARCH_WINDOW_SIZE); deltaWindowMemory = rc.getLong("pack", null, "windowmemory", 0); 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); } private static int compression(Config rc) { 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 index 25e2bea1a..7cf8d2a26 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -172,6 +172,8 @@ public class PackWriter { */ public static final int DEFAULT_DELTA_SEARCH_WINDOW_SIZE = 10; + static final long DEFAULT_BIG_FILE_THRESHOLD = 50 * 1024 * 1024; + private static final int PACK_VERSION_GENERATED = 2; @SuppressWarnings("unchecked") @@ -216,6 +218,8 @@ public class PackWriter { private int indexVersion; + private long bigFileThreshold = DEFAULT_BIG_FILE_THRESHOLD; + private boolean thin; private boolean ignoreMissingUninteresting = true; @@ -269,6 +273,7 @@ public PackWriter(final Repository repo, final ObjectReader reader) { maxDeltaDepth = pc.deltaDepth; compressionLevel = pc.compression; indexVersion = pc.indexVersion; + bigFileThreshold = pc.bigFileThreshold; } private static Config configOf(final Repository repo) { @@ -455,6 +460,29 @@ public void setDeltaSearchWindowSize(int objectCount) { deltaSearchWindowSize = objectCount; } + /** + * Get the maximum file size that will be delta compressed. + *

      + * 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; + } + /** @return true if this writer is producing a thin pack. */ public boolean isThin() { return thin; From 85b7a53d52c3f2bfb113cc665bd7893504cd4e50 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 9 Jul 2010 07:59:30 -0700 Subject: [PATCH 093/103] Refactor ObjectToPack's delta depth setting Long ago when PackWriter is first written we thought that the delta depth could be updated automatically. But its never used. Instead make this a simple standard setter so the caller can more directly set the delta depth of this object. This permits us to configure a depth that takes into account more than just the depth of another object in this same pack. Change-Id: I1d71b74f2edd7029b8743a2c13b591098ce8cc8f Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/storage/pack/ObjectToPack.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) 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 index 4016e92c9..afab01752 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java @@ -179,14 +179,7 @@ int getDeltaDepth() { return flags >>> DELTA_SHIFT; } - void updateDeltaDepth() { - final int d; - if (deltaBase instanceof ObjectToPack) - d = ((ObjectToPack) deltaBase).getDeltaDepth() + 1; - else if (deltaBase != null) - d = 1; - else - d = 0; + void setDeltaDepth(int d) { flags = (d << DELTA_SHIFT) | (flags & NON_DELTA_MASK); } From 4569d77e13c7bbf0c7466cbc57d0eefd117cb206 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 9 Jul 2010 08:04:06 -0700 Subject: [PATCH 094/103] Correctly classify the compressing objects phase Searching for reuse candidates should be fast compared to actually doing delta compression. So pull the progress monitor out of this phase and rename it back to identify the compressing objects state. Change-Id: I5eb80919f21c1251e0e3420ff7774126f1f79b27 Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/storage/pack/PackWriter.java | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) 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 index 7cf8d2a26..cae552831 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -122,12 +122,11 @@ public class PackWriter { public static final String COUNTING_OBJECTS_PROGRESS = JGitText.get().countingObjects; /** - * Title of {@link ProgressMonitor} task used during searching for objects - * reuse or delta reuse. + * Title of {@link ProgressMonitor} task used during compression. * * @see #writePack(ProgressMonitor, ProgressMonitor, OutputStream) */ - public static final String SEARCHING_REUSE_PROGRESS = JGitText.get().compressingObjects; + public static final String COMPRESSING_OBJECTS_PROGRESS = JGitText.get().compressingObjects; /** * Title of {@link ProgressMonitor} task used during writing out pack @@ -686,7 +685,7 @@ private List sortByName() { *

      * 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} + * written. {@link ProgressMonitor} tasks {@value #COMPRESSING_OBJECTS_PROGRESS} * (only if reuseDeltas or reuseObjects is enabled) and * {@value #WRITING_OBJECTS_PROGRESS} are updated during packing. *

      @@ -716,7 +715,7 @@ public void writePack(ProgressMonitor compressMonitor, writeMonitor = NullProgressMonitor.INSTANCE; if ((reuseDeltas || reuseObjects) && reuseSupport != null) - searchForReuse(compressMonitor); + searchForReuse(); final PackOutputStream out = new PackOutputStream(writeMonitor, packStream, isDeltaBaseAsOffset()); @@ -739,19 +738,11 @@ public void release() { } } - private void searchForReuse(ProgressMonitor compressMonitor) - throws IOException { - compressMonitor.beginTask(SEARCHING_REUSE_PROGRESS, getObjectsNumber()); + private void searchForReuse() throws IOException { for (List list : objectsLists) { - for (ObjectToPack otp : list) { - if (compressMonitor.isCancelled()) - throw new IOException( - JGitText.get().packingCancelledDuringObjectsWriting); + for (ObjectToPack otp : list) reuseSupport.selectObjectRepresentation(this, otp); - compressMonitor.update(1); - } } - compressMonitor.endTask(); } private void writeObjects(ProgressMonitor writeMonitor, PackOutputStream out) From 699e4aa7c5673b3c85f2be28652fbab6bd0dd968 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 9 Jul 2010 08:51:47 -0700 Subject: [PATCH 095/103] Make ObjectToPack clearReuseAsIs signal available to subclasses A subclass may want to use this method to release handles that are caching reuse information. Make it protected so they can override it and update themselves. Change-Id: I2277a56ad28560d2d2d97961cbc74bc7405a70d4 Signed-off-by: Shawn O. Pearce --- .../org/eclipse/jgit/storage/file/LocalObjectToPack.java | 6 ++++++ .../src/org/eclipse/jgit/storage/pack/ObjectToPack.java | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) 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 index b4bf37a5e..c7ef2c913 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalObjectToPack.java @@ -62,6 +62,12 @@ class LocalObjectToPack extends ObjectToPack { super(obj); } + @Override + protected void clearReuseAsIs() { + super.clearReuseAsIs(); + pack = null; + } + @Override public void select(StoredObjectRepresentation ref) { LocalObjectRepresentation ptr = (LocalObjectRepresentation) ref; 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 index afab01752..d3c409d9d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java @@ -199,7 +199,14 @@ void setReuseAsIs() { flags |= REUSE_AS_IS; } - void clearReuseAsIs() { + /** + * Forget the reuse information previously stored. + *

      + * 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; } From b38426ae8c401bf482792da20b8a8169542900a0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 9 Jul 2010 08:53:06 -0700 Subject: [PATCH 096/103] Add debugging toString() method to ObjectToPack Its useful to know what the flags are or what the base that was selected is. Dump these out as part of the object's toString. Change-Id: I8810067fb8337b08b4fcafd5f9ea3e1e31ca6726 Signed-off-by: Shawn O. Pearce --- .../jgit/storage/pack/ObjectToPack.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) 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 index d3c409d9d..471122a4f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java @@ -45,6 +45,7 @@ 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; @@ -261,4 +262,31 @@ void setPathHash(int hc) { 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(); + } } From 8612c0ace184797f32204e8e8126f20cf5f02214 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 7 Jul 2010 08:33:56 -0700 Subject: [PATCH 097/103] Initial pack format delta generator DeltaIndex is a simple pack style delta generator. The function works by creating a compact index of a source buffer's blocks, and then walking a sliding window along a desired result buffer, searching for the window in the index. When a match is found, the window is stretched to the longest possible length that is common with the source buffer, and a copy instruction is created. Rabin's polynomial hash function is used to compute the hash for a block, permitting efficient sliding of the window in single byte increments. The update function to slide one byte originated from David Mazieres' work in LBFS, and our implementation of the update step was certainly inspired by the initial work Geert Bosch proposed for C Git in http://marc.info/?l=git&m=114565424620771&w=2. To ensure the encoder runs in linear time with respect to the size of the two input buffers (source and result), the maximum number of blocks that can share the same position in the index's hashtable is capped at a constant number. This prevents bad inputs from causing the encoder to run in quadratic time, but comes with a penalty of creating a longer delta due to fewer considered copy positions. Strange hackery is used to cap the amount of memory used by the index to be no more than 12 bytes for every 16 bytes of source buffer, no matter what the JVM per-object overhead is. This permits an index to always be no larger than 1.75x the source buffer length, which is an important feature to support large windows of candidates to match against while packing. Here the strange hackery is nothing more than a manually managed chained hashtable, where pointers are array indexes into storage arrays rather than object references. Computation of the hash function for a single fixed sized block is done through an unrolled loop, where the first 4 iterations have been manually reduced down to eliminate unnecessary instructions. The pattern is derived from ObjectId.equals(byte[], int, byte[], int), where we have unrolled the loop required to compare two 20 byte arrays. Hours of testing with the Sun 1.6 JRE concluded that the non-obvious "foo[idx + 1]" style of reference is faster than "foo[idx++]", and so that is what we use here during hashing. Change-Id: If9fb2a1524361bc701405920560d8ae752221768 Signed-off-by: Shawn O. Pearce --- .../jgit/storage/pack/DeltaIndexTest.java | 228 +++++++ .../jgit/storage/pack/BinaryDelta.java | 102 +++- .../jgit/storage/pack/DeltaEncoder.java | 73 ++- .../eclipse/jgit/storage/pack/DeltaIndex.java | 573 ++++++++++++++++++ .../jgit/storage/pack/DeltaIndexScanner.java | 130 ++++ 5 files changed, 1093 insertions(+), 13 deletions(-) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/pack/DeltaIndexTest.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaIndex.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaIndexScanner.java 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 000000000..868ef8825 --- /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/src/org/eclipse/jgit/storage/pack/BinaryDelta.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/BinaryDelta.java index 494623df2..1d433d78a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/BinaryDelta.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/BinaryDelta.java @@ -45,6 +45,8 @@ 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. @@ -125,7 +127,8 @@ public static final byte[] apply(final byte[] base, final byte[] delta) { 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). // @@ -179,10 +182,105 @@ public static final byte[] apply(final byte[] base, final byte[] delta) { // 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/DeltaEncoder.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaEncoder.java index 9984eb1ff..204030b4a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaEncoder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaEncoder.java @@ -77,10 +77,12 @@ public class DeltaEncoder { private final byte[] buf = new byte[MAX_COPY_CMD_SIZE * 4]; + private final int limit; + private int size; /** - * Create an encoder. + * Create an encoder with no upper bound on the instruction stream size. * * @param out * buffer to store the instructions written. @@ -95,7 +97,31 @@ public class DeltaEncoder { */ 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); } @@ -107,8 +133,9 @@ private void writeVarint(long sz) throws IOException { sz >>>= 7; } buf[p++] = (byte) (((int) sz) & 0x7f); - out.write(buf, 0, p); size += p; + if (limit <= 0 || size < limit) + out.write(buf, 0, p); } /** @return current size of the delta stream, in bytes. */ @@ -121,11 +148,13 @@ public int getSize() { * * @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 void insert(String text) throws IOException { - insert(Constants.encode(text)); + public boolean insert(String text) throws IOException { + return insert(Constants.encode(text)); } /** @@ -133,11 +162,13 @@ public void insert(String text) throws IOException { * * @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 void insert(byte[] text) throws IOException { - insert(text, 0, text.length); + public boolean insert(byte[] text) throws IOException { + return insert(text, 0, text.length); } /** @@ -149,18 +180,31 @@ public void insert(byte[] text) throws IOException { * 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 void insert(byte[] text, int off, int cnt) throws IOException { - while (0 < cnt) { + 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; } /** @@ -171,12 +215,14 @@ public void insert(byte[] text, int off, int cnt) throws IOException { * 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 void copy(long offset, int cnt) throws IOException { + public boolean copy(long offset, int cnt) throws IOException { if (cnt == 0) - return; + return true; int p = 0; @@ -190,6 +236,8 @@ public void copy(long offset, int cnt) throws IOException { 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; @@ -197,8 +245,11 @@ public void copy(long offset, int cnt) throws IOException { } 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) { 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 000000000..c45a076b3 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaIndex.java @@ -0,0 +1,573 @@ +/* + * 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. + *

      + * 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. + *

      + * 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. + *

      + * 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 + + /** + * Maximum number of positions to consider for a given content hash. + *

      + * 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. + *

      + * 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. + *

      + * 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. + *

      + * 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. + *

      + * 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 000000000..d30690d40 --- /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 From 074055d747026c47040d0306585863ad5d428860 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 9 Jul 2010 11:59:55 -0700 Subject: [PATCH 098/103] debug-show-packdelta: Dump a pack delta to the console This is a horribly crude application, it doesn't even verify that the object its dumping is delta encoded. Its method of getting the delta is pretty abusive to the public PackWriter API, because right now we don't want to expose the real internal low-level methods actually required to do this. Change-Id: I437a17ceb98708b5603a2061126eb251e82f4ed4 Signed-off-by: Shawn O. Pearce --- org.eclipse.jgit.pgm/META-INF/MANIFEST.MF | 1 + .../services/org.eclipse.jgit.pgm.TextBuiltin | 1 + .../eclipse/jgit/pgm/debug/ShowPackDelta.java | 127 ++++++++++++++++++ .../jgit/storage/pack/PackOutputStream.java | 20 ++- .../eclipse/jgit/storage/pack/PackWriter.java | 2 +- 5 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF index 0a4376921..d6eac232e 100644 --- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF @@ -18,6 +18,7 @@ Import-Package: org.eclipse.jgit.api;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 0d4a140e6..075cadef7 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/debug/ShowPackDelta.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java new file mode 100644 index 000000000..1718ef30f --- /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/src/org/eclipse/jgit/storage/pack/PackOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackOutputStream.java index a93ac0516..92e1a197c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackOutputStream.java @@ -71,11 +71,25 @@ public final class PackOutputStream extends OutputStream { private byte[] copyBuffer; - PackOutputStream(final ProgressMonitor writeMonitor, - final OutputStream out, final boolean ofsDelta) { + /** + * Initialize a pack output stream. + *

      + * 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 = ofsDelta; + this.ofsDelta = pw.isDeltaBaseAsOffset(); } @Override 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 index cae552831..d96d5ddfd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -718,7 +718,7 @@ public void writePack(ProgressMonitor compressMonitor, searchForReuse(); final PackOutputStream out = new PackOutputStream(writeMonitor, - packStream, isDeltaBaseAsOffset()); + packStream, this); writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber()); out.writeFileHeader(PACK_VERSION_GENERATED, getObjectsNumber()); From dfad23bf3d9b17cc3e9d736fa3daf6ace52dbc33 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 9 Jul 2010 15:16:15 -0700 Subject: [PATCH 099/103] Implement delta generation during packing PackWriter now produces new deltas if there is not a suitable delta available for reuse from an existing pack file. This permits JGit to send less data on the wire by sending a delta relative to an object the other side already has, instead of sending the whole object. The delta searching algorithm is similar in style to what C Git uses, but apparently has some differences (see below for more on). Briefly, objects that should be considered for delta compression are pushed onto a list. This list is then sorted by a rough similarity score, which is derived from the path name the object was discovered at in the repository during object counting. The list is then walked in order. At each position in the list, up to $WINDOW objects prior to it are attempted as delta bases. Each object in the window is tried, and the shortest delta instruction sequence selects the base object. Some rough rules are used to prevent pathological behavior during this matching phase, like skipping pairings of objects that are not similar enough in size. PackWriter intentionally excludes commits and annotated tags from this new delta search phase. In the JGit repository only 28 out of 2600+ commits can be delta compressed by C Git. As the commit count tends to be a fair percentage of the total number of objects in the repository, and they generally do not delta compress well, skipping over them can improve performance with little increase in the output pack size. Because this implementation was rebuilt from scratch based on my own memory of how the packing algorithm has evolved over the years in C Git, PackWriter, DeltaWindow, and DeltaEncoder don't use exactly the same rules everywhere, and that leads JGit to produce different (but logically equivalent) pack files. Repository | Pack Size (bytes) | Packing Time | JGit - CGit = Difference | JGit / CGit -----------+----------------------------------+----------------- git | 25094348 - 24322890 = +771458 | 59.434s / 59.133s jgit | 5669515 - 5709046 = - 39531 | 6.654s / 6.806s linux-2.6 | 389M - 386M = +3M | 20m02s / 18m01s For the above tests pack.threads was set to 1, window size=10, delta depth=50, and delta and object reuse was disabled for both implementations. Both implementations were reading from an already fully packed repository on local disk. The running time reported is after 1 warm-up run of the tested implementation. PackWriter is writing 771 KiB more data on git.git, 3M more on linux-2.6, but is actually 39.5 KiB smaller on jgit.git. Being larger by less than 0.7% on linux-2.6 isn't bad, nor is taking an extra 2 minutes to pack. On the running time side, JGit is at a major disadvantage because linux-2.6 doesn't fit into the default WindowCache of 20M, while C Git is able to mmap the entire pack and have it available instantly in physical memory (assuming hot cache). CGit also has a feature where it caches deltas that were created during the compression phase, and uses those cached deltas during the writing phase. PackWriter does not implement this (yet), and therefore must create every delta twice. This could easily account for the increased running time we are seeing. Change-Id: I6292edc66c2e95fbe45b519b65fdb3918068889c Signed-off-by: Shawn O. Pearce --- .../jgit/storage/pack/DeltaWindow.java | 369 ++++++++++++++++++ .../jgit/storage/pack/DeltaWindowEntry.java | 80 ++++ .../eclipse/jgit/storage/pack/PackWriter.java | 174 ++++++++- 3 files changed, 622 insertions(+), 1 deletion(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindowEntry.java 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 000000000..4ae4eb8fa --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java @@ -0,0 +1,369 @@ +/* + * 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.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 ObjectReader reader; + + private final DeltaWindowEntry[] window; + + /** Maximum depth we should create for any delta chain. */ + private final int maxDepth; + + // 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. + *

      + * 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; + + DeltaWindow(PackWriter pw, ObjectReader or) { + writer = pw; + 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(); + + maxDepth = pw.getMaxDeltaDepth(); + } + + void search(ProgressMonitor monitor, ObjectToPack[] toSearch, int off, + int cnt) throws IOException { + for (int end = off + cnt; off < end; off++) { + monitor.update(1); + + res = window[resSlot]; + 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 coming through. + // + keepInWindow(); + } else { + // Search for a delta for the current window slot. + // + search(); + } + } + } + + 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(); + + // 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 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() { + if (++resSlot == window.length) + resSlot = 0; + } + + 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; + } + ent.index = idx; + } + return idx; + } + + private byte[] buffer(DeltaWindowEntry ent) throws MissingObjectException, + IncorrectObjectTypeException, IOException, LargeObjectException { + byte[] buf = ent.buffer; + if (buf == null) + ent.buffer = buf = writer.buffer(reader, ent.object); + return buf; + } +} \ No newline at end of file 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 000000000..0f1e6329f --- /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/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java index d96d5ddfd..f88f2635e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -48,11 +48,14 @@ 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.zip.Deflater; @@ -61,6 +64,7 @@ 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; @@ -78,6 +82,8 @@ 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; /** *

      @@ -716,6 +722,8 @@ public void writePack(ProgressMonitor compressMonitor, if ((reuseDeltas || reuseObjects) && reuseSupport != null) searchForReuse(); + if (deltaCompress) + searchForDeltas(compressMonitor); final PackOutputStream out = new PackOutputStream(writeMonitor, packStream, this); @@ -745,6 +753,103 @@ private void searchForReuse() throws IOException { } } + 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() { + 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(ProgressMonitor monitor, + ObjectToPack[] list, int cnt) throws MissingObjectException, + IncorrectObjectTypeException, LargeObjectException, IOException { + DeltaWindow dw = new DeltaWindow(this, reader); + dw.search(monitor, list, 0, cnt); + } + private void writeObjects(ProgressMonitor writeMonitor, PackOutputStream out) throws IOException { for (List list : objectsLists) { @@ -793,7 +898,10 @@ private void writeObject(PackOutputStream out, final ObjectToPack otp) // If we reached here, reuse wasn't possible. // - writeWholeObjectDeflate(out, otp); + if (otp.isDeltaRepresentation()) + writeDeltaObjectDeflate(out, otp); + else + writeWholeObjectDeflate(out, otp); out.endObject(); otp.setCRC(out.getCRC32()); } @@ -845,6 +953,70 @@ private void writeWholeObjectDeflate(PackOutputStream out, dst.finish(); } + private void writeDeltaObjectDeflate(PackOutputStream out, + final ObjectToPack otp) throws IOException { + 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); From a960d1429eec198a9d46e296c39e9e1aef95c6c7 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 9 Jul 2010 17:19:32 -0700 Subject: [PATCH 100/103] Cache small deltas during packing PackWriter now caches small deltas, or deltas that are very tiny compared to their source inputs, so that the writing phase goes faster by reusing those cached deltas. The cached data is stored compressed, which usually translates to a bigger footprint due to deltas being very hard to compress, but saves time during writing by avoiding the deflate step. They are held under SoftReferences so that the JVM GC can clear out deltas if memory gets very tight. We would rather continue working and spend a bit more CPU time during writing than crash due to OOME. To avoid OutOfMemoryErrors during the caching phase we also trap OOME and just abort out of the caching. Because deflateBound() always produces something larger than what we need to actually store the deflated data, we copy it over into a new buffer if the actual length doesn't match the buffer length. When packing jgit.git this saves over 111 KiB in the cache, and is thus a worthwhile hit on CPU time. To further save memory we store the inflated size of the delta (which we need for the object header) in the same field as the pathHash, as the pathHash is no longer necessary by this phase of the packing algorithm. Change-Id: I0da0c600d845e8ec962289751f24e65b5afa56d7 Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/storage/pack/DeltaCache.java | 129 ++++++++++++++++ .../jgit/storage/pack/DeltaWindow.java | 138 +++++++++++++++--- .../jgit/storage/pack/ObjectToPack.java | 28 ++++ .../eclipse/jgit/storage/pack/PackConfig.java | 6 + .../eclipse/jgit/storage/pack/PackWriter.java | 95 +++++++++++- 5 files changed, 377 insertions(+), 19 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaCache.java 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 000000000..7ad1c7f03 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaCache.java @@ -0,0 +1,129 @@ +/* + * 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 queue; + + private long used; + + DeltaCache(PackWriter pw) { + size = pw.getDeltaCacheSize(); + entryLimit = pw.getDeltaCacheLimit(); + queue = new ReferenceQueue(); + } + + 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. + // + if (data.length != actLen) { + byte[] nbuf = new byte[actLen]; + System.arraycopy(data, 0, nbuf, 0, actLen); + data = nbuf; + } + + // 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); + } + + private void checkForGarbageCollectedObjects() { + Ref r; + while ((r = (Ref) queue.poll()) != null) + used -= r.cost; + } + + static class Ref extends SoftReference { + final int cost; + + Ref(byte[] array, ReferenceQueue queue) { + super(array, queue); + cost = array.length; + } + } +} 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 index 4ae4eb8fa..fa577b669 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java @@ -43,7 +43,10 @@ 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; @@ -59,6 +62,8 @@ class DeltaWindow { private final PackWriter writer; + private final DeltaCache deltaCache; + private final ObjectReader reader; private final DeltaWindowEntry[] window; @@ -87,8 +92,12 @@ class DeltaWindow { /** If we have {@link #bestDelta}, the window position it was created by. */ private int bestSlot; - DeltaWindow(PackWriter pw, ObjectReader or) { + /** 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. @@ -111,25 +120,31 @@ class DeltaWindow { void search(ProgressMonitor monitor, ObjectToPack[] toSearch, int off, int cnt) throws IOException { - for (int end = off + cnt; off < end; off++) { - monitor.update(1); + try { + for (int end = off + cnt; off < end; off++) { + monitor.update(1); - res = window[resSlot]; - res.set(toSearch[off]); + res = window[resSlot]; + 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 coming through. - // - keepInWindow(); - } else { - // Search for a delta for the current window slot. - // - search(); + 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(); } } @@ -184,6 +199,7 @@ private void search() throws IOException { } resObj.setDeltaDepth(srcObj.getDeltaDepth() + 1); resObj.clearReuseAsIs(); + cacheDelta(srcObj, resObj); // Discard the cached best result, otherwise it leaks. // @@ -275,6 +291,33 @@ private int delta(final DeltaWindowEntry src, final int 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); + 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 @@ -366,4 +409,63 @@ private byte[] buffer(DeltaWindowEntry ent) throws MissingObjectException, ent.buffer = buf = writer.buffer(reader, ent.object); return buf; } -} \ No newline at end of file + + 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/ObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java index 471122a4f..70188a380 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectToPack.java @@ -90,6 +90,9 @@ public class ObjectToPack extends PackedObjectInfo { /** 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. * @@ -150,8 +153,25 @@ 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; + } } /** @@ -248,6 +268,14 @@ 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. *

      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 index bd506a759..f1c17d761 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java @@ -61,6 +61,10 @@ public PackConfig parse(final Config cfg) { final int deltaDepth; + final long deltaCacheSize; + + final int deltaCacheLimit; + final int compression; final int indexVersion; @@ -70,6 +74,8 @@ public PackConfig parse(final Config cfg) { 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); 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 index f88f2635e..bd9c1e312 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -179,6 +179,10 @@ public class PackWriter { 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") @@ -221,6 +225,10 @@ public class PackWriter { private int deltaSearchWindowSize = DEFAULT_DELTA_SEARCH_WINDOW_SIZE; + private long deltaCacheSize = DEFAULT_DELTA_CACHE_SIZE; + + private int deltaCacheLimit = DEFAULT_DELTA_CACHE_LIMIT; + private int indexVersion; private long bigFileThreshold = DEFAULT_BIG_FILE_THRESHOLD; @@ -275,6 +283,8 @@ public PackWriter(final Repository repo, final ObjectReader reader) { final PackConfig pc = configOf(repo).get(PackConfig.KEY); deltaSearchWindowSize = pc.deltaWindow; + deltaCacheSize = pc.deltaCacheSize; + deltaCacheLimit = pc.deltaCacheLimit; maxDeltaDepth = pc.deltaDepth; compressionLevel = pc.compression; indexVersion = pc.indexVersion; @@ -465,6 +475,57 @@ public void setDeltaSearchWindowSize(int objectCount) { deltaSearchWindowSize = objectCount; } + /** + * Get the size of the in-memory delta cache. + * + * @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. + *

      + * 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. + *

      + * 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. *

      @@ -488,6 +549,27 @@ 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 true if this writer is producing a thin pack. */ public boolean isThin() { return thin; @@ -846,7 +928,8 @@ private boolean loadSize(ObjectToPack e) throws MissingObjectException, private void searchForDeltas(ProgressMonitor monitor, ObjectToPack[] list, int cnt) throws MissingObjectException, IncorrectObjectTypeException, LargeObjectException, IOException { - DeltaWindow dw = new DeltaWindow(this, reader); + DeltaCache dc = new DeltaCache(this); + DeltaWindow dw = new DeltaWindow(this, dc, reader); dw.search(monitor, list, 0, cnt); } @@ -955,6 +1038,16 @@ private void writeWholeObjectDeflate(PackOutputStream out, 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()); From 74e08350129825c63c1b0c683b402241a844aabb Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 9 Jul 2010 18:15:50 -0700 Subject: [PATCH 101/103] Honor pack.threads and perform delta search in parallel If we have multiple CPUs available, packing usually goes faster when each CPU is assigned a slice of the available search space. The number of threads to use is guessed from the runtime if it wasn't set by the caller, or wasn't set in the configuration. Change-Id: If554fd8973db77632a52a0f45377dd6ec13fc220 Signed-off-by: Shawn O. Pearce --- .../org/eclipse/jgit/lib/ObjectReader.java | 10 ++ .../jgit/lib/ThreadSafeProgressMonitor.java | 111 ++++++++++++++ .../jgit/storage/file/WindowCursor.java | 5 + .../eclipse/jgit/storage/pack/DeltaCache.java | 15 +- .../eclipse/jgit/storage/pack/PackConfig.java | 3 + .../eclipse/jgit/storage/pack/PackWriter.java | 139 +++++++++++++++++- .../storage/pack/ThreadSafeDeltaCache.java | 86 +++++++++++ 7 files changed, 358 insertions(+), 11 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ThreadSafeDeltaCache.java diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java index d0351f867..ae7063867 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java @@ -59,6 +59,16 @@ 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. + *

      + * 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? * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java new file mode 100644 index 000000000..9708bb2f9 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java @@ -0,0 +1,111 @@ +/* + * 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.util.concurrent.locks.ReentrantLock; + +/** + * Wrapper around the general {@link ProgressMonitor} to make it thread safe. + */ +public class ThreadSafeProgressMonitor implements ProgressMonitor { + private final ProgressMonitor pm; + + private final ReentrantLock lock; + + /** + * 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(); + } + } + + public void beginTask(String title, int totalWork) { + lock.lock(); + try { + pm.beginTask(title, totalWork); + } finally { + lock.unlock(); + } + } + + public void update(int completed) { + lock.lock(); + try { + pm.update(completed); + } finally { + lock.unlock(); + } + } + + public boolean isCancelled() { + lock.lock(); + try { + return pm.isCancelled(); + } finally { + lock.unlock(); + } + } + + public void endTask() { + lock.lock(); + try { + pm.endTask(); + } finally { + lock.unlock(); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java index 04ee8b2c4..5376d077a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java @@ -77,6 +77,11 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs { this.db = db; } + @Override + public ObjectReader newReader() { + return new WindowCursor(db); + } + public boolean has(AnyObjectId objectId) throws IOException { return db.has(objectId); } 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 index 7ad1c7f03..b6a7436f1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaCache.java @@ -95,11 +95,7 @@ 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. // - if (data.length != actLen) { - byte[] nbuf = new byte[actLen]; - System.arraycopy(data, 0, nbuf, 0, actLen); - data = nbuf; - } + 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 @@ -112,6 +108,15 @@ Ref cache(byte[] data, int actLen, int reservedSize) { 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) 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 index f1c17d761..814ab8f29 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java @@ -71,6 +71,8 @@ public PackConfig parse(final Config cfg) { 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); @@ -80,6 +82,7 @@ private PackConfig(Config rc) { 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) { 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 index bd9c1e312..3769d6b4b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -58,6 +58,9 @@ 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; @@ -77,6 +80,7 @@ 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; @@ -233,6 +237,8 @@ public class PackWriter { private long bigFileThreshold = DEFAULT_BIG_FILE_THRESHOLD; + private int threads = 1; + private boolean thin; private boolean ignoreMissingUninteresting = true; @@ -289,6 +295,7 @@ public PackWriter(final Repository repo, final ObjectReader reader) { compressionLevel = pc.compression; indexVersion = pc.indexVersion; bigFileThreshold = pc.bigFileThreshold; + threads = pc.threads; } private static Config configOf(final Repository repo) { @@ -452,6 +459,9 @@ public void setMaxDeltaDepth(int maxDeltaDepth) { /** * Get the number of objects to try when looking for a delta base. + *

      + * 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. */ @@ -477,6 +487,8 @@ public void setDeltaSearchWindowSize(int objectCount) { /** * Get the size of the in-memory delta cache. + *

      + * 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 @@ -570,6 +582,26 @@ 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. + *

      + * 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; @@ -925,12 +957,107 @@ private boolean loadSize(ObjectToPack e) throws MissingObjectException, return true; } - private void searchForDeltas(ProgressMonitor monitor, - ObjectToPack[] list, int cnt) throws MissingObjectException, - IncorrectObjectTypeException, LargeObjectException, IOException { - DeltaCache dc = new DeltaCache(this); - DeltaWindow dw = new DeltaWindow(this, dc, reader); - dw.search(monitor, list, 0, cnt); + 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 errors = Collections + .synchronizedList(new ArrayList()); + 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) 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 000000000..141289190 --- /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(); + } + } +} From 97341949171dcdd7d6c30f3eef89ee2c63d8eb34 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 9 Jul 2010 18:32:33 -0700 Subject: [PATCH 102/103] Honor pack.windowlimit to cap memory usage during packing The pack.windowlimit configuration parameter places an upper bound on the number of bytes used by the DeltaWindow class as it scans through the object list. If memory usage would exceed the limit the window is temporarily decreased in size to keep memory used within that bound. Change-Id: I09521b8f335475d8aee6125826da8ba2e545060d Signed-off-by: Shawn O. Pearce --- .../eclipse/jgit/storage/pack/DeltaIndex.java | 13 +++++ .../jgit/storage/pack/DeltaWindow.java | 47 +++++++++++++++++-- .../eclipse/jgit/storage/pack/PackWriter.java | 33 +++++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) 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 index c45a076b3..e548cc936 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaIndex.java @@ -70,6 +70,19 @@ 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. + *

      + * 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. *

      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 index fa577b669..ee865fbe5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java @@ -68,9 +68,15 @@ class DeltaWindow { 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. */ @@ -115,6 +121,7 @@ class DeltaWindow { for (int i = 0; i < window.length; i++) window[i] = new DeltaWindowEntry(); + maxMemory = pw.getDeltaSearchMemoryLimit(); maxDepth = pw.getMaxDeltaDepth(); } @@ -125,6 +132,15 @@ void search(ProgressMonitor monitor, ObjectToPack[] toSearch, int 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()) { @@ -148,6 +164,18 @@ void search(ProgressMonitor monitor, ObjectToPack[] toSearch, int off, } } + 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 @@ -336,8 +364,13 @@ private void shuffleBaseUpInPriority() { } private void keepInWindow() { - if (++resSlot == window.length) - resSlot = 0; + resSlot = next(resSlot); + } + + private int next(int slot) { + if (++slot == window.length) + return 0; + return slot; } private int prior(int slot) { @@ -397,6 +430,8 @@ private DeltaIndex index(DeltaWindowEntry ent) e.initCause(noMemory); throw e; } + if (0 < maxMemory) + loaded += idx.getIndexSize() - idx.getSourceSize(); ent.index = idx; } return idx; @@ -405,8 +440,12 @@ private DeltaIndex index(DeltaWindowEntry ent) private byte[] buffer(DeltaWindowEntry ent) throws MissingObjectException, IncorrectObjectTypeException, IOException, LargeObjectException { byte[] buf = ent.buffer; - if (buf == null) - ent.buffer = buf = writer.buffer(reader, ent.object); + if (buf == null) { + buf = writer.buffer(reader, ent.object); + if (0 < maxMemory) + loaded += buf.length; + ent.buffer = buf; + } return buf; } 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 index 3769d6b4b..38f00b573 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -229,6 +229,8 @@ public class PackWriter { 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; @@ -289,6 +291,7 @@ public PackWriter(final Repository repo, final ObjectReader reader) { final PackConfig pc = configOf(repo).get(PackConfig.KEY); deltaSearchWindowSize = pc.deltaWindow; + deltaSearchMemoryLimit = pc.deltaWindowMemory; deltaCacheSize = pc.deltaCacheSize; deltaCacheLimit = pc.deltaCacheLimit; maxDeltaDepth = pc.deltaDepth; @@ -485,6 +488,36 @@ public void setDeltaSearchWindowSize(int objectCount) { deltaSearchWindowSize = objectCount; } + /** + * Get maximum number of bytes to put into the delta search window. + *

      + * 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. + *

      + * 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. + *

      + * 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. *

      From 12fe0f2d1eb18aab2964532e99d11d4311d558eb Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 16 Jul 2010 10:41:09 -0700 Subject: [PATCH 103/103] Discard the uncompressed delta as soon as its compressed The DeltaCache will most likely need to copy the compressed delta into a new buffer in order to compact away the wasted space at the end caused by over allocation. Since we don't need the uncompressed format anymore, null out our only reference to it so the GC can reclaim this memory if it needs to perform a collection in order to satisfy the cache's allocation attempt. Change-Id: I50403cfd2e3001b093f93a503cccf7adab43cc9d Signed-off-by: Shawn O. Pearce --- .../src/org/eclipse/jgit/storage/pack/DeltaWindow.java | 1 + 1 file changed, 1 insertion(+) 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 index ee865fbe5..6521e6d3e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java @@ -330,6 +330,7 @@ private void cacheDelta(ObjectToPack srcObj, ObjectToPack resObj) { ZipStream zs = new ZipStream(deflater(), zbuf); bestDelta.writeTo(zs, null); + bestDelta = null; int len = zs.finish(); resObj.setCachedDelta(deltaCache.cache(zbuf, len, rawsz));