]> source.dussan.org Git - jgit.git/commitdiff
PackWriter: Support reuse of entire packs 88/2388/4
authorShawn O. Pearce <spearce@spearce.org>
Mon, 31 Jan 2011 16:35:17 +0000 (08:35 -0800)
committerShawn O. Pearce <spearce@spearce.org>
Thu, 3 Feb 2011 21:20:22 +0000 (13:20 -0800)
The most expensive part of packing a repository for transport to
another system is enumerating all of the objects in the repository.
Once this gets to the size of the linux-2.6 repository (1.8 million
objects), enumeration can take several CPU minutes and costs a lot
of temporary working set memory.

Teach PackWriter to efficiently reuse an existing "cached pack"
by answering a clone request with a thin pack followed by a larger
cached pack appended to the end.  This requires the repository
owner to first construct the cached pack by hand, and record the
tip commits inside of $GIT_DIR/objects/info/cached-packs:

  cd $GIT_DIR
  root=$(git rev-parse master)
  tmp=objects/.tmp-$$
  names=$(echo $root | git pack-objects --keep-true-parents --revs $tmp)
  for n in $names; do
    chmod a-w $tmp-$n.pack $tmp-$n.idx
    touch objects/pack/pack-$n.keep
    mv $tmp-$n.pack objects/pack/pack-$n.pack
    mv $tmp-$n.idx objects/pack/pack-$n.idx
  done

  (echo "+ $root";
   for n in $names; do echo "P $n"; done;
   echo) >>objects/info/cached-packs

  git repack -a -d

When a clone request needs to include $root, the corresponding
cached pack will be copied as-is, rather than enumerating all of
the objects that are reachable from $root.

For a linux-2.6 kernel repository that should be about 376 MiB,
the above process creates two packs of 368 MiB and 38 MiB[1].
This is a local disk usage increase of ~26 MiB, due to reduced
delta compression between the large cached pack and the smaller
recent activity pack.  The overhead is similar to 1 full copy of
the compressed project sources.

With this cached pack in hand, JGit daemon completes a clone request
in 1m17s less time, but a slightly larger data transfer (+2.39 MiB):

  Before:
    remote: Counting objects: 1861830, done
    remote: Finding sources: 100% (1861830/1861830)
    remote: Getting sizes: 100% (88243/88243)
    remote: Compressing objects: 100% (88184/88184)
    Receiving objects: 100% (1861830/1861830), 376.01 MiB | 19.01 MiB/s, done.
    remote: Total 1861830 (delta 4706), reused 1851053 (delta 1553844)
    Resolving deltas: 100% (1564621/1564621), done.

    real  3m19.005s

  After:
    remote: Counting objects: 1601, done
    remote: Counting objects: 1828460, done
    remote: Finding sources: 100% (50475/50475)
    remote: Getting sizes: 100% (18843/18843)
    remote: Compressing objects: 100% (7585/7585)
    remote: Total 1861830 (delta 2407), reused 1856197 (delta 37510)
    Receiving objects: 100% (1861830/1861830), 378.40 MiB | 31.31 MiB/s, done.
    Resolving deltas: 100% (1559477/1559477), done.

    real 2m2.938s

Repository owners can periodically refresh their cached packs by
repacking their repository, folding all newer objects into a larger
cached pack.  Since repacking is already considered to be a normal
Git maintenance activity, this isn't a very big burden.

[1] In this test $root was set back about two weeks.

Change-Id: Ib87131d5c4b5e8c5cacb0f4fe16ff4ece554734b
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
18 files changed:
org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties
org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteArrayWindow.java
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteBufferWindow.java
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ByteWindow.java
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/CachedObjectDirectory.java
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileObjectDatabase.java
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalCachedPack.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCursor.java
org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/BaseSearch.java
org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/CachedPack.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ObjectReuseAsIs.java
org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackOutputStream.java
org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java

index bc4603eeb96a043953b85f90c20fd134cc021f3b..4681daa66207e528ddba74c4e44b53a5c3ca3f3a 100644 (file)
@@ -35,6 +35,7 @@ bareRepositoryNoWorkdirAndIndex=Bare Repository has neither a working tree, nor
 blobNotFound=Blob not found: {0}
 branchNameInvalid=Branch name {0} is not allowed
 blobNotFoundForPath=Blob not found: {0} for path: {1}
+cachedPacksPreventsIndexCreation=Using cached packs prevents index creation
 cannotBeCombined=Cannot be combined.
 cannotCombineTreeFilterWithRevFilter=Cannot combine TreeFilter {0} with RefFilter {1}.
 cannotCommitOnARepoWithState=Cannot commit on a repo with state: {0}
index 2834bd2549278b3dc855690f20aa37d8f1b7f5b7..e29c9bc24e6818c12e68b3b62ef18e99f4c21d39 100644 (file)
@@ -95,6 +95,7 @@ public class JGitText extends TranslationBundle {
        /***/ public String blobNotFound;
        /***/ public String blobNotFoundForPath;
        /***/ public String branchNameInvalid;
+       /***/ public String cachedPacksPreventsIndexCreation;
        /***/ public String cannotBeCombined;
        /***/ public String cannotCombineTreeFilterWithRevFilter;
        /***/ public String cannotCommitOnARepoWithState;
index 457c8dc90a082f6609a5eaad5f23fec8a3ff0a71..5b5a511c17a21e753f8ad273745d88cf5ac40bcd 100644 (file)
 package org.eclipse.jgit.storage.file;
 
 import java.io.IOException;
-import java.io.OutputStream;
 import java.util.zip.CRC32;
 import java.util.zip.DataFormatException;
 import java.util.zip.Inflater;
 
+import org.eclipse.jgit.storage.pack.PackOutputStream;
+
 /**
  * A {@link ByteWindow} with an underlying byte array for storage.
  */
@@ -81,7 +82,8 @@ final class ByteArrayWindow extends ByteWindow {
                out.update(array, (int) (pos - start), cnt);
        }
 
-       void write(OutputStream out, long pos, int cnt) throws IOException {
+       @Override
+       void write(PackOutputStream out, long pos, int cnt) throws IOException {
                out.write(array, (int) (pos - start), cnt);
        }
 
index 29a0159950f3d0053a1f746265bcfefb2a2ed884..b9af158a05636a25a9b97e82a8f884386f95f4db 100644 (file)
 
 package org.eclipse.jgit.storage.file;
 
+import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.zip.DataFormatException;
 import java.util.zip.Inflater;
 
+import org.eclipse.jgit.storage.pack.PackOutputStream;
+
 /**
  * A window for accessing git packs using a {@link ByteBuffer} for storage.
  *
@@ -71,6 +74,20 @@ final class ByteBufferWindow extends ByteWindow {
                return n;
        }
 
+       @Override
+       void write(PackOutputStream out, long pos, int cnt) throws IOException {
+               final ByteBuffer s = buffer.slice();
+               s.position((int) (pos - start));
+
+               while (0 < cnt) {
+                       byte[] buf = out.getCopyBuffer();
+                       int n = Math.min(cnt, buf.length);
+                       s.get(buf, 0, n);
+                       out.write(buf, 0, n);
+                       cnt -= n;
+               }
+       }
+
        @Override
        protected int setInput(final int pos, final Inflater inf)
                        throws DataFormatException {
index f92efb4ac900ba15e06e06233024f2737f0c7996..3e147898be855700b8f5ca0c87da6f757a61e3f9 100644 (file)
 
 package org.eclipse.jgit.storage.file;
 
+import java.io.IOException;
 import java.util.zip.DataFormatException;
 import java.util.zip.Inflater;
 
+import org.eclipse.jgit.storage.pack.PackOutputStream;
+
 /**
  * A window of data currently stored within a cache.
  * <p>
@@ -117,6 +120,9 @@ abstract class ByteWindow {
         */
        protected abstract int copy(int pos, byte[] dstbuf, int dstoff, int cnt);
 
+       abstract void write(PackOutputStream out, long pos, int cnt)
+                       throws IOException;
+
        final int setInput(long pos, Inflater inf) throws DataFormatException {
                return setInput((int) (pos - start), inf);
        }
index d5f2ac53cb9537adbe6c5176cd34028f81e20986..f1c370c2a8a085b241bf19cbefdba4c2b3561484 100644 (file)
@@ -46,6 +46,7 @@ package org.eclipse.jgit.storage.file;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Collection;
 import java.util.Set;
 
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
@@ -56,6 +57,7 @@ import org.eclipse.jgit.lib.ObjectDatabase;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdSubclassMap;
 import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.storage.pack.CachedPack;
 import org.eclipse.jgit.storage.pack.ObjectToPack;
 import org.eclipse.jgit.storage.pack.PackWriter;
 import org.eclipse.jgit.util.FS;
@@ -138,6 +140,11 @@ class CachedObjectDirectory extends FileObjectDatabase {
                return wrapped.getFS();
        }
 
+       @Override
+       Collection<? extends CachedPack> getCachedPacks() throws IOException {
+               return wrapped.getCachedPacks();
+       }
+
        @Override
        AlternateHandle[] myAlternates() {
                if (alts == null) {
index 22e5412ef650ec29dd28a7d1e595f726c996dc17..f28facbec6f7ef392e0bb7dc09f0b0c3030fb714 100644 (file)
@@ -45,6 +45,7 @@ package org.eclipse.jgit.storage.file;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Collection;
 import java.util.Set;
 
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
@@ -54,6 +55,7 @@ import org.eclipse.jgit.lib.ObjectDatabase;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.storage.pack.CachedPack;
 import org.eclipse.jgit.storage.pack.ObjectToPack;
 import org.eclipse.jgit.storage.pack.PackWriter;
 import org.eclipse.jgit.util.FS;
@@ -258,6 +260,9 @@ abstract class FileObjectDatabase extends ObjectDatabase {
 
        abstract File getDirectory();
 
+       abstract Collection<? extends CachedPack> getCachedPacks()
+                       throws IOException;
+
        abstract AlternateHandle[] myAlternates();
 
        abstract boolean tryAgain1();
@@ -292,6 +297,11 @@ abstract class FileObjectDatabase extends ObjectDatabase {
                        this.db = db;
                }
 
+               @SuppressWarnings("unchecked")
+               Collection<CachedPack> getCachedPacks() throws IOException {
+                       return (Collection<CachedPack>) db.getCachedPacks();
+               }
+
                void close() {
                        db.close();
                }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalCachedPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LocalCachedPack.java
new file mode 100644 (file)
index 0000000..18b32c9
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2011, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.file;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.storage.pack.CachedPack;
+import org.eclipse.jgit.storage.pack.PackOutputStream;
+
+class LocalCachedPack extends CachedPack {
+       private final ObjectDirectory odb;
+
+       private final Set<ObjectId> tips;
+
+       private final String[] packNames;
+
+       LocalCachedPack(ObjectDirectory odb, Set<ObjectId> tips,
+                       List<String> packNames) {
+               this.odb = odb;
+
+               if (tips.size() == 1)
+                       this.tips = Collections.singleton(tips.iterator().next());
+               else
+                       this.tips = Collections.unmodifiableSet(tips);
+
+               this.packNames = packNames.toArray(new String[packNames.size()]);
+       }
+
+       @Override
+       public Set<ObjectId> getTips() {
+               return tips;
+       }
+
+       @Override
+       public long getObjectCount() throws IOException {
+               long cnt = 0;
+               for (String packName : packNames)
+                       cnt += getPackFile(packName).getObjectCount();
+               return cnt;
+       }
+
+       void copyAsIs(PackOutputStream out, WindowCursor wc) throws IOException {
+               for (String packName : packNames)
+                       getPackFile(packName).copyPackAsIs(out, wc);
+       }
+
+       @Override
+       public <T extends ObjectId> Set<ObjectId> hasObject(Iterable<T> toFind)
+                       throws IOException {
+               PackFile[] packs = new PackFile[packNames.length];
+               for (int i = 0; i < packNames.length; i++)
+                       packs[i] = getPackFile(packNames[i]);
+
+               Set<ObjectId> have = new HashSet<ObjectId>();
+               for (ObjectId id : toFind) {
+                       for (PackFile pack : packs) {
+                               if (pack.hasObject(id)) {
+                                       have.add(id);
+                                       break;
+                               }
+                       }
+               }
+               return have;
+       }
+
+       private PackFile getPackFile(String packName) throws FileNotFoundException {
+               for (PackFile pack : odb.getPacks()) {
+                       if (packName.equals(pack.getPackName()))
+                               return pack;
+               }
+               throw new FileNotFoundException(getPackFilePath(packName));
+       }
+
+       private String getPackFilePath(String packName) {
+               final File packDir = new File(odb.getDirectory(), "pack");
+               return new File(packDir, "pack-" + packName + ".pack").getPath();
+       }
+}
index 745c18fdb06ecc49feb0528cb8ccf71d92f334cf..1918b158de2880c730ca4a76ab48f902d0291efd 100644 (file)
@@ -72,10 +72,13 @@ import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.RepositoryCache;
 import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.storage.pack.CachedPack;
 import org.eclipse.jgit.storage.pack.ObjectToPack;
 import org.eclipse.jgit.storage.pack.PackWriter;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.RawParseUtils;
 
 /**
  * Traditional file system based {@link ObjectDatabase}.
@@ -112,8 +115,12 @@ public class ObjectDirectory extends FileObjectDatabase {
 
        private final File alternatesFile;
 
+       private final File cachedPacksFile;
+
        private final AtomicReference<PackList> packList;
 
+       private final AtomicReference<CachedPackList> cachedPacks;
+
        private final FS fs;
 
        private final AtomicReference<AlternateHandle[]> alternates;
@@ -142,7 +149,9 @@ public class ObjectDirectory extends FileObjectDatabase {
                infoDirectory = new File(objects, "info");
                packDirectory = new File(objects, "pack");
                alternatesFile = new File(infoDirectory, "alternates");
+               cachedPacksFile = new File(infoDirectory, "cached-packs");
                packList = new AtomicReference<PackList>(NO_PACKS);
+               cachedPacks = new AtomicReference<CachedPackList>();
                unpackedObjectCache = new UnpackedObjectCache();
                this.fs = fs;
 
@@ -226,6 +235,83 @@ public class ObjectDirectory extends FileObjectDatabase {
                return Collections.unmodifiableCollection(Arrays.asList(packs));
        }
 
+       @Override
+       Collection<? extends CachedPack> getCachedPacks() throws IOException {
+               CachedPackList list = cachedPacks.get();
+               if (list == null || list.snapshot.isModified(cachedPacksFile))
+                       list = scanCachedPacks(list);
+
+               Collection<CachedPack> result = list.getCachedPacks();
+               boolean resultIsCopy = false;
+
+               for (AlternateHandle h : myAlternates()) {
+                       Collection<CachedPack> altPacks = h.getCachedPacks();
+                       if (altPacks.isEmpty())
+                               continue;
+
+                       if (result.isEmpty()) {
+                               result = altPacks;
+                               continue;
+                       }
+
+                       if (!resultIsCopy) {
+                               result = new ArrayList<CachedPack>(result);
+                               resultIsCopy = true;
+                       }
+                       result.addAll(altPacks);
+               }
+               return result;
+       }
+
+       private CachedPackList scanCachedPacks(CachedPackList old)
+                       throws IOException {
+               FileSnapshot s = FileSnapshot.save(cachedPacksFile);
+               byte[] buf;
+               try {
+                       buf = IO.readFully(cachedPacksFile);
+               } catch (FileNotFoundException e) {
+                       buf = new byte[0];
+               }
+
+               if (old != null && old.snapshot.equals(s)
+                               && Arrays.equals(old.raw, buf)) {
+                       old.snapshot.setClean(s);
+                       return old;
+               }
+
+               ArrayList<LocalCachedPack> list = new ArrayList<LocalCachedPack>(4);
+               Set<ObjectId> tips = new HashSet<ObjectId>();
+               int ptr = 0;
+               while (ptr < buf.length) {
+                       if (buf[ptr] == '#' || buf[ptr] == '\n') {
+                               ptr = RawParseUtils.nextLF(buf, ptr);
+                               continue;
+                       }
+
+                       if (buf[ptr] == '+') {
+                               tips.add(ObjectId.fromString(buf, ptr + 2));
+                               ptr = RawParseUtils.nextLF(buf, ptr + 2);
+                               continue;
+                       }
+
+                       List<String> names = new ArrayList<String>(4);
+                       while (ptr < buf.length && buf[ptr] == 'P') {
+                               int end = RawParseUtils.nextLF(buf, ptr);
+                               if (buf[end - 1] == '\n')
+                                       end--;
+                               names.add(RawParseUtils.decode(buf, ptr + 2, end));
+                               ptr = RawParseUtils.nextLF(buf, end);
+                       }
+
+                       if (!tips.isEmpty() && !names.isEmpty()) {
+                               list.add(new LocalCachedPack(this, tips, names));
+                               tips = new HashSet<ObjectId>();
+                       }
+               }
+               list.trimToSize();
+               return new CachedPackList(s, Collections.unmodifiableList(list), buf);
+       }
+
        /**
         * Add a single existing pack to the list of available pack files.
         *
@@ -760,6 +846,26 @@ public class ObjectDirectory extends FileObjectDatabase {
                }
        }
 
+       private static final class CachedPackList {
+               final FileSnapshot snapshot;
+
+               final Collection<LocalCachedPack> packs;
+
+               final byte[] raw;
+
+               CachedPackList(FileSnapshot sn, List<LocalCachedPack> list, byte[] buf) {
+                       snapshot = sn;
+                       packs = list;
+                       raw = buf;
+               }
+
+               @SuppressWarnings("unchecked")
+               Collection<CachedPack> getCachedPacks() {
+                       Collection p = packs;
+                       return p;
+               }
+       }
+
        @Override
        public ObjectDatabase newCachedDatabase() {
                return newCachedFileObjectDatabase();
index 5782d0ad8073676aab184db7c211f56bb5a31a89..3a76d4c66adcf23c8b6a2e6aeec7f6186eb54d25 100644 (file)
@@ -175,6 +175,16 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> {
                return packFile;
        }
 
+       /** @return name extracted from {@code pack-*.pack} pattern. */
+       public String getPackName() {
+               String name = getPackFile().getName();
+               if (name.startsWith("pack-"))
+                       name = name.substring("pack-".length());
+               if (name.endsWith(".pack"))
+                       name = name.substring(0, name.length() - ".pack".length());
+               return name;
+       }
+
        /**
         * Determine if an object is contained within the pack file.
         * <p>
@@ -295,6 +305,13 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> {
                return dstbuf;
        }
 
+       void copyPackAsIs(PackOutputStream out, WindowCursor curs)
+                       throws IOException {
+               // Pin the first window, this ensures the length is accurate.
+               curs.pin(this, 0);
+               curs.copyPackAsIs(this, out, 12, length - (12 + 20));
+       }
+
        final void copyAsIs(PackOutputStream out, LocalObjectToPack src,
                        WindowCursor curs) throws IOException,
                        StoredObjectRepresentationNotAvailableException {
index 86ee460b147936416f1280536847ad2643d492a2..5a3e01c8c1ec1084e96195a43b6bab5509263c58 100644 (file)
@@ -64,6 +64,7 @@ import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.storage.pack.CachedPack;
 import org.eclipse.jgit.storage.pack.ObjectReuseAsIs;
 import org.eclipse.jgit.storage.pack.ObjectToPack;
 import org.eclipse.jgit.storage.pack.PackOutputStream;
@@ -154,6 +155,11 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
                        out.writeObject(otp);
        }
 
+       @SuppressWarnings("unchecked")
+       public Collection<CachedPack> getCachedPacks() throws IOException {
+               return (Collection<CachedPack>) db.getCachedPacks();
+       }
+
        /**
         * Copy bytes from the window to a caller supplied buffer.
         *
@@ -190,6 +196,24 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
                return cnt - need;
        }
 
+       public void copyPackAsIs(PackOutputStream out, CachedPack pack)
+                       throws IOException {
+               ((LocalCachedPack) pack).copyAsIs(out, this);
+       }
+
+       void copyPackAsIs(final PackFile pack, final PackOutputStream out,
+                       long position, long cnt) throws IOException {
+               while (0 < cnt) {
+                       pin(pack, position);
+
+                       int ptr = (int) (position - window.start);
+                       int n = (int) Math.min(window.size() - ptr, cnt);
+                       window.write(out, position, n);
+                       position += n;
+                       cnt -= n;
+               }
+       }
+
        /**
         * Inflate a region of the pack starting at {@code position}.
         *
index 785b8eb69cbe09ba829b735240c6a649dcee48a6..d4116bff353727b3045ccd9b923a91b0921972f7 100644 (file)
@@ -47,6 +47,7 @@ import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
 
 import java.io.IOException;
+import java.util.List;
 import java.util.Set;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -73,7 +74,9 @@ class BaseSearch {
 
        private final ObjectId[] baseTrees;
 
-       private final ObjectIdSubclassMap<ObjectToPack> edgeObjects;
+       private final ObjectIdSubclassMap<ObjectToPack> objectsMap;
+
+       private final List<ObjectToPack> edgeObjects;
 
        private final IntSet alreadyProcessed;
 
@@ -84,10 +87,12 @@ class BaseSearch {
        private final MutableObjectId idBuf;
 
        BaseSearch(ProgressMonitor countingMonitor, Set<RevTree> bases,
-                       ObjectIdSubclassMap<ObjectToPack> edges, ObjectReader or) {
+                       ObjectIdSubclassMap<ObjectToPack> objects,
+                       List<ObjectToPack> edges, ObjectReader or) {
                progress = countingMonitor;
                reader = or;
                baseTrees = bases.toArray(new ObjectId[bases.size()]);
+               objectsMap = objects;
                edgeObjects = edges;
 
                alreadyProcessed = new IntSet();
@@ -175,8 +180,10 @@ class BaseSearch {
                obj.setEdge();
                obj.setPathHash(pathHash);
 
-               if (edgeObjects.addIfAbsent(obj) == obj)
+               if (objectsMap.addIfAbsent(obj) == obj) {
+                       edgeObjects.add(obj);
                        progress.update(1);
+               }
        }
 
        private byte[] readTree(AnyObjectId id) throws MissingObjectException,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/CachedPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/CachedPack.java
new file mode 100644 (file)
index 0000000..c369e65
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2011, 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.util.Set;
+
+import org.eclipse.jgit.lib.ObjectId;
+
+/** Describes a pack file {@link ObjectReuseAsIs} can append onto a stream. */
+public abstract class CachedPack {
+       /**
+        * Objects that start this pack.
+        * <p>
+        * All objects reachable from the tips are contained within this pack. If
+        * {@link PackWriter} is going to include everything reachable from all of
+        * these objects, this cached pack is eligible to be appended directly onto
+        * the output pack stream.
+        *
+        * @return the tip objects that describe this pack.
+        */
+       public abstract Set<ObjectId> getTips();
+
+       /**
+        * Get the number of objects in this pack.
+        *
+        * @return the total object count for the pack.
+        * @throws IOException
+        *             if the object count cannot be read.
+        */
+       public abstract long getObjectCount() throws IOException;
+
+       /**
+        * Determine if the pack contains the requested objects.
+        *
+        * @param <T>
+        *            any type of ObjectId to search for.
+        * @param toFind
+        *            the objects to search for.
+        * @return the objects contained in the pack.
+        * @throws IOException
+        *             the pack cannot be accessed
+        */
+       public abstract <T extends ObjectId> Set<ObjectId> hasObject(
+                       Iterable<T> toFind) throws IOException;
+}
index ce4cd2c0e787e3c397c6a6a1fec19ac202738bed..480e0519d52a4f086a437250a5f32614b82a6744 100644 (file)
@@ -44,6 +44,7 @@
 package org.eclipse.jgit.storage.pack;
 
 import java.io.IOException;
+import java.util.Collection;
 import java.util.List;
 
 import org.eclipse.jgit.errors.MissingObjectException;
@@ -180,4 +181,33 @@ public interface ObjectReuseAsIs {
         */
        public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp)
                        throws IOException, StoredObjectRepresentationNotAvailableException;
+
+       /**
+        * Obtain the available cached packs.
+        * <p>
+        * A cached pack has known starting points and may be sent entirely as-is,
+        * with almost no effort on the sender's part.
+        *
+        * @return the available cached packs.
+        * @throws IOException
+        *             the cached packs cannot be listed from the repository.
+        *             Callers may choose to ignore this and continue as-if there
+        *             were no cached packs.
+        */
+       public Collection<CachedPack> getCachedPacks() throws IOException;
+
+       /**
+        * Append an entire pack's contents onto the output stream.
+        * <p>
+        * The entire pack, excluding its header and trailing footer is sent.
+        *
+        * @param out
+        *            stream to append the pack onto.
+        * @param pack
+        *            the cached pack to send.
+        * @throws IOException
+        *             the pack cannot be read, or stream did not accept a write.
+        */
+       public abstract void copyPackAsIs(PackOutputStream out, CachedPack pack)
+                       throws IOException;
 }
index 38f5e3aef7d602501e535efd21cf533ec397dc5f..a7925b52b7ac94b4d8195614018886e4cc96ffe4 100644 (file)
@@ -135,10 +135,10 @@ public final class PackOutputStream extends OutputStream {
                out.flush();
        }
 
-       void writeFileHeader(int version, int objectCount) throws IOException {
+       void writeFileHeader(int version, long objectCount) throws IOException {
                System.arraycopy(Constants.PACK_SIGNATURE, 0, headerBuffer, 0, 4);
                NB.encodeInt32(headerBuffer, 4, version);
-               NB.encodeInt32(headerBuffer, 8, objectCount);
+               NB.encodeInt32(headerBuffer, 8, (int) objectCount);
                write(headerBuffer, 0, 12);
        }
 
index 320fa3b008c0cae895976e57cb94fdcc6d28e736..eee08ed3e1e383b83a4d373018c33b8be4ee96f2 100644 (file)
@@ -56,9 +56,11 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -90,6 +92,7 @@ import org.eclipse.jgit.revwalk.AsyncRevObjectQueue;
 import org.eclipse.jgit.revwalk.ObjectWalk;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevFlagSet;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevSort;
 import org.eclipse.jgit.revwalk.RevTree;
@@ -145,7 +148,9 @@ public class PackWriter {
        private final ObjectIdSubclassMap<ObjectToPack> objectsMap = new ObjectIdSubclassMap<ObjectToPack>();
 
        // edge objects for thin packs
-       private final ObjectIdSubclassMap<ObjectToPack> edgeObjects = new ObjectIdSubclassMap<ObjectToPack>();
+       private List<ObjectToPack> edgeObjects = new ArrayList<ObjectToPack>();
+
+       private List<CachedPack> cachedPacks = new ArrayList<CachedPack>(2);
 
        private Deflater myDeflater;
 
@@ -168,6 +173,8 @@ public class PackWriter {
 
        private boolean thin;
 
+       private boolean useCachedPacks;
+
        private boolean ignoreMissingUninteresting = true;
 
        /**
@@ -281,6 +288,24 @@ public class PackWriter {
                thin = packthin;
        }
 
+       /** @return true to reuse cached packs. If true index creation isn't available. */
+       public boolean isUseCachedPacks() {
+               return useCachedPacks;
+       }
+
+       /**
+        * @param useCached
+        *            if set to true and a cached pack is present, it will be
+        *            appended onto the end of a thin-pack, reducing the amount of
+        *            working set space and CPU used by PackWriter. Enabling this
+        *            feature prevents PackWriter from creating an index for the
+        *            newly created pack, so its only suitable for writing to a
+        *            network client, where the client will make the index.
+        */
+       public void setUseCachedPacks(boolean useCached) {
+               useCachedPacks = useCached;
+       }
+
        /**
         * @return true to ignore objects that are uninteresting and also not found
         *         on local disk; false to throw a {@link MissingObjectException}
@@ -308,8 +333,8 @@ public class PackWriter {
         *
         * @return number of objects in pack.
         */
-       public int getObjectsNumber() {
-               return objectsMap.size();
+       public long getObjectsNumber() {
+               return stats.totalObjects;
        }
 
        /**
@@ -323,26 +348,15 @@ public class PackWriter {
         * a caller side. Iterator must return each id of object to write exactly
         * once.
         * </p>
-        * <p>
-        * When iterator returns object that has {@link RevFlag#UNINTERESTING} flag,
-        * this object won't be included in an output pack. Instead, it is recorded
-        * as edge-object (known to remote repository) for thin-pack. In such a case
-        * writer may pack objects with delta base object not within set of objects
-        * to pack, but belonging to party repository - those marked with
-        * {@link RevFlag#UNINTERESTING} flag. This type of pack is used only for
-        * transport.
-        * </p>
         *
         * @param objectsSource
         *            iterator of object to store in a pack; order of objects within
         *            each type is important, ordering by type is not needed;
         *            allowed types for objects are {@link Constants#OBJ_COMMIT},
         *            {@link Constants#OBJ_TREE}, {@link Constants#OBJ_BLOB} and
-        *            {@link Constants#OBJ_TAG}; objects returned by iterator may
-        *            be later reused by caller as object id and type are internally
-        *            copied in each iteration; if object returned by iterator has
-        *            {@link RevFlag#UNINTERESTING} flag set, it won't be included
-        *            in a pack, but is considered as edge-object for thin-pack.
+        *            {@link Constants#OBJ_TAG}; objects returned by iterator may be
+        *            later reused by caller as object id and type are internally
+        *            copied in each iteration.
         * @throws IOException
         *             when some I/O problem occur during reading objects.
         */
@@ -392,9 +406,21 @@ public class PackWriter {
         * @param id
         *            the object to test the existence of.
         * @return true if the object will appear in the output pack file.
+        * @throws IOException
+        *             a cached pack cannot be examined.
         */
-       public boolean willInclude(final AnyObjectId id) {
-               return get(id) != null;
+       public boolean willInclude(final AnyObjectId id) throws IOException {
+               ObjectToPack obj = objectsMap.get(id);
+               if (obj != null && !obj.isEdge())
+                       return true;
+
+               Set<ObjectId> toFind = Collections.singleton(id.toObjectId());
+               for (CachedPack pack : cachedPacks) {
+                       if (pack.hasObject(toFind).contains(id))
+                               return true;
+               }
+
+               return false;
        }
 
        /**
@@ -405,7 +431,8 @@ public class PackWriter {
         * @return the object we are packing, or null.
         */
        public ObjectToPack get(AnyObjectId id) {
-               return objectsMap.get(id);
+               ObjectToPack obj = objectsMap.get(id);
+               return obj != null && !obj.isEdge() ? obj : null;
        }
 
        /**
@@ -439,6 +466,9 @@ public class PackWriter {
         *             the index data could not be written to the supplied stream.
         */
        public void writeIndex(final OutputStream indexStream) throws IOException {
+               if (!cachedPacks.isEmpty())
+                       throw new IOException(JGitText.get().cachedPacksPreventsIndexCreation);
+
                final List<ObjectToPack> list = sortByName();
                final PackIndexWriter iw;
                int indexVersion = config.getIndexVersion();
@@ -451,7 +481,10 @@ public class PackWriter {
 
        private List<ObjectToPack> sortByName() {
                if (sortedByName == null) {
-                       sortedByName = new ArrayList<ObjectToPack>(objectsMap.size());
+                       int cnt = 0;
+                       for (List<ObjectToPack> list : objectsLists)
+                               cnt += list.size();
+                       sortedByName = new ArrayList<ObjectToPack>(cnt);
                        for (List<ObjectToPack> list : objectsLists) {
                                for (ObjectToPack otp : list)
                                        sortedByName.add(otp);
@@ -501,12 +534,21 @@ public class PackWriter {
                final PackOutputStream out = new PackOutputStream(writeMonitor,
                                packStream, this);
 
-               int objCnt = getObjectsNumber();
+               long objCnt = 0;
+               for (List<ObjectToPack> list : objectsLists)
+                       objCnt += list.size();
+               for (CachedPack pack : cachedPacks)
+                       objCnt += pack.getObjectCount();
                stats.totalObjects = objCnt;
-               writeMonitor.beginTask(JGitText.get().writingObjects, objCnt);
+
+               writeMonitor.beginTask(JGitText.get().writingObjects, (int) objCnt);
                out.writeFileHeader(PACK_VERSION_GENERATED, objCnt);
                out.flush();
                writeObjects(out);
+               for (CachedPack pack : cachedPacks) {
+                       stats.reusedObjects += pack.getObjectCount();
+                       reuseSupport.copyPackAsIs(out, pack);
+               }
                writeChecksum(out);
 
                reader.release();
@@ -532,7 +574,10 @@ public class PackWriter {
        }
 
        private void searchForReuse(ProgressMonitor monitor) throws IOException {
-               monitor.beginTask(JGitText.get().searchForReuse, getObjectsNumber());
+               int cnt = 0;
+               for (List<ObjectToPack> list : objectsLists)
+                       cnt += list.size();
+               monitor.beginTask(JGitText.get().searchForReuse, cnt);
                for (List<ObjectToPack> list : objectsLists)
                        reuseSupport.selectObjectRepresentation(this, monitor, list);
                monitor.endTask();
@@ -591,8 +636,8 @@ public class PackWriter {
                                                        continue;
                                                }
 
-                                               otp = edgeObjects.get(notFound.getObjectId());
-                                               if (otp != null) {
+                                               otp = objectsMap.get(notFound.getObjectId());
+                                               if (otp != null && otp.isEdge()) {
                                                        otp.setDoNotDelta(true);
                                                        continue;
                                                }
@@ -601,11 +646,8 @@ public class PackWriter {
                                }
 
                                ObjectToPack otp = sizeQueue.getCurrent();
-                               if (otp == null) {
+                               if (otp == null)
                                        otp = objectsMap.get(sizeQueue.getObjectId());
-                                       if (otp == null)
-                                               otp = edgeObjects.get(sizeQueue.getObjectId());
-                               }
 
                                long sz = sizeQueue.getSize();
                                if (limit <= sz || Integer.MAX_VALUE <= sz)
@@ -1014,16 +1056,38 @@ public class PackWriter {
                all.addAll(want);
                all.addAll(have);
 
+               final Map<ObjectId, CachedPack> tipToPack = new HashMap<ObjectId, CachedPack>();
                final ObjectWalk walker = new ObjectWalk(reader);
+               final RevFlag inCachedPack = walker.newFlag("inCachedPack");
+               final RevFlag include = walker.newFlag("include");
+
+               final RevFlagSet keepOnRestart = new RevFlagSet();
+               keepOnRestart.add(inCachedPack);
+
                walker.setRetainBody(false);
-               if (have.isEmpty())
+               walker.carry(include);
+
+               int haveEst = have.size();
+               if (have.isEmpty()) {
                        walker.sort(RevSort.COMMIT_TIME_DESC);
-               else {
+                       if (useCachedPacks && reuseSupport != null) {
+                               for (CachedPack pack : reuseSupport.getCachedPacks()) {
+                                       for (ObjectId id : pack.getTips()) {
+                                               tipToPack.put(id, pack);
+                                               all.add(id);
+                                       }
+                               }
+                               haveEst += tipToPack.size();
+                       }
+               } else {
                        walker.sort(RevSort.TOPO);
                        if (thin)
                                walker.sort(RevSort.BOUNDARY, true);
                }
 
+               List<RevObject> wantObjs = new ArrayList<RevObject>(want.size());
+               List<RevObject> haveObjs = new ArrayList<RevObject>(haveEst);
+
                AsyncRevObjectQueue q = walker.parseAny(all, true);
                try {
                        for (;;) {
@@ -1031,10 +1095,18 @@ public class PackWriter {
                                        RevObject o = q.next();
                                        if (o == null)
                                                break;
-                                       if (have.contains(o))
+
+                                       if (tipToPack.containsKey(o))
+                                               o.add(inCachedPack);
+
+                                       if (have.contains(o)) {
+                                               haveObjs.add(o);
                                                walker.markUninteresting(o);
-                                       else
+                                       } else if (want.contains(o)) {
+                                               o.add(include);
+                                               wantObjs.add(o);
                                                walker.markStart(o);
+                                       }
                                } catch (MissingObjectException e) {
                                        if (ignoreMissingUninteresting
                                                        && have.contains(e.getObjectId()))
@@ -1046,21 +1118,45 @@ public class PackWriter {
                        q.release();
                }
 
+               int typesToPrune = 0;
                final int maxBases = config.getDeltaSearchWindowSize();
                Set<RevTree> baseTrees = new HashSet<RevTree>();
                RevObject o;
                while ((o = walker.next()) != null) {
+                       if (o.has(inCachedPack)) {
+                               CachedPack pack = tipToPack.get(o);
+                               if (includesAllTips(pack, include, walker)) {
+                                       useCachedPack(walker, keepOnRestart, //
+                                                       wantObjs, haveObjs, pack);
+
+                                       countingMonitor.endTask();
+                                       countingMonitor.beginTask(JGitText.get().countingObjects,
+                                                       ProgressMonitor.UNKNOWN);
+                                       continue;
+                               }
+                       }
+
                        if (o.has(RevFlag.UNINTERESTING)) {
                                if (baseTrees.size() <= maxBases)
                                        baseTrees.add(((RevCommit) o).getTree());
                                continue;
                        }
+
                        addObject(o, 0);
                        countingMonitor.update(1);
                }
 
+               for (CachedPack p : cachedPacks) {
+                       for (ObjectId d : p.hasObject(objectsLists[Constants.OBJ_COMMIT])) {
+                               if (baseTrees.size() <= maxBases)
+                                       baseTrees.add(walker.lookupCommit(d).getTree());
+                               objectsMap.get(d).setEdge();
+                               typesToPrune |= 1 << Constants.OBJ_COMMIT;
+                       }
+               }
+
                BaseSearch bases = new BaseSearch(countingMonitor, baseTrees, //
-                               edgeObjects, reader);
+                               objectsMap, edgeObjects, reader);
                while ((o = walker.nextObject()) != null) {
                        if (o.has(RevFlag.UNINTERESTING))
                                continue;
@@ -1073,9 +1169,87 @@ public class PackWriter {
                        addObject(o, pathHash);
                        countingMonitor.update(1);
                }
+
+               for (CachedPack p : cachedPacks) {
+                       for (ObjectId d : p.hasObject(objectsLists[Constants.OBJ_TREE])) {
+                               objectsMap.get(d).setEdge();
+                               typesToPrune |= 1 << Constants.OBJ_TREE;
+                       }
+                       for (ObjectId d : p.hasObject(objectsLists[Constants.OBJ_BLOB])) {
+                               objectsMap.get(d).setEdge();
+                               typesToPrune |= 1 << Constants.OBJ_BLOB;
+                       }
+                       for (ObjectId d : p.hasObject(objectsLists[Constants.OBJ_TAG])) {
+                               objectsMap.get(d).setEdge();
+                               typesToPrune |= 1 << Constants.OBJ_TAG;
+                       }
+               }
+
+               if (typesToPrune != 0) {
+                       pruneObjectList(typesToPrune, Constants.OBJ_COMMIT);
+                       pruneObjectList(typesToPrune, Constants.OBJ_TREE);
+                       pruneObjectList(typesToPrune, Constants.OBJ_BLOB);
+                       pruneObjectList(typesToPrune, Constants.OBJ_TAG);
+               }
+
+               for (CachedPack pack : cachedPacks)
+                       countingMonitor.update((int) pack.getObjectCount());
                countingMonitor.endTask();
        }
 
+       private void pruneObjectList(int typesToPrune, int typeCode) {
+               if ((typesToPrune & (1 << typeCode)) == 0)
+                       return;
+
+               final List<ObjectToPack> list = objectsLists[typeCode];
+               final int size = list.size();
+               int src = 0;
+               int dst = 0;
+
+               for (; src < size; src++) {
+                       ObjectToPack obj = list.get(src);
+                       if (obj.isEdge())
+                               continue;
+                       if (dst != src)
+                               list.set(dst, obj);
+                       dst++;
+               }
+
+               while (dst < list.size())
+                       list.remove(dst);
+       }
+
+       private void useCachedPack(ObjectWalk walker, RevFlagSet keepOnRestart,
+                       List<RevObject> wantObj, List<RevObject> baseObj, CachedPack pack)
+                       throws MissingObjectException, IncorrectObjectTypeException,
+                       IOException {
+               cachedPacks.add(pack);
+               for (ObjectId id : pack.getTips())
+                       baseObj.add(walker.lookupOrNull(id));
+
+               objectsMap.clear();
+               objectsLists[Constants.OBJ_COMMIT] = new ArrayList<ObjectToPack>();
+
+               setThin(true);
+               walker.resetRetain(keepOnRestart);
+               walker.sort(RevSort.TOPO);
+               walker.sort(RevSort.BOUNDARY, true);
+
+               for (RevObject id : wantObj)
+                       walker.markStart(id);
+               for (RevObject id : baseObj)
+                       walker.markUninteresting(id);
+       }
+
+       private static boolean includesAllTips(CachedPack pack, RevFlag include,
+                       ObjectWalk walker) {
+               for (ObjectId id : pack.getTips()) {
+                       if (!walker.lookupOrNull(id).has(include))
+                               return false;
+               }
+               return true;
+       }
+
        /**
         * Include one object to the output file.
         * <p>
@@ -1090,20 +1264,6 @@ public class PackWriter {
         */
        public void addObject(final RevObject object)
                        throws IncorrectObjectTypeException {
-               if (object.has(RevFlag.UNINTERESTING)) {
-                       switch (object.getType()) {
-                       case Constants.OBJ_TREE:
-                       case Constants.OBJ_BLOB:
-                               ObjectToPack otp = new ObjectToPack(object);
-                               otp.setPathHash(0);
-                               otp.setEdge();
-                               edgeObjects.addIfAbsent(otp);
-                               thin = true;
-                               break;
-                       }
-                       return;
-               }
-
                addObject(object, 0);
        }
 
@@ -1162,11 +1322,11 @@ public class PackWriter {
                if (nFmt == PACK_DELTA && reuseDeltas) {
                        ObjectId baseId = next.getDeltaBase();
                        ObjectToPack ptr = objectsMap.get(baseId);
-                       if (ptr != null) {
+                       if (ptr != null && !ptr.isEdge()) {
                                otp.setDeltaBase(ptr);
                                otp.setReuseAsIs();
                                otp.setWeight(nWeight);
-                       } else if (thin && edgeObjects.contains(baseId)) {
+                       } else if (thin && ptr != null && ptr.isEdge()) {
                                otp.setDeltaBase(baseId);
                                otp.setReuseAsIs();
                                otp.setWeight(nWeight);
index 2112d85b321b181a2c6665a3b26a30126bca9af8..011c1ee2dcae92f2c1067e0f856024af04b04af6 100644 (file)
@@ -259,6 +259,7 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
                                        newObjects.add(r.getNewObjectId());
                        }
 
+                       writer.setUseCachedPacks(true);
                        writer.setThin(thinPack);
                        writer.setDeltaBaseAsOffset(capableOfsDelta);
                        writer.preparePack(monitor, newObjects, remoteObjects);
index d8edfa0df326fe19f354662c7d09380fbaa1e1b1..81da24792e2b12aeb39677c0fe0da01e631d505a 100644 (file)
@@ -636,6 +636,7 @@ public class UploadPack {
                        cfg = new PackConfig(db);
                final PackWriter pw = new PackWriter(cfg, walk.getObjectReader());
                try {
+                       pw.setUseCachedPacks(true);
                        pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA));
                        pw.setThin(options.contains(OPTION_THIN_PACK));
                        pw.preparePack(pm, wantAll, commonBase);