2 * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * - Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
18 * - Neither the name of the Git Development Community nor the
19 * names of its contributors may be used to endorse or promote
20 * products derived from this software without specific prior
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
24 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
25 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
30 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
33 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
35 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 package org
.spearce
.jgit
.lib
;
40 import java
.io
.BufferedOutputStream
;
41 import java
.io
.IOException
;
42 import java
.io
.OutputStream
;
43 import java
.security
.MessageDigest
;
44 import java
.util
.ArrayList
;
45 import java
.util
.Collection
;
46 import java
.util
.Collections
;
47 import java
.util
.Iterator
;
48 import java
.util
.List
;
49 import java
.util
.zip
.Deflater
;
51 import org
.spearce
.jgit
.errors
.IncorrectObjectTypeException
;
52 import org
.spearce
.jgit
.errors
.MissingObjectException
;
53 import org
.spearce
.jgit
.revwalk
.ObjectWalk
;
54 import org
.spearce
.jgit
.revwalk
.RevFlag
;
55 import org
.spearce
.jgit
.revwalk
.RevObject
;
56 import org
.spearce
.jgit
.revwalk
.RevSort
;
57 import org
.spearce
.jgit
.transport
.PackedObjectInfo
;
58 import org
.spearce
.jgit
.util
.NB
;
62 * PackWriter class is responsible for generating pack files from specified set
63 * of objects from repository. This implementation produce pack files in format
67 * Source of objects may be specified in two ways:
69 * <li>(usually) by providing sets of interesting and uninteresting objects in
70 * repository - all interesting objects and their ancestors except uninteresting
71 * objects and their ancestors will be included in pack, or</li>
72 * <li>by providing iterator of {@link RevObject} specifying exact list and
73 * order of objects in pack</li>
75 * Typical usage consists of creating instance intended for some pack,
76 * configuring options, preparing the list of objects by calling
77 * {@link #preparePack(Iterator)} or
78 * {@link #preparePack(Collection, Collection)}, and finally
79 * producing the stream with {@link #writePack(OutputStream)}.
82 * Class provide set of configurable options and {@link ProgressMonitor}
83 * support, as operations may take a long time for big repositories. Deltas
84 * searching algorithm is <b>NOT IMPLEMENTED</b> yet - this implementation
85 * relies only on deltas and objects reuse.
88 * This class is not thread safe, it is intended to be used in one thread, with
89 * one instance per created pack. Subsequent calls to writePack result in
94 public class PackWriter
{
96 * Title of {@link ProgressMonitor} task used during counting objects to
99 * @see #preparePack(Collection, Collection)
101 public static final String COUNTING_OBJECTS_PROGRESS
= "Counting objects";
104 * Title of {@link ProgressMonitor} task used during searching for objects
105 * reuse or delta reuse.
107 * @see #writePack(OutputStream)
109 public static final String SEARCHING_REUSE_PROGRESS
= "Compressing objects";
112 * Title of {@link ProgressMonitor} task used during writing out pack
115 * @see #writePack(OutputStream)
117 public static final String WRITING_OBJECTS_PROGRESS
= "Writing objects";
120 * Default value of deltas reuse option.
122 * @see #setReuseDeltas(boolean)
124 public static final boolean DEFAULT_REUSE_DELTAS
= true;
127 * Default value of objects reuse option.
129 * @see #setReuseObjects(boolean)
131 public static final boolean DEFAULT_REUSE_OBJECTS
= true;
134 * Default value of delta base as offset option.
136 * @see #setDeltaBaseAsOffset(boolean)
138 public static final boolean DEFAULT_DELTA_BASE_AS_OFFSET
= false;
141 * Default value of maximum delta chain depth.
143 * @see #setMaxDeltaDepth(int)
145 public static final int DEFAULT_MAX_DELTA_DEPTH
= 50;
147 private static final int PACK_VERSION_GENERATED
= 2;
149 @SuppressWarnings("unchecked")
150 private final List
<ObjectToPack
> objectsLists
[] = new List
[Constants
.OBJ_TAG
+ 1];
152 objectsLists
[0] = Collections
.<ObjectToPack
> emptyList();
153 objectsLists
[Constants
.OBJ_COMMIT
] = new ArrayList
<ObjectToPack
>();
154 objectsLists
[Constants
.OBJ_TREE
] = new ArrayList
<ObjectToPack
>();
155 objectsLists
[Constants
.OBJ_BLOB
] = new ArrayList
<ObjectToPack
>();
156 objectsLists
[Constants
.OBJ_TAG
] = new ArrayList
<ObjectToPack
>();
159 private final ObjectIdSubclassMap
<ObjectToPack
> objectsMap
= new ObjectIdSubclassMap
<ObjectToPack
>();
161 // edge objects for thin packs
162 private final ObjectIdSubclassMap
<ObjectId
> edgeObjects
= new ObjectIdSubclassMap
<ObjectId
>();
164 private final Repository db
;
166 private PackOutputStream out
;
168 private final Deflater deflater
;
170 private ProgressMonitor initMonitor
;
172 private ProgressMonitor writeMonitor
;
174 private final byte[] buf
= new byte[16384]; // 16 KB
176 private final WindowCursor windowCursor
= new WindowCursor();
178 private List
<ObjectToPack
> sortedByName
;
180 private byte packcsum
[];
182 private boolean reuseDeltas
= DEFAULT_REUSE_DELTAS
;
184 private boolean reuseObjects
= DEFAULT_REUSE_OBJECTS
;
186 private boolean deltaBaseAsOffset
= DEFAULT_DELTA_BASE_AS_OFFSET
;
188 private int maxDeltaDepth
= DEFAULT_MAX_DELTA_DEPTH
;
190 private int outputVersion
;
192 private boolean thin
;
194 private boolean ignoreMissingUninteresting
= true;
197 * Create writer for specified repository.
199 * Objects for packing are specified in {@link #preparePack(Iterator)} or
200 * {@link #preparePack(Collection, Collection)}.
203 * repository where objects are stored.
205 * operations progress monitor, used within
206 * {@link #preparePack(Iterator)},
207 * {@link #preparePack(Collection, Collection)}
208 * , or {@link #writePack(OutputStream)}.
210 public PackWriter(final Repository repo
, final ProgressMonitor monitor
) {
211 this(repo
, monitor
, monitor
);
215 * Create writer for specified repository.
217 * Objects for packing are specified in {@link #preparePack(Iterator)} or
218 * {@link #preparePack(Collection, Collection)}.
221 * repository where objects are stored.
223 * operations progress monitor, used within
224 * {@link #preparePack(Iterator)},
225 * {@link #preparePack(Collection, Collection)}
227 * operations progress monitor, used within
228 * {@link #writePack(OutputStream)}.
230 public PackWriter(final Repository repo
, final ProgressMonitor imonitor
,
231 final ProgressMonitor wmonitor
) {
233 initMonitor
= imonitor
;
234 writeMonitor
= wmonitor
;
235 this.deflater
= new Deflater(db
.getConfig().getCore().getCompression());
236 outputVersion
= repo
.getConfig().getCore().getPackIndexVersion();
240 * Check whether object is configured to reuse deltas existing in
243 * Default setting: {@value #DEFAULT_REUSE_DELTAS}
246 * @return true if object is configured to reuse deltas; false otherwise.
248 public boolean isReuseDeltas() {
253 * Set reuse deltas configuration option for this writer. When enabled,
254 * writer will search for delta representation of object in repository and
255 * use it if possible. Normally, only deltas with base to another object
256 * existing in set of objects to pack will be used. Exception is however
258 * {@link #preparePack(Collection, Collection)} and
259 * {@link #preparePack(Iterator)}) where base object must exist on other
262 * When raw delta data is directly copied from a pack file, checksum is
263 * computed to verify data.
266 * Default setting: {@value #DEFAULT_REUSE_DELTAS}
270 * boolean indicating whether or not try to reuse deltas.
272 public void setReuseDeltas(boolean reuseDeltas
) {
273 this.reuseDeltas
= reuseDeltas
;
277 * Checks whether object is configured to reuse existing objects
278 * representation in repository.
280 * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
283 * @return true if writer is configured to reuse objects representation from
284 * pack; false otherwise.
286 public boolean isReuseObjects() {
291 * Set reuse objects configuration option for this writer. If enabled,
292 * writer searches for representation in a pack file. If possible,
293 * compressed data is directly copied from such a pack file. Data checksum
296 * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
299 * @param reuseObjects
300 * boolean indicating whether or not writer should reuse existing
301 * objects representation.
303 public void setReuseObjects(boolean reuseObjects
) {
304 this.reuseObjects
= reuseObjects
;
308 * Check whether writer can store delta base as an offset (new style
309 * reducing pack size) or should store it as an object id (legacy style,
310 * compatible with old readers).
312 * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
315 * @return true if delta base is stored as an offset; false if it is stored
318 public boolean isDeltaBaseAsOffset() {
319 return deltaBaseAsOffset
;
323 * Set writer delta base format. Delta base can be written as an offset in a
324 * pack file (new approach reducing file size) or as an object id (legacy
325 * approach, compatible with old readers).
327 * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
330 * @param deltaBaseAsOffset
331 * boolean indicating whether delta base can be stored as an
334 public void setDeltaBaseAsOffset(boolean deltaBaseAsOffset
) {
335 this.deltaBaseAsOffset
= deltaBaseAsOffset
;
339 * Get maximum depth of delta chain set up for this writer. Generated chains
340 * are not longer than this value.
342 * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
345 * @return maximum delta chain depth.
347 public int getMaxDeltaDepth() {
348 return maxDeltaDepth
;
352 * Set up maximum depth of delta chain for this writer. Generated chains are
353 * not longer than this value. Too low value causes low compression level,
354 * while too big makes unpacking (reading) longer.
356 * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
359 * @param maxDeltaDepth
360 * maximum delta chain depth.
362 public void setMaxDeltaDepth(int maxDeltaDepth
) {
363 this.maxDeltaDepth
= maxDeltaDepth
;
366 /** @return true if this writer is producing a thin pack. */
367 public boolean isThin() {
373 * a boolean indicating whether writer may pack objects with
374 * delta base object not within set of objects to pack, but
375 * belonging to party repository (uninteresting/boundary) as
376 * determined by set; this kind of pack is used only for
377 * transport; true - to produce thin pack, false - otherwise.
379 public void setThin(final boolean packthin
) {
384 * @return true to ignore objects that are uninteresting and also not found
385 * on local disk; false to throw a {@link MissingObjectException}
386 * out of {@link #preparePack(Collection, Collection)} if an
387 * uninteresting object is not in the source repository. By default,
388 * true, permitting gracefully ignoring of uninteresting objects.
390 public boolean isIgnoreMissingUninteresting() {
391 return ignoreMissingUninteresting
;
396 * true if writer should ignore non existing uninteresting
397 * objects during construction set of objects to pack; false
398 * otherwise - non existing uninteresting objects may cause
399 * {@link MissingObjectException}
401 public void setIgnoreMissingUninteresting(final boolean ignore
) {
402 ignoreMissingUninteresting
= ignore
;
406 * Set the pack index file format version this instance will create.
409 * the version to write. The special version 0 designates the
410 * oldest (most compatible) format available for the objects.
411 * @see PackIndexWriter
413 public void setIndexVersion(final int version
) {
414 outputVersion
= version
;
418 * Returns objects number in a pack file that was created by this writer.
420 * @return number of objects in pack.
422 public int getObjectsNumber() {
423 return objectsMap
.size();
427 * Prepare the list of objects to be written to the pack stream.
429 * Iterator <b>exactly</b> determines which objects are included in a pack
430 * and order they appear in pack (except that objects order by type is not
431 * needed at input). This order should conform general rules of ordering
432 * objects in git - by recency and path (type and delta-base first is
433 * internally secured) and responsibility for guaranteeing this order is on
434 * a caller side. Iterator must return each id of object to write exactly
438 * When iterator returns object that has {@link RevFlag#UNINTERESTING} flag,
439 * this object won't be included in an output pack. Instead, it is recorded
440 * as edge-object (known to remote repository) for thin-pack. In such a case
441 * writer may pack objects with delta base object not within set of objects
442 * to pack, but belonging to party repository - those marked with
443 * {@link RevFlag#UNINTERESTING} flag. This type of pack is used only for
447 * @param objectsSource
448 * iterator of object to store in a pack; order of objects within
449 * each type is important, ordering by type is not needed;
450 * allowed types for objects are {@link Constants#OBJ_COMMIT},
451 * {@link Constants#OBJ_TREE}, {@link Constants#OBJ_BLOB} and
452 * {@link Constants#OBJ_TAG}; objects returned by iterator may
453 * be later reused by caller as object id and type are internally
454 * copied in each iteration; if object returned by iterator has
455 * {@link RevFlag#UNINTERESTING} flag set, it won't be included
456 * in a pack, but is considered as edge-object for thin-pack.
457 * @throws IOException
458 * when some I/O problem occur during reading objects.
460 public void preparePack(final Iterator
<RevObject
> objectsSource
)
462 while (objectsSource
.hasNext()) {
463 addObject(objectsSource
.next());
468 * Prepare the list of objects to be written to the pack stream.
470 * Basing on these 2 sets, another set of objects to put in a pack file is
471 * created: this set consists of all objects reachable (ancestors) from
472 * interesting objects, except uninteresting objects and their ancestors.
473 * This method uses class {@link ObjectWalk} extensively to find out that
474 * appropriate set of output objects and their optimal order in output pack.
475 * Order is consistent with general git in-pack rules: sort by object type,
476 * recency, path and delta-base first.
479 * @param interestingObjects
480 * collection of objects to be marked as interesting (start
481 * points of graph traversal).
482 * @param uninterestingObjects
483 * collection of objects to be marked as uninteresting (end
484 * points of graph traversal).
485 * @throws IOException
486 * when some I/O problem occur during reading objects.
488 public void preparePack(
489 final Collection
<?
extends ObjectId
> interestingObjects
,
490 final Collection
<?
extends ObjectId
> uninterestingObjects
)
492 ObjectWalk walker
= setUpWalker(interestingObjects
,
493 uninterestingObjects
);
494 findObjectsToPack(walker
);
498 * Determine if the pack file will contain the requested object.
501 * the object to test the existence of.
502 * @return true if the object will appear in the output pack file.
504 public boolean willInclude(final AnyObjectId id
) {
505 return objectsMap
.get(id
) != null;
509 * Computes SHA-1 of lexicographically sorted objects ids written in this
510 * pack, as used to name a pack file in repository.
512 * @return ObjectId representing SHA-1 name of a pack that was created.
514 public ObjectId
computeName() {
515 final MessageDigest md
= Constants
.newMessageDigest();
516 for (ObjectToPack otp
: sortByName()) {
517 otp
.copyRawTo(buf
, 0);
518 md
.update(buf
, 0, Constants
.OBJECT_ID_LENGTH
);
520 return ObjectId
.fromRaw(md
.digest());
524 * Create an index file to match the pack file just written.
526 * This method can only be invoked after {@link #preparePack(Iterator)} or
527 * {@link #preparePack(Collection, Collection)} has been
528 * invoked and completed successfully. Writing a corresponding index is an
529 * optional feature that not all pack users may require.
532 * output for the index data. Caller is responsible for closing
534 * @throws IOException
535 * the index data could not be written to the supplied stream.
537 public void writeIndex(final OutputStream indexStream
) throws IOException
{
538 final List
<ObjectToPack
> list
= sortByName();
539 final PackIndexWriter iw
;
540 if (outputVersion
<= 0)
541 iw
= PackIndexWriter
.createOldestPossible(indexStream
, list
);
543 iw
= PackIndexWriter
.createVersion(indexStream
, outputVersion
);
544 iw
.write(list
, packcsum
);
547 private List
<ObjectToPack
> sortByName() {
548 if (sortedByName
== null) {
549 sortedByName
= new ArrayList
<ObjectToPack
>(objectsMap
.size());
550 for (List
<ObjectToPack
> list
: objectsLists
) {
551 for (ObjectToPack otp
: list
)
552 sortedByName
.add(otp
);
554 Collections
.sort(sortedByName
);
560 * Write the prepared pack to the supplied stream.
562 * At first, this method collects and sorts objects to pack, then deltas
563 * search is performed if set up accordingly, finally pack stream is
564 * written. {@link ProgressMonitor} tasks {@value #SEARCHING_REUSE_PROGRESS}
565 * (only if reuseDeltas or reuseObjects is enabled) and
566 * {@value #WRITING_OBJECTS_PROGRESS} are updated during packing.
569 * All reused objects data checksum (Adler32/CRC32) is computed and
570 * validated against existing checksum.
574 * output stream of pack data. If the stream is not buffered it
575 * will be buffered by the writer. Caller is responsible for
576 * closing the stream.
577 * @throws IOException
578 * an error occurred reading a local object's data to include in
579 * the pack, or writing compressed object data to the output
582 public void writePack(OutputStream packStream
) throws IOException
{
583 if (reuseDeltas
|| reuseObjects
)
586 if (!(packStream
instanceof BufferedOutputStream
))
587 packStream
= new BufferedOutputStream(packStream
);
588 out
= new PackOutputStream(packStream
);
590 writeMonitor
.beginTask(WRITING_OBJECTS_PROGRESS
, getObjectsNumber());
596 windowCursor
.release();
597 writeMonitor
.endTask();
600 private void searchForReuse() throws IOException
{
601 initMonitor
.beginTask(SEARCHING_REUSE_PROGRESS
, getObjectsNumber());
602 final Collection
<PackedObjectLoader
> reuseLoaders
= new ArrayList
<PackedObjectLoader
>();
603 for (List
<ObjectToPack
> list
: objectsLists
) {
604 for (ObjectToPack otp
: list
) {
605 if (initMonitor
.isCancelled())
606 throw new IOException(
607 "Packing cancelled during objects writing");
608 reuseLoaders
.clear();
609 db
.openObjectInAllPacks(otp
, reuseLoaders
, windowCursor
);
611 selectDeltaReuseForObject(otp
, reuseLoaders
);
613 // delta reuse is preferred over object reuse
614 if (reuseObjects
&& !otp
.hasReuseLoader()) {
615 selectObjectReuseForObject(otp
, reuseLoaders
);
617 initMonitor
.update(1);
621 initMonitor
.endTask();
624 private void selectDeltaReuseForObject(final ObjectToPack otp
,
625 final Collection
<PackedObjectLoader
> loaders
) throws IOException
{
626 PackedObjectLoader bestLoader
= null;
627 ObjectId bestBase
= null;
629 for (PackedObjectLoader loader
: loaders
) {
630 ObjectId idBase
= loader
.getDeltaBase();
633 ObjectToPack otpBase
= objectsMap
.get(idBase
);
635 // only if base is in set of objects to write or thin-pack's edge
636 if ((otpBase
!= null || (thin
&& edgeObjects
.get(idBase
) != null))
637 // select smallest possible delta if > 1 available
638 && isBetterDeltaReuseLoader(bestLoader
, loader
)) {
640 bestBase
= (otpBase
!= null ? otpBase
: idBase
);
644 if (bestLoader
!= null) {
645 otp
.setReuseLoader(bestLoader
);
646 otp
.setDeltaBase(bestBase
);
650 private static boolean isBetterDeltaReuseLoader(
651 PackedObjectLoader currentLoader
, PackedObjectLoader loader
)
653 if (currentLoader
== null)
655 if (loader
.getRawSize() < currentLoader
.getRawSize())
657 return (loader
.getRawSize() == currentLoader
.getRawSize()
658 && loader
.supportsFastCopyRawData() && !currentLoader
659 .supportsFastCopyRawData());
662 private void selectObjectReuseForObject(final ObjectToPack otp
,
663 final Collection
<PackedObjectLoader
> loaders
) {
664 for (final PackedObjectLoader loader
: loaders
) {
665 if (loader
instanceof WholePackedObjectLoader
) {
666 otp
.setReuseLoader(loader
);
672 private void writeHeader() throws IOException
{
673 System
.arraycopy(Constants
.PACK_SIGNATURE
, 0, buf
, 0, 4);
674 NB
.encodeInt32(buf
, 4, PACK_VERSION_GENERATED
);
675 NB
.encodeInt32(buf
, 8, getObjectsNumber());
676 out
.write(buf
, 0, 12);
679 private void writeObjects() throws IOException
{
680 for (List
<ObjectToPack
> list
: objectsLists
) {
681 for (ObjectToPack otp
: list
) {
682 if (writeMonitor
.isCancelled())
683 throw new IOException(
684 "Packing cancelled during objects writing");
685 if (!otp
.isWritten())
691 private void writeObject(final ObjectToPack otp
) throws IOException
{
693 if (otp
.isDeltaRepresentation()) {
694 ObjectToPack deltaBase
= otp
.getDeltaBase();
695 assert deltaBase
!= null || thin
;
696 if (deltaBase
!= null && !deltaBase
.isWritten()) {
697 if (deltaBase
.wantWrite()) {
698 otp
.clearDeltaBase(); // cycle detected
701 writeObject(deltaBase
);
706 assert !otp
.isWritten();
709 otp
.setOffset(out
.length());
710 if (otp
.isDeltaRepresentation())
711 writeDeltaObject(otp
);
713 writeWholeObject(otp
);
714 otp
.setCRC(out
.getCRC32());
716 writeMonitor
.update(1);
719 private void writeWholeObject(final ObjectToPack otp
) throws IOException
{
720 if (otp
.hasReuseLoader()) {
721 final PackedObjectLoader loader
= otp
.getReuseLoader();
722 writeObjectHeader(otp
.getType(), loader
.getSize());
723 loader
.copyRawData(out
, buf
);
726 final ObjectLoader loader
= db
.openObject(windowCursor
, otp
);
727 final byte[] data
= loader
.getCachedBytes();
728 writeObjectHeader(otp
.getType(), data
.length
);
730 deflater
.setInput(data
, 0, data
.length
);
733 final int n
= deflater
.deflate(buf
, 0, buf
.length
);
735 out
.write(buf
, 0, n
);
736 } while (!deflater
.finished());
740 private void writeDeltaObject(final ObjectToPack otp
) throws IOException
{
741 final PackedObjectLoader loader
= otp
.getReuseLoader();
742 if (deltaBaseAsOffset
&& otp
.getDeltaBase() != null) {
743 writeObjectHeader(Constants
.OBJ_OFS_DELTA
, loader
.getRawSize());
745 final ObjectToPack deltaBase
= otp
.getDeltaBase();
746 long offsetDiff
= otp
.getOffset() - deltaBase
.getOffset();
747 int pos
= buf
.length
- 1;
748 buf
[pos
] = (byte) (offsetDiff
& 0x7F);
749 while ((offsetDiff
>>= 7) > 0) {
750 buf
[--pos
] = (byte) (0x80 | (--offsetDiff
& 0x7F));
753 out
.write(buf
, pos
, buf
.length
- pos
);
755 writeObjectHeader(Constants
.OBJ_REF_DELTA
, loader
.getRawSize());
756 otp
.getDeltaBaseId().copyRawTo(buf
, 0);
757 out
.write(buf
, 0, Constants
.OBJECT_ID_LENGTH
);
759 loader
.copyRawData(out
, buf
);
763 private void writeObjectHeader(final int objectType
, long dataLength
)
765 long nextLength
= dataLength
>>> 4;
767 buf
[size
++] = (byte) ((nextLength
> 0 ?
0x80 : 0x00)
768 | (objectType
<< 4) | (dataLength
& 0x0F));
769 dataLength
= nextLength
;
770 while (dataLength
> 0) {
772 buf
[size
++] = (byte) ((nextLength
> 0 ?
0x80 : 0x00) | (dataLength
& 0x7F));
773 dataLength
= nextLength
;
775 out
.write(buf
, 0, size
);
778 private void writeChecksum() throws IOException
{
779 packcsum
= out
.getDigest();
783 private ObjectWalk
setUpWalker(
784 final Collection
<?
extends ObjectId
> interestingObjects
,
785 final Collection
<?
extends ObjectId
> uninterestingObjects
)
786 throws MissingObjectException
, IOException
,
787 IncorrectObjectTypeException
{
788 final ObjectWalk walker
= new ObjectWalk(db
);
789 walker
.sort(RevSort
.TOPO
);
790 walker
.sort(RevSort
.COMMIT_TIME_DESC
, true);
792 walker
.sort(RevSort
.BOUNDARY
, true);
794 for (ObjectId id
: interestingObjects
) {
795 RevObject o
= walker
.parseAny(id
);
798 for (ObjectId id
: uninterestingObjects
) {
801 o
= walker
.parseAny(id
);
802 } catch (MissingObjectException x
) {
803 if (ignoreMissingUninteresting
)
807 walker
.markUninteresting(o
);
812 private void findObjectsToPack(final ObjectWalk walker
)
813 throws MissingObjectException
, IncorrectObjectTypeException
,
815 initMonitor
.beginTask(COUNTING_OBJECTS_PROGRESS
,
816 ProgressMonitor
.UNKNOWN
);
819 while ((o
= walker
.next()) != null) {
822 initMonitor
.update(1);
824 while ((o
= walker
.nextObject()) != null) {
827 initMonitor
.update(1);
829 initMonitor
.endTask();
833 * Include one object to the output file.
835 * Objects are written in the order they are added. If the same object is
836 * added twice, it may be written twice, creating a larger than necessary
841 * @throws IncorrectObjectTypeException
842 * the object is an unsupported type.
844 public void addObject(final RevObject object
)
845 throws IncorrectObjectTypeException
{
846 if (object
.has(RevFlag
.UNINTERESTING
)) {
847 edgeObjects
.add(object
);
852 final ObjectToPack otp
= new ObjectToPack(object
, object
.getType());
854 objectsLists
[object
.getType()].add(otp
);
855 } catch (ArrayIndexOutOfBoundsException x
) {
856 throw new IncorrectObjectTypeException(object
,
857 "COMMIT nor TREE nor BLOB nor TAG");
858 } catch (UnsupportedOperationException x
) {
859 // index pointing to "dummy" empty list
860 throw new IncorrectObjectTypeException(object
,
861 "COMMIT nor TREE nor BLOB nor TAG");
867 * Class holding information about object that is going to be packed by
868 * {@link PackWriter}. Information include object representation in a
869 * pack-file and object status.
872 static class ObjectToPack
extends PackedObjectInfo
{
873 private ObjectId deltaBase
;
875 private PackedObjectLoader reuseLoader
;
878 * Bit field, from bit 0 to bit 31:
880 * <li>1 bit: wantWrite</li>
881 * <li>3 bits: type</li>
882 * <li>28 bits: deltaDepth</li>
888 * Construct object for specified object id. <br/> By default object is
889 * marked as not written and non-delta packed (as a whole object).
892 * object id of object for packing
894 * real type code of the object, not its in-pack type.
896 ObjectToPack(AnyObjectId src
, final int type
) {
902 * @return delta base object id if object is going to be packed in delta
903 * representation; null otherwise - if going to be packed as a
906 ObjectId
getDeltaBaseId() {
911 * @return delta base object to pack if object is going to be packed in
912 * delta representation and delta is specified as object to
913 * pack; null otherwise - if going to be packed as a whole
914 * object or delta base is specified only as id.
916 ObjectToPack
getDeltaBase() {
917 if (deltaBase
instanceof ObjectToPack
)
918 return (ObjectToPack
) deltaBase
;
923 * Set delta base for the object. Delta base set by this method is used
924 * by {@link PackWriter} to write object - determines its representation
928 * delta base object or null if object should be packed as a
932 void setDeltaBase(ObjectId deltaBase
) {
933 this.deltaBase
= deltaBase
;
936 void clearDeltaBase() {
937 this.deltaBase
= null;
941 * @return true if object is going to be written as delta; false
944 boolean isDeltaRepresentation() {
945 return deltaBase
!= null;
949 * Check if object is already written in a pack. This information is
950 * used to achieve delta-base precedence in a pack file.
952 * @return true if object is already written; false otherwise.
954 boolean isWritten() {
955 return getOffset() != 0;
958 PackedObjectLoader
getReuseLoader() {
962 boolean hasReuseLoader() {
963 return reuseLoader
!= null;
966 void setReuseLoader(PackedObjectLoader reuseLoader
) {
967 this.reuseLoader
= reuseLoader
;
970 void disposeLoader() {
971 this.reuseLoader
= null;
975 return (flags
>>1) & 0x7;
978 int getDeltaDepth() {
982 void updateDeltaDepth() {
984 if (deltaBase
instanceof ObjectToPack
)
985 d
= ((ObjectToPack
) deltaBase
).getDeltaDepth() + 1;
986 else if (deltaBase
!= null)
990 flags
= (d
<< 4) | flags
& 0x15;
993 boolean wantWrite() {
994 return (flags
& 1) == 1;
997 void markWantWrite() {