/* * 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 java.io.OutputStream; import java.security.MessageDigest; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.zip.Deflater; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevSort; import org.eclipse.jgit.transport.PackedObjectInfo; import org.eclipse.jgit.util.NB; /** *

* PackWriter class is responsible for generating pack files from specified set * of objects from repository. This implementation produce pack files in format * version 2. *

*

* Source of objects may be specified in two ways: *

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

*

* Class provide set of configurable options and {@link ProgressMonitor} * support, as operations may take a long time for big repositories. Deltas * searching algorithm is NOT IMPLEMENTED yet - this implementation * relies only on deltas and objects reuse. *

*

* This class is not thread safe, it is intended to be used in one thread, with * one instance per created pack. Subsequent calls to writePack result in * undefined behavior. *

*/ public class PackWriter { /** * Title of {@link ProgressMonitor} task used during counting objects to * pack. * * @see #preparePack(Collection, Collection) */ public static final String COUNTING_OBJECTS_PROGRESS = "Counting objects"; /** * Title of {@link ProgressMonitor} task used during searching for objects * reuse or delta reuse. * * @see #writePack(OutputStream) */ public static final String SEARCHING_REUSE_PROGRESS = "Compressing objects"; /** * Title of {@link ProgressMonitor} task used during writing out pack * (objects) * * @see #writePack(OutputStream) */ public static final String WRITING_OBJECTS_PROGRESS = "Writing objects"; /** * Default value of deltas reuse option. * * @see #setReuseDeltas(boolean) */ public static final boolean DEFAULT_REUSE_DELTAS = true; /** * Default value of objects reuse option. * * @see #setReuseObjects(boolean) */ public static final boolean DEFAULT_REUSE_OBJECTS = true; /** * Default value of delta base as offset option. * * @see #setDeltaBaseAsOffset(boolean) */ public static final boolean DEFAULT_DELTA_BASE_AS_OFFSET = false; /** * Default value of maximum delta chain depth. * * @see #setMaxDeltaDepth(int) */ public static final int DEFAULT_MAX_DELTA_DEPTH = 50; private static final int PACK_VERSION_GENERATED = 2; @SuppressWarnings("unchecked") private final List 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(); } private final ObjectIdSubclassMap objectsMap = new ObjectIdSubclassMap(); // edge objects for thin packs private final ObjectIdSubclassMap edgeObjects = new ObjectIdSubclassMap(); private final Repository db; private PackOutputStream out; private final Deflater deflater; private ProgressMonitor initMonitor; private ProgressMonitor writeMonitor; private final byte[] buf = new byte[16384]; // 16 KB private final WindowCursor windowCursor = new WindowCursor(); private List sortedByName; private byte packcsum[]; private boolean reuseDeltas = DEFAULT_REUSE_DELTAS; private boolean reuseObjects = DEFAULT_REUSE_OBJECTS; private boolean deltaBaseAsOffset = DEFAULT_DELTA_BASE_AS_OFFSET; private int maxDeltaDepth = DEFAULT_MAX_DELTA_DEPTH; private int outputVersion; private boolean thin; private boolean ignoreMissingUninteresting = true; /** * Create writer for specified repository. *

* Objects for packing are specified in {@link #preparePack(Iterator)} or * {@link #preparePack(Collection, Collection)}. * * @param repo * repository where objects are stored. * @param monitor * operations progress monitor, used within * {@link #preparePack(Iterator)}, * {@link #preparePack(Collection, Collection)} * , or {@link #writePack(OutputStream)}. */ public PackWriter(final Repository repo, final ProgressMonitor monitor) { this(repo, monitor, monitor); } /** * Create writer for specified repository. *

* Objects for packing are specified in {@link #preparePack(Iterator)} or * {@link #preparePack(Collection, Collection)}. * * @param repo * repository where objects are stored. * @param imonitor * operations progress monitor, used within * {@link #preparePack(Iterator)}, * {@link #preparePack(Collection, Collection)} * @param wmonitor * operations progress monitor, used within * {@link #writePack(OutputStream)}. */ public PackWriter(final Repository repo, final ProgressMonitor imonitor, final ProgressMonitor wmonitor) { this.db = repo; initMonitor = imonitor == null ? NullProgressMonitor.INSTANCE : imonitor; writeMonitor = wmonitor == null ? NullProgressMonitor.INSTANCE : wmonitor; this.deflater = new Deflater(db.getConfig().getCore().getCompression()); outputVersion = repo.getConfig().getCore().getPackIndexVersion(); } /** * Check whether object is configured to reuse deltas existing in * repository. *

* Default setting: {@value #DEFAULT_REUSE_DELTAS} *

* * @return true if object is configured to reuse deltas; false otherwise. */ public boolean isReuseDeltas() { return reuseDeltas; } /** * Set reuse deltas configuration option for this writer. When enabled, * writer will search for delta representation of object in repository and * use it if possible. Normally, only deltas with base to another object * existing in set of objects to pack will be used. Exception is however * thin-pack (see * {@link #preparePack(Collection, Collection)} and * {@link #preparePack(Iterator)}) where base object must exist on other * side machine. *

* When raw delta data is directly copied from a pack file, checksum is * computed to verify data. *

*

* Default setting: {@value #DEFAULT_REUSE_DELTAS} *

* * @param reuseDeltas * boolean indicating whether or not try to reuse deltas. */ public void setReuseDeltas(boolean reuseDeltas) { this.reuseDeltas = reuseDeltas; } /** * Checks whether object is configured to reuse existing objects * representation in repository. *

* Default setting: {@value #DEFAULT_REUSE_OBJECTS} *

* * @return true if writer is configured to reuse objects representation from * pack; false otherwise. */ public boolean isReuseObjects() { return reuseObjects; } /** * Set reuse objects configuration option for this writer. If enabled, * writer searches for representation in a pack file. If possible, * compressed data is directly copied from such a pack file. Data checksum * is verified. *

* Default setting: {@value #DEFAULT_REUSE_OBJECTS} *

* * @param reuseObjects * boolean indicating whether or not writer should reuse existing * objects representation. */ public void setReuseObjects(boolean reuseObjects) { this.reuseObjects = reuseObjects; } /** * Check whether writer can store delta base as an offset (new style * reducing pack size) or should store it as an object id (legacy style, * compatible with old readers). *

* Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET} *

* * @return true if delta base is stored as an offset; false if it is stored * as an object id. */ public boolean isDeltaBaseAsOffset() { return deltaBaseAsOffset; } /** * Set writer delta base format. Delta base can be written as an offset in a * pack file (new approach reducing file size) or as an object id (legacy * approach, compatible with old readers). *

* Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET} *

* * @param deltaBaseAsOffset * boolean indicating whether delta base can be stored as an * offset. */ public void setDeltaBaseAsOffset(boolean deltaBaseAsOffset) { this.deltaBaseAsOffset = deltaBaseAsOffset; } /** * Get maximum depth of delta chain set up for this writer. Generated chains * are not longer than this value. *

* Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH} *

* * @return maximum delta chain depth. */ public int getMaxDeltaDepth() { return maxDeltaDepth; } /** * Set up maximum depth of delta chain for this writer. Generated chains are * not longer than this value. Too low value causes low compression level, * while too big makes unpacking (reading) longer. *

* Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH} *

* * @param maxDeltaDepth * maximum delta chain depth. */ public void setMaxDeltaDepth(int maxDeltaDepth) { this.maxDeltaDepth = maxDeltaDepth; } /** @return true if this writer is producing a thin pack. */ public boolean isThin() { return thin; } /** * @param packthin * a boolean indicating whether writer may pack objects with * delta base object not within set of objects to pack, but * belonging to party repository (uninteresting/boundary) as * determined by set; this kind of pack is used only for * transport; true - to produce thin pack, false - otherwise. */ public void setThin(final boolean packthin) { thin = packthin; } /** * @return true to ignore objects that are uninteresting and also not found * on local disk; false to throw a {@link MissingObjectException} * out of {@link #preparePack(Collection, Collection)} if an * uninteresting object is not in the source repository. By default, * true, permitting gracefully ignoring of uninteresting objects. */ public boolean isIgnoreMissingUninteresting() { return ignoreMissingUninteresting; } /** * @param ignore * true if writer should ignore non existing uninteresting * objects during construction set of objects to pack; false * otherwise - non existing uninteresting objects may cause * {@link MissingObjectException} */ public void setIgnoreMissingUninteresting(final boolean ignore) { ignoreMissingUninteresting = ignore; } /** * Set the pack index file format version this instance will create. * * @param version * the version to write. The special version 0 designates the * oldest (most compatible) format available for the objects. * @see PackIndexWriter */ public void setIndexVersion(final int version) { outputVersion = version; } /** * Returns objects number in a pack file that was created by this writer. * * @return number of objects in pack. */ public int getObjectsNumber() { return objectsMap.size(); } /** * Prepare the list of objects to be written to the pack stream. *

* Iterator exactly determines which objects are included in a pack * and order they appear in pack (except that objects order by type is not * needed at input). This order should conform general rules of ordering * objects in git - by recency and path (type and delta-base first is * internally secured) and responsibility for guaranteeing this order is on * a caller side. Iterator must return each id of object to write exactly * once. *

*

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

* * @param objectsSource * iterator of object to store in a pack; order of objects within * each type is important, ordering by type is not needed; * allowed types for objects are {@link Constants#OBJ_COMMIT}, * {@link Constants#OBJ_TREE}, {@link Constants#OBJ_BLOB} and * {@link Constants#OBJ_TAG}; objects returned by iterator may * be later reused by caller as object id and type are internally * copied in each iteration; if object returned by iterator has * {@link RevFlag#UNINTERESTING} flag set, it won't be included * in a pack, but is considered as edge-object for thin-pack. * @throws IOException * when some I/O problem occur during reading objects. */ public void preparePack(final Iterator objectsSource) throws IOException { while (objectsSource.hasNext()) { addObject(objectsSource.next()); } } /** * Prepare the list of objects to be written to the pack stream. *

* Basing on these 2 sets, another set of objects to put in a pack file is * created: this set consists of all objects reachable (ancestors) from * interesting objects, except uninteresting objects and their ancestors. * This method uses class {@link ObjectWalk} extensively to find out that * appropriate set of output objects and their optimal order in output pack. * Order is consistent with general git in-pack rules: sort by object type, * recency, path and delta-base first. *

* * @param interestingObjects * collection of objects to be marked as interesting (start * points of graph traversal). * @param uninterestingObjects * collection of objects to be marked as uninteresting (end * points of graph traversal). * @throws IOException * when some I/O problem occur during reading objects. */ public void preparePack( final Collection interestingObjects, final Collection uninterestingObjects) throws IOException { ObjectWalk walker = setUpWalker(interestingObjects, uninterestingObjects); findObjectsToPack(walker); } /** * Determine if the pack file will contain the requested object. * * @param id * the object to test the existence of. * @return true if the object will appear in the output pack file. */ public boolean willInclude(final AnyObjectId id) { return objectsMap.get(id) != null; } /** * Computes SHA-1 of lexicographically sorted objects ids written in this * pack, as used to name a pack file in repository. * * @return ObjectId representing SHA-1 name of a pack that was created. */ public ObjectId computeName() { final MessageDigest md = Constants.newMessageDigest(); for (ObjectToPack otp : sortByName()) { otp.copyRawTo(buf, 0); md.update(buf, 0, Constants.OBJECT_ID_LENGTH); } return ObjectId.fromRaw(md.digest()); } /** * Create an index file to match the pack file just written. *

* This method can only be invoked after {@link #preparePack(Iterator)} or * {@link #preparePack(Collection, Collection)} has been * invoked and completed successfully. Writing a corresponding index is an * optional feature that not all pack users may require. * * @param indexStream * output for the index data. Caller is responsible for closing * this stream. * @throws IOException * the index data could not be written to the supplied stream. */ public void writeIndex(final OutputStream indexStream) throws IOException { final List list = sortByName(); final PackIndexWriter iw; if (outputVersion <= 0) iw = PackIndexWriter.createOldestPossible(indexStream, list); else iw = PackIndexWriter.createVersion(indexStream, outputVersion); iw.write(list, packcsum); } private List sortByName() { if (sortedByName == null) { sortedByName = new ArrayList(objectsMap.size()); for (List list : objectsLists) { for (ObjectToPack otp : list) sortedByName.add(otp); } Collections.sort(sortedByName); } return sortedByName; } /** * Write the prepared pack to the supplied stream. *

* At first, this method collects and sorts objects to pack, then deltas * search is performed if set up accordingly, finally pack stream is * written. {@link ProgressMonitor} tasks {@value #SEARCHING_REUSE_PROGRESS} * (only if reuseDeltas or reuseObjects is enabled) and * {@value #WRITING_OBJECTS_PROGRESS} are updated during packing. *

*

* All reused objects data checksum (Adler32/CRC32) is computed and * validated against existing checksum. *

* * @param packStream * output stream of pack data. The stream should be buffered by * the caller. The caller is responsible for closing the stream. * @throws IOException * an error occurred reading a local object's data to include in * the pack, or writing compressed object data to the output * stream. */ public void writePack(OutputStream packStream) throws IOException { if (reuseDeltas || reuseObjects) searchForReuse(); out = new PackOutputStream(packStream); writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber()); writeHeader(); writeObjects(); writeChecksum(); windowCursor.release(); writeMonitor.endTask(); } private void searchForReuse() throws IOException { initMonitor.beginTask(SEARCHING_REUSE_PROGRESS, getObjectsNumber()); final Collection reuseLoaders = new ArrayList(); for (List list : objectsLists) { for (ObjectToPack otp : list) { if (initMonitor.isCancelled()) throw new IOException( "Packing cancelled during objects writing"); reuseLoaders.clear(); searchForReuse(reuseLoaders, otp); initMonitor.update(1); } } initMonitor.endTask(); } private void searchForReuse( final Collection reuseLoaders, final ObjectToPack otp) throws IOException { db.openObjectInAllPacks(otp, reuseLoaders, windowCursor); if (reuseDeltas) { selectDeltaReuseForObject(otp, reuseLoaders); } // delta reuse is preferred over object reuse if (reuseObjects && !otp.isCopyable()) { selectObjectReuseForObject(otp, reuseLoaders); } } private void selectDeltaReuseForObject(final ObjectToPack otp, final Collection loaders) throws IOException { PackedObjectLoader bestLoader = null; ObjectId bestBase = null; for (PackedObjectLoader loader : loaders) { ObjectId idBase = loader.getDeltaBase(); if (idBase == null) continue; ObjectToPack otpBase = objectsMap.get(idBase); // only if base is in set of objects to write or thin-pack's edge if ((otpBase != null || (thin && edgeObjects.get(idBase) != null)) // select smallest possible delta if > 1 available && isBetterDeltaReuseLoader(bestLoader, loader)) { bestLoader = loader; bestBase = (otpBase != null ? otpBase : idBase); } } if (bestLoader != null) { otp.setCopyFromPack(bestLoader); otp.setDeltaBase(bestBase); } } private static boolean isBetterDeltaReuseLoader( PackedObjectLoader currentLoader, PackedObjectLoader loader) throws IOException { if (currentLoader == null) return true; if (loader.getRawSize() < currentLoader.getRawSize()) return true; return (loader.getRawSize() == currentLoader.getRawSize() && loader.supportsFastCopyRawData() && !currentLoader .supportsFastCopyRawData()); } private void selectObjectReuseForObject(final ObjectToPack otp, final Collection loaders) { for (final PackedObjectLoader loader : loaders) { if (loader instanceof WholePackedObjectLoader) { otp.setCopyFromPack(loader); return; } } } private void writeHeader() throws IOException { System.arraycopy(Constants.PACK_SIGNATURE, 0, buf, 0, 4); NB.encodeInt32(buf, 4, PACK_VERSION_GENERATED); NB.encodeInt32(buf, 8, getObjectsNumber()); out.write(buf, 0, 12); } private void writeObjects() throws IOException { for (List list : objectsLists) { for (ObjectToPack otp : list) { if (writeMonitor.isCancelled()) throw new IOException( "Packing cancelled during objects writing"); if (!otp.isWritten()) writeObject(otp); } } } private void writeObject(final ObjectToPack otp) throws IOException { otp.markWantWrite(); if (otp.isDeltaRepresentation()) { ObjectToPack deltaBase = otp.getDeltaBase(); assert deltaBase != null || thin; if (deltaBase != null && !deltaBase.isWritten()) { if (deltaBase.wantWrite()) { otp.clearDeltaBase(); // cycle detected otp.clearSourcePack(); } else { writeObject(deltaBase); } } } assert !otp.isWritten(); out.resetCRC32(); otp.setOffset(out.length()); final PackedObjectLoader reuse = open(otp); if (reuse != null) { try { if (otp.isDeltaRepresentation()) writeDeltaObjectHeader(otp, reuse); else writeObjectHeader(otp.getType(), reuse.getSize()); reuse.copyRawData(out, buf, windowCursor); } finally { reuse.endCopyRawData(); } } else if (otp.isDeltaRepresentation()) { throw new IOException("creating deltas is not implemented"); } else { writeWholeObjectDeflate(otp); } otp.setCRC(out.getCRC32()); writeMonitor.update(1); } private PackedObjectLoader open(final ObjectToPack otp) throws IOException { while (otp.isCopyable()) { try { PackedObjectLoader reuse = otp.getCopyLoader(windowCursor); reuse.beginCopyRawData(); return reuse; } catch (IOException err) { // The pack we found the object in originally is gone, or // it has been overwritten with a different layout. // otp.clearDeltaBase(); otp.clearSourcePack(); searchForReuse(new ArrayList(), otp); continue; } } return null; } private void writeWholeObjectDeflate(final ObjectToPack otp) throws IOException { final ObjectLoader loader = db.openObject(windowCursor, otp); final byte[] data = loader.getCachedBytes(); writeObjectHeader(otp.getType(), data.length); deflater.reset(); deflater.setInput(data, 0, data.length); deflater.finish(); do { final int n = deflater.deflate(buf, 0, buf.length); if (n > 0) out.write(buf, 0, n); } while (!deflater.finished()); } private void writeDeltaObjectHeader(final ObjectToPack otp, final PackedObjectLoader reuse) throws IOException { if (deltaBaseAsOffset && otp.getDeltaBase() != null) { writeObjectHeader(Constants.OBJ_OFS_DELTA, reuse.getRawSize()); final ObjectToPack deltaBase = otp.getDeltaBase(); long offsetDiff = otp.getOffset() - deltaBase.getOffset(); int pos = buf.length - 1; buf[pos] = (byte) (offsetDiff & 0x7F); while ((offsetDiff >>= 7) > 0) { buf[--pos] = (byte) (0x80 | (--offsetDiff & 0x7F)); } out.write(buf, pos, buf.length - pos); } else { writeObjectHeader(Constants.OBJ_REF_DELTA, reuse.getRawSize()); otp.getDeltaBaseId().copyRawTo(buf, 0); out.write(buf, 0, Constants.OBJECT_ID_LENGTH); } } private void writeObjectHeader(final int objectType, long dataLength) throws IOException { long nextLength = dataLength >>> 4; int size = 0; buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (objectType << 4) | (dataLength & 0x0F)); dataLength = nextLength; while (dataLength > 0) { nextLength >>>= 7; buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (dataLength & 0x7F)); dataLength = nextLength; } out.write(buf, 0, size); } private void writeChecksum() throws IOException { packcsum = out.getDigest(); out.write(packcsum); } private ObjectWalk setUpWalker( final Collection interestingObjects, final Collection uninterestingObjects) throws MissingObjectException, IOException, IncorrectObjectTypeException { final ObjectWalk walker = new ObjectWalk(db); walker.setRetainBody(false); walker.sort(RevSort.TOPO); walker.sort(RevSort.COMMIT_TIME_DESC, true); if (thin) walker.sort(RevSort.BOUNDARY, true); for (ObjectId id : interestingObjects) { RevObject o = walker.parseAny(id); walker.markStart(o); } if (uninterestingObjects != null) { for (ObjectId id : uninterestingObjects) { final RevObject o; try { o = walker.parseAny(id); } catch (MissingObjectException x) { if (ignoreMissingUninteresting) continue; throw x; } walker.markUninteresting(o); } } return walker; } private void findObjectsToPack(final ObjectWalk walker) throws MissingObjectException, IncorrectObjectTypeException, IOException { initMonitor.beginTask(COUNTING_OBJECTS_PROGRESS, ProgressMonitor.UNKNOWN); RevObject o; while ((o = walker.next()) != null) { addObject(o); initMonitor.update(1); } while ((o = walker.nextObject()) != null) { addObject(o); initMonitor.update(1); } initMonitor.endTask(); } /** * Include one object to the output file. *

* Objects are written in the order they are added. If the same object is * added twice, it may be written twice, creating a larger than necessary * file. * * @param object * the object to add. * @throws IncorrectObjectTypeException * the object is an unsupported type. */ public void addObject(final RevObject object) throws IncorrectObjectTypeException { if (object.has(RevFlag.UNINTERESTING)) { edgeObjects.add(object); thin = true; return; } final ObjectToPack otp = new ObjectToPack(object, object.getType()); try { objectsLists[object.getType()].add(otp); } catch (ArrayIndexOutOfBoundsException x) { throw new IncorrectObjectTypeException(object, "COMMIT nor TREE nor BLOB nor TAG"); } catch (UnsupportedOperationException x) { // index pointing to "dummy" empty list throw new IncorrectObjectTypeException(object, "COMMIT nor TREE nor BLOB nor TAG"); } 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; } } }