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
.IOException
;
41 import java
.io
.OutputStream
;
42 import java
.security
.DigestOutputStream
;
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
.LinkedList
;
49 import java
.util
.List
;
50 import java
.util
.zip
.Deflater
;
51 import java
.util
.zip
.DeflaterOutputStream
;
53 import org
.spearce
.jgit
.errors
.IncorrectObjectTypeException
;
54 import org
.spearce
.jgit
.errors
.MissingObjectException
;
55 import org
.spearce
.jgit
.revwalk
.ObjectWalk
;
56 import org
.spearce
.jgit
.revwalk
.RevFlag
;
57 import org
.spearce
.jgit
.revwalk
.RevObject
;
58 import org
.spearce
.jgit
.revwalk
.RevSort
;
59 import org
.spearce
.jgit
.util
.CountingOutputStream
;
60 import org
.spearce
.jgit
.util
.NB
;
64 * PackWriter class is responsible for generating pack files from specified set
65 * of objects from repository. This implementation produce pack files in format
69 * Source of objects may be specified in two ways:
71 * <li>(usually) by providing sets of interesting and uninteresting objects in
72 * repository - all interesting objects and their ancestors except uninteresting
73 * objects and their ancestors will be included in pack, or</li>
74 * <li>by providing iterator of {@link RevObject} specifying exact list and
75 * order of objects in pack</li>
77 * Typical usage consists of creating instance intended for some pack,
78 * configuring options through accessors methods and finally call
79 * {@link #writePack(Iterator)} or
80 * {@link #writePack(Collection, Collection, boolean, boolean)} with objects
81 * specification, to generate a pack stream.
84 * Class provide set of configurable options and {@link ProgressMonitor}
85 * support, as operations may take a long time for big repositories. Deltas
86 * searching algorithm is <b>NOT IMPLEMENTED</b> yet - this implementation
87 * relies only on deltas and objects reuse.
90 * This class is not thread safe, it is intended to be used in one thread, with
91 * one instance per created pack. Subsequent calls to writePack result in
96 public class PackWriter
{
98 * Title of {@link ProgressMonitor} task used during counting objects to
101 * @see #writePack(Collection, Collection, boolean, boolean)
103 public static final String COUNTING_OBJECTS_PROGRESS
= "Counting objects to pack";
106 * Title of {@link ProgressMonitor} task used during searching for objects
107 * reuse or delta reuse.
109 * @see #writePack(Iterator)
110 * @see #writePack(Collection, Collection, boolean, boolean)
112 public static final String SEARCHING_REUSE_PROGRESS
= "Searching for delta and object reuse";
115 * Title of {@link ProgressMonitor} task used during writing out pack
118 * @see #writePack(Iterator)
119 * @see #writePack(Collection, Collection, boolean, boolean)
121 public static final String WRITING_OBJECTS_PROGRESS
= "Writing objects";
124 * Default value of deltas reuse option.
126 * @see #setReuseDeltas(boolean)
128 public static final boolean DEFAULT_REUSE_DELTAS
= true;
131 * Default value of objects reuse option.
133 * @see #setReuseObjects(boolean)
135 public static final boolean DEFAULT_REUSE_OBJECTS
= true;
138 * Default value of delta base as offset option.
140 * @see #setDeltaBaseAsOffset(boolean)
142 public static final boolean DEFAULT_DELTA_BASE_AS_OFFSET
= false;
145 * Default value of maximum delta chain depth.
147 * @see #setMaxDeltaDepth(int)
149 public static final int DEFAULT_MAX_DELTA_DEPTH
= 50;
151 private static final int PACK_VERSION_GENERATED
= 2;
153 @SuppressWarnings("unchecked")
154 private final List
<ObjectToPack
> objectsLists
[] = new List
[Constants
.OBJ_TAG
+ 1];
156 objectsLists
[0] = Collections
.<ObjectToPack
> emptyList();
157 objectsLists
[Constants
.OBJ_COMMIT
] = new ArrayList
<ObjectToPack
>();
158 objectsLists
[Constants
.OBJ_TREE
] = new ArrayList
<ObjectToPack
>();
159 objectsLists
[Constants
.OBJ_BLOB
] = new ArrayList
<ObjectToPack
>();
160 objectsLists
[Constants
.OBJ_TAG
] = new ArrayList
<ObjectToPack
>();
163 private final ObjectIdSubclassMap
<ObjectToPack
> objectsMap
= new ObjectIdSubclassMap
<ObjectToPack
>();
165 // edge objects for thin packs
166 private final ObjectIdSubclassMap
<ObjectId
> edgeObjects
= new ObjectIdSubclassMap
<ObjectId
>();
168 private final Repository db
;
170 private final DigestOutputStream out
;
172 private final CountingOutputStream countingOut
;
174 private final Deflater deflater
;
176 private final ProgressMonitor monitor
;
178 private final byte[] buf
= new byte[16384]; // 16 KB
180 private final WindowCursor windowCursor
= new WindowCursor();
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 boolean thin
;
193 * Create writer for specified repository, that will write a pack to
194 * provided output stream. Objects for packing are specified in
195 * {@link #writePack(Iterator)} or
196 * {@link #writePack(Collection, Collection, boolean, boolean)}.
199 * repository where objects are stored.
201 * output stream of pack data; no buffering is guaranteed by
204 * operations progress monitor, used within
205 * {@link #writePack(Iterator)} or
206 * {@link #writePack(Collection, Collection, boolean, boolean)}.
208 public PackWriter(final Repository repo
, final OutputStream out
,
209 final ProgressMonitor monitor
) {
211 this.monitor
= monitor
;
212 this.countingOut
= new CountingOutputStream(out
);
213 this.out
= new DigestOutputStream(countingOut
, Constants
214 .newMessageDigest());
215 this.deflater
= new Deflater(db
.getConfig().getCore().getCompression());
219 * Check whether object is configured to reuse deltas existing in
222 * Default setting: {@value #DEFAULT_REUSE_DELTAS}
225 * @return true if object is configured to reuse deltas; false otherwise.
227 public boolean isReuseDeltas() {
232 * Set reuse deltas configuration option for this writer. When enabled,
233 * writer will search for delta representation of object in repository and
234 * use it if possible. Normally, only deltas with base to another object
235 * existing in set of objects to pack will be used. Exception is however
237 * {@link #writePack(Collection, Collection, boolean, boolean)} and
238 * {@link #writePack(Iterator)}) where base object must exist on other side
241 * When raw delta data is directly copied from a pack file, checksum is
242 * computed to verify data.
245 * Default setting: {@value #DEFAULT_REUSE_DELTAS}
249 * boolean indicating whether or not try to reuse deltas.
251 public void setReuseDeltas(boolean reuseDeltas
) {
252 this.reuseDeltas
= reuseDeltas
;
256 * Checks whether object is configured to reuse existing objects
257 * representation in repository.
259 * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
262 * @return true if writer is configured to reuse objects representation from
263 * pack; false otherwise.
265 public boolean isReuseObjects() {
270 * Set reuse objects configuration option for this writer. If enabled,
271 * writer searches for representation in a pack file. If possible,
272 * compressed data is directly copied from such a pack file. Data checksum
275 * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
278 * @param reuseObjects
279 * boolean indicating whether or not writer should reuse existing
280 * objects representation.
282 public void setReuseObjects(boolean reuseObjects
) {
283 this.reuseObjects
= reuseObjects
;
287 * Check whether writer can store delta base as an offset (new style
288 * reducing pack size) or should store it as an object id (legacy style,
289 * compatible with old readers).
291 * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
294 * @return true if delta base is stored as an offset; false if it is stored
297 public boolean isDeltaBaseAsOffset() {
298 return deltaBaseAsOffset
;
302 * Set writer delta base format. Delta base can be written as an offset in a
303 * pack file (new approach reducing file size) or as an object id (legacy
304 * approach, compatible with old readers).
306 * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
309 * @param deltaBaseAsOffset
310 * boolean indicating whether delta base can be stored as an
313 public void setDeltaBaseAsOffset(boolean deltaBaseAsOffset
) {
314 this.deltaBaseAsOffset
= deltaBaseAsOffset
;
318 * Get maximum depth of delta chain set up for this writer. Generated chains
319 * are not longer than this value.
321 * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
324 * @return maximum delta chain depth.
326 public int getMaxDeltaDepth() {
327 return maxDeltaDepth
;
331 * Set up maximum depth of delta chain for this writer. Generated chains are
332 * not longer than this value. Too low value causes low compression level,
333 * while too big makes unpacking (reading) longer.
335 * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
338 * @param maxDeltaDepth
339 * maximum delta chain depth.
341 public void setMaxDeltaDepth(int maxDeltaDepth
) {
342 this.maxDeltaDepth
= maxDeltaDepth
;
346 * Returns objects number in a pack file that was created by this writer.
348 * @return number of objects in pack.
350 public int getObjectsNumber() {
351 return objectsMap
.size();
355 * Write pack to output stream according to current writer configuration for
356 * provided source iterator of objects.
358 * Iterator <b>exactly</b> determines which objects are included in a pack
359 * and order they appear in pack (except that objects order by type is not
360 * needed at input). This order should conform general rules of ordering
361 * objects in git - by recency and path (type and delta-base first is
362 * internally secured) and responsibility for guaranteeing this order is on
363 * a caller side. Iterator must return each id of object to write exactly
367 * When iterator returns object that has {@link RevFlag#UNINTERESTING} flag,
368 * this object won't be included in an output pack. Instead, it is recorded
369 * as edge-object (known to remote repository) for thin-pack. In such a case
370 * writer may pack objects with delta base object not within set of objects
371 * to pack, but belonging to party repository - those marked with
372 * {@link RevFlag#UNINTERESTING} flag. This type of pack is used only for
376 * At first, this method collects and sorts objects to pack, then deltas
377 * search is performed if set up accordingly, finally pack stream is
378 * written. {@link ProgressMonitor} tasks {@value #SEARCHING_REUSE_PROGRESS}
379 * (only if resueDeltas or reuseObjects is enabled) and
380 * {@value #WRITING_OBJECTS_PROGRESS} are updated during packing.
383 * All reused objects data checksum (Adler32/CRC32) is computed and
384 * validated against existing checksum.
387 * @param objectsSource
388 * iterator of object to store in a pack; order of objects within
389 * each type is important, ordering by type is not needed;
390 * allowed types for objects are {@link Constants#OBJ_COMMIT},
391 * {@link Constants#OBJ_TREE}, {@link Constants#OBJ_BLOB} and
392 * {@link Constants#OBJ_TAG}; objects returned by iterator may
393 * be later reused by caller as object id and type are internally
394 * copied in each iteration; if object returned by iterator has
395 * {@link RevFlag#UNINTERESTING} flag set, it won't be included
396 * in a pack, but is considered as edge-object for thin-pack.
397 * @throws IOException
398 * when some I/O problem occur during reading objects for pack
399 * or writing pack stream.
401 public void writePack(final Iterator
<RevObject
> objectsSource
)
403 while (objectsSource
.hasNext()) {
404 addObject(objectsSource
.next());
410 * Write pack to output stream according to current writer configuration for
411 * provided sets of interesting and uninteresting objects.
413 * Basing on these 2 sets, another set of objects to put in a pack file is
414 * created: this set consists of all objects reachable (ancestors) from
415 * interesting objects, except uninteresting objects and their ancestors.
416 * This method uses class {@link ObjectWalk} extensively to find out that
417 * appropriate set of output objects and their optimal order in output pack.
418 * Order is consistent with general git in-pack rules: sort by object type,
419 * recency, path and delta-base first.
422 * At first, this method collects and sorts objects to pack, then deltas
423 * search is performed if set up accordingly, finally pack stream is
424 * written. {@link ProgressMonitor} tasks
425 * {@value #COUNTING_OBJECTS_PROGRESS}, {@value #SEARCHING_REUSE_PROGRESS}
426 * (only if resueDeltas or reuseObjects is enabled) and
427 * {@value #WRITING_OBJECTS_PROGRESS} are updated during packing.
430 * All reused objects data checksum (Adler32/CRC32) is computed and
431 * validated against existing checksum.
434 * @param interestingObjects
435 * collection of objects to be marked as interesting (start
436 * points of graph traversal).
437 * @param uninterestingObjects
438 * collection of objects to be marked as uninteresting (end
439 * points of graph traversal).
441 * a boolean indicating whether writer may pack objects with
442 * delta base object not within set of objects to pack, but
443 * belonging to party repository (uninteresting/boundary) as
444 * determined by set; this kind of pack is used only for
445 * transport; true - to produce thin pack, false - otherwise.
446 * @param ignoreMissingUninteresting
447 * true if writer should ignore non existing uninteresting
448 * objects during construction set of objects to pack; false
449 * otherwise - non existing uninteresting objects may cause
450 * {@link MissingObjectException}
451 * @throws IOException
452 * when some I/O problem occur during reading objects for pack
453 * or writing pack stream.
455 public void writePack(final Collection
<ObjectId
> interestingObjects
,
456 final Collection
<ObjectId
> uninterestingObjects
,
457 final boolean thin
, final boolean ignoreMissingUninteresting
)
459 ObjectWalk walker
= setUpWalker(interestingObjects
,
460 uninterestingObjects
, thin
, ignoreMissingUninteresting
);
461 findObjectsToPack(walker
);
466 * Computes SHA-1 of lexicographically sorted objects ids written in this
467 * pack, as used to name a pack file in repository.
469 * @return ObjectId representing SHA-1 name of a pack that was created.
471 public ObjectId
computeName() {
472 final ArrayList
<ObjectToPack
> sorted
= new ArrayList
<ObjectToPack
>(
474 for (List
<ObjectToPack
> list
: objectsLists
) {
475 for (ObjectToPack otp
: list
)
479 final MessageDigest md
= Constants
.newMessageDigest();
480 Collections
.sort(sorted
);
481 for (ObjectToPack otp
: sorted
) {
482 otp
.copyRawTo(buf
, 0);
483 md
.update(buf
, 0, Constants
.OBJECT_ID_LENGTH
);
485 return ObjectId
.fromRaw(md
.digest());
488 private void writePackInternal() throws IOException
{
489 if (reuseDeltas
|| reuseObjects
)
492 monitor
.beginTask(WRITING_OBJECTS_PROGRESS
, getObjectsNumber());
498 windowCursor
.release();
502 private void searchForReuse() throws IOException
{
503 monitor
.beginTask(SEARCHING_REUSE_PROGRESS
, getObjectsNumber());
504 final Collection
<PackedObjectLoader
> reuseLoaders
= new LinkedList
<PackedObjectLoader
>();
506 for (List
<ObjectToPack
> list
: objectsLists
) {
507 for (ObjectToPack otp
: list
) {
508 if (monitor
.isCancelled())
509 throw new IOException(
510 "Packing cancelled during objects writing");
511 reuseLoaders
.clear();
512 db
.openObjectInAllPacks(otp
, reuseLoaders
, windowCursor
);
514 selectDeltaReuseForObject(otp
, reuseLoaders
);
516 // delta reuse is preferred over object reuse
517 if (reuseObjects
&& !otp
.hasReuseLoader()) {
518 selectObjectReuseForObject(otp
, reuseLoaders
);
527 private void selectDeltaReuseForObject(final ObjectToPack otp
,
528 final Collection
<PackedObjectLoader
> loaders
) throws IOException
{
529 PackedObjectLoader bestLoader
= null;
530 ObjectId bestBase
= null;
532 for (PackedObjectLoader loader
: loaders
) {
533 ObjectId idBase
= loader
.getDeltaBase();
536 ObjectToPack otpBase
= objectsMap
.get(idBase
);
538 // only if base is in set of objects to write or thin-pack's edge
539 if ((otpBase
!= null || (thin
&& edgeObjects
.get(idBase
) != null))
540 // select smallest possible delta if > 1 available
541 && isBetterDeltaReuseLoader(bestLoader
, loader
)) {
543 bestBase
= (otpBase
!= null ? otpBase
: idBase
);
547 if (bestLoader
!= null) {
548 otp
.setReuseLoader(bestLoader
);
549 otp
.setDeltaBase(bestBase
);
553 private static boolean isBetterDeltaReuseLoader(
554 PackedObjectLoader currentLoader
, PackedObjectLoader loader
)
556 if (currentLoader
== null)
558 if (loader
.getRawSize() < currentLoader
.getRawSize())
560 return (loader
.getRawSize() == currentLoader
.getRawSize()
561 && loader
.supportsFastCopyRawData() && !currentLoader
562 .supportsFastCopyRawData());
565 private void selectObjectReuseForObject(final ObjectToPack otp
,
566 final Collection
<PackedObjectLoader
> loaders
) {
567 for (final PackedObjectLoader loader
: loaders
) {
568 if (loader
instanceof WholePackedObjectLoader
) {
569 otp
.setReuseLoader(loader
);
575 private void writeHeader() throws IOException
{
576 out
.write(Constants
.PACK_SIGNATURE
);
578 NB
.encodeInt32(buf
, 0, PACK_VERSION_GENERATED
);
579 out
.write(buf
, 0, 4);
581 NB
.encodeInt32(buf
, 0, getObjectsNumber());
582 out
.write(buf
, 0, 4);
585 private void writeObjects() throws IOException
{
586 for (List
<ObjectToPack
> list
: objectsLists
) {
587 for (ObjectToPack otp
: list
) {
588 if (monitor
.isCancelled())
589 throw new IOException(
590 "Packing cancelled during objects writing");
591 if (!otp
.isWritten())
597 private void writeObject(final ObjectToPack otp
) throws IOException
{
599 if (otp
.isDeltaRepresentation()) {
600 ObjectToPack deltaBase
= otp
.getDeltaBase();
601 assert deltaBase
!= null || thin
;
602 if (deltaBase
!= null && !deltaBase
.isWritten()) {
603 if (deltaBase
.wantWrite()) {
604 otp
.clearDeltaBase(); // cycle detected
607 writeObject(deltaBase
);
611 otp
.updateDeltaDepth();
612 if (otp
.getDeltaDepth() > maxDeltaDepth
) {
613 otp
.clearDeltaBase();
618 assert !otp
.isWritten();
620 otp
.markWritten(countingOut
.getCount());
621 if (otp
.isDeltaRepresentation())
622 writeDeltaObject(otp
);
624 writeWholeObject(otp
);
629 private void writeWholeObject(final ObjectToPack otp
) throws IOException
{
630 if (otp
.hasReuseLoader()) {
631 final PackedObjectLoader loader
= otp
.getReuseLoader();
632 writeObjectHeader(loader
.getType(), loader
.getSize());
633 loader
.copyRawData(out
, buf
);
636 final ObjectLoader loader
= db
.openObject(windowCursor
, otp
);
637 final DeflaterOutputStream deflaterOut
= new DeflaterOutputStream(
639 writeObjectHeader(loader
.getType(), loader
.getSize());
640 deflaterOut
.write(loader
.getCachedBytes());
641 deflaterOut
.finish();
646 private void writeDeltaObject(final ObjectToPack otp
) throws IOException
{
647 final PackedObjectLoader loader
= otp
.getReuseLoader();
648 if (deltaBaseAsOffset
&& otp
.getDeltaBase() != null) {
649 writeObjectHeader(Constants
.OBJ_OFS_DELTA
, loader
.getRawSize());
651 final ObjectToPack deltaBase
= otp
.getDeltaBase();
652 long offsetDiff
= otp
.getOffset() - deltaBase
.getOffset();
653 int pos
= buf
.length
- 1;
654 buf
[pos
] = (byte) (offsetDiff
& 0x7F);
655 while ((offsetDiff
>>= 7) > 0) {
656 buf
[--pos
] = (byte) (0x80 | (--offsetDiff
& 0x7F));
659 out
.write(buf
, pos
, buf
.length
- pos
);
661 writeObjectHeader(Constants
.OBJ_REF_DELTA
, loader
.getRawSize());
662 otp
.getDeltaBaseId().copyRawTo(buf
, 0);
663 out
.write(buf
, 0, Constants
.OBJECT_ID_LENGTH
);
665 loader
.copyRawData(out
, buf
);
669 private void writeObjectHeader(final int objectType
, long dataLength
)
671 long nextLength
= dataLength
>>> 4;
673 buf
[size
++] = (byte) ((nextLength
> 0 ?
0x80 : 0x00)
674 | (objectType
<< 4) | (dataLength
& 0x0F));
675 dataLength
= nextLength
;
676 while (dataLength
> 0) {
678 buf
[size
++] = (byte) ((nextLength
> 0 ?
0x80 : 0x00) | (dataLength
& 0x7F));
679 dataLength
= nextLength
;
681 out
.write(buf
, 0, size
);
684 private void writeChecksum() throws IOException
{
686 final byte checksum
[] = out
.getMessageDigest().digest();
690 private ObjectWalk
setUpWalker(
691 final Collection
<ObjectId
> interestingObjects
,
692 final Collection
<ObjectId
> uninterestingObjects
,
693 final boolean thin
, final boolean ignoreMissingUninteresting
)
694 throws MissingObjectException
, IOException
,
695 IncorrectObjectTypeException
{
696 final ObjectWalk walker
= new ObjectWalk(db
);
697 walker
.sort(RevSort
.TOPO
, true);
698 walker
.sort(RevSort
.COMMIT_TIME_DESC
, true);
700 walker
.sort(RevSort
.BOUNDARY
);
702 for (ObjectId id
: interestingObjects
) {
703 RevObject o
= walker
.parseAny(id
);
706 for (ObjectId id
: uninterestingObjects
) {
709 o
= walker
.parseAny(id
);
710 } catch (MissingObjectException x
) {
711 if (ignoreMissingUninteresting
)
715 walker
.markUninteresting(o
);
720 private void findObjectsToPack(final ObjectWalk walker
)
721 throws MissingObjectException
, IncorrectObjectTypeException
,
723 monitor
.beginTask(COUNTING_OBJECTS_PROGRESS
, ProgressMonitor
.UNKNOWN
);
726 while ((o
= walker
.next()) != null) {
730 while ((o
= walker
.nextObject()) != null) {
737 private void addObject(RevObject object
)
738 throws IncorrectObjectTypeException
{
739 if (object
.has(RevFlag
.UNINTERESTING
)) {
740 edgeObjects
.add(object
);
745 final ObjectToPack otp
= new ObjectToPack(object
);
747 objectsLists
[object
.getType()].add(otp
);
748 } catch (ArrayIndexOutOfBoundsException x
) {
749 throw new IncorrectObjectTypeException(object
,
750 "COMMIT nor TREE nor BLOB nor TAG");
751 } catch (UnsupportedOperationException x
) {
752 // index pointing to "dummy" empty list
753 throw new IncorrectObjectTypeException(object
,
754 "COMMIT nor TREE nor BLOB nor TAG");
760 * Class holding information about object that is going to be packed by
761 * {@link PackWriter}. Information include object representation in a
762 * pack-file and object status.
765 static class ObjectToPack
extends ObjectId
{
766 private ObjectId deltaBase
;
768 private PackedObjectLoader reuseLoader
;
770 private long offset
= -1;
772 private int deltaDepth
;
774 private boolean wantWrite
;
777 * Construct object for specified object id. <br/> By default object is
778 * marked as not written and non-delta packed (as a whole object).
781 * object id of object for packing
783 ObjectToPack(AnyObjectId src
) {
788 * @return delta base object id if object is going to be packed in delta
789 * representation; null otherwise - if going to be packed as a
792 ObjectId
getDeltaBaseId() {
797 * @return delta base object to pack if object is going to be packed in
798 * delta representation and delta is specified as object to
799 * pack; null otherwise - if going to be packed as a whole
800 * object or delta base is specified only as id.
802 ObjectToPack
getDeltaBase() {
803 if (deltaBase
instanceof ObjectToPack
)
804 return (ObjectToPack
) deltaBase
;
809 * Set delta base for the object. Delta base set by this method is used
810 * by {@link PackWriter} to write object - determines its representation
814 * delta base object or null if object should be packed as a
818 void setDeltaBase(ObjectId deltaBase
) {
819 this.deltaBase
= deltaBase
;
822 void clearDeltaBase() {
823 this.deltaBase
= null;
827 * @return true if object is going to be written as delta; false
830 boolean isDeltaRepresentation() {
831 return deltaBase
!= null;
835 * Check if object is already written in a pack. This information is
836 * used to achieve delta-base precedence in a pack file.
838 * @return true if object is already written; false otherwise.
840 boolean isWritten() {
845 * @return offset in pack when object has been already written, or -1 if
846 * it has not been written yet
853 * Mark object as written. This information is used to achieve
854 * delta-base precedence in a pack file.
857 * offset where written object starts
859 void markWritten(long offset
) {
860 this.offset
= offset
;
863 PackedObjectLoader
getReuseLoader() {
867 boolean hasReuseLoader() {
868 return reuseLoader
!= null;
871 void setReuseLoader(PackedObjectLoader reuseLoader
) {
872 this.reuseLoader
= reuseLoader
;
875 void disposeLoader() {
876 this.reuseLoader
= null;
879 int getDeltaDepth() {
883 void updateDeltaDepth() {
884 if (deltaBase
instanceof ObjectToPack
)
885 deltaDepth
= ((ObjectToPack
) deltaBase
).deltaDepth
+ 1;
886 else if (deltaBase
!= null)
890 boolean wantWrite() {
894 void markWantWrite() {
895 this.wantWrite
= true;