Rename a parameter in PackWriter that hides field with same name
[egit/qmx.git] / org.spearce.jgit / src / org / spearce / jgit / lib / PackWriter.java
blob601ce71613075ac1f92cd1f99ea9f473d5910c7f
1 /*
2 * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
8 * conditions are met:
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
21 * written permission.
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.DigestOutputStream;
44 import java.security.MessageDigest;
45 import java.util.ArrayList;
46 import java.util.Collection;
47 import java.util.Collections;
48 import java.util.Iterator;
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.transport.PackedObjectInfo;
60 import org.spearce.jgit.util.CountingOutputStream;
61 import org.spearce.jgit.util.NB;
63 /**
64 * <p>
65 * PackWriter class is responsible for generating pack files from specified set
66 * of objects from repository. This implementation produce pack files in format
67 * version 2.
68 * </p>
69 * <p>
70 * Source of objects may be specified in two ways:
71 * <ul>
72 * <li>(usually) by providing sets of interesting and uninteresting objects in
73 * repository - all interesting objects and their ancestors except uninteresting
74 * objects and their ancestors will be included in pack, or</li>
75 * <li>by providing iterator of {@link RevObject} specifying exact list and
76 * order of objects in pack</li>
77 * </ul>
78 * Typical usage consists of creating instance intended for some pack,
79 * configuring options, preparing the list of objects by calling
80 * {@link #preparePack(Iterator)} or
81 * {@link #preparePack(Collection, Collection, boolean, boolean)}, and finally
82 * producing the stream with {@link #writePack(OutputStream)}.
83 * </p>
84 * <p>
85 * Class provide set of configurable options and {@link ProgressMonitor}
86 * support, as operations may take a long time for big repositories. Deltas
87 * searching algorithm is <b>NOT IMPLEMENTED</b> yet - this implementation
88 * relies only on deltas and objects reuse.
89 * </p>
90 * <p>
91 * This class is not thread safe, it is intended to be used in one thread, with
92 * one instance per created pack. Subsequent calls to writePack result in
93 * undefined behavior.
94 * </p>
97 public class PackWriter {
98 /**
99 * Title of {@link ProgressMonitor} task used during counting objects to
100 * pack.
102 * @see #preparePack(Collection, Collection, boolean, boolean)
104 public static final String COUNTING_OBJECTS_PROGRESS = "Counting objects";
107 * Title of {@link ProgressMonitor} task used during searching for objects
108 * reuse or delta reuse.
110 * @see #writePack(OutputStream)
112 public static final String SEARCHING_REUSE_PROGRESS = "Compressing objects";
115 * Title of {@link ProgressMonitor} task used during writing out pack
116 * (objects)
118 * @see #writePack(OutputStream)
120 public static final String WRITING_OBJECTS_PROGRESS = "Writing objects";
123 * Default value of deltas reuse option.
125 * @see #setReuseDeltas(boolean)
127 public static final boolean DEFAULT_REUSE_DELTAS = true;
130 * Default value of objects reuse option.
132 * @see #setReuseObjects(boolean)
134 public static final boolean DEFAULT_REUSE_OBJECTS = true;
137 * Default value of delta base as offset option.
139 * @see #setDeltaBaseAsOffset(boolean)
141 public static final boolean DEFAULT_DELTA_BASE_AS_OFFSET = false;
144 * Default value of maximum delta chain depth.
146 * @see #setMaxDeltaDepth(int)
148 public static final int DEFAULT_MAX_DELTA_DEPTH = 50;
150 private static final int PACK_VERSION_GENERATED = 2;
152 @SuppressWarnings("unchecked")
153 private final List<ObjectToPack> objectsLists[] = new List[Constants.OBJ_TAG + 1];
155 objectsLists[0] = Collections.<ObjectToPack> emptyList();
156 objectsLists[Constants.OBJ_COMMIT] = new ArrayList<ObjectToPack>();
157 objectsLists[Constants.OBJ_TREE] = new ArrayList<ObjectToPack>();
158 objectsLists[Constants.OBJ_BLOB] = new ArrayList<ObjectToPack>();
159 objectsLists[Constants.OBJ_TAG] = new ArrayList<ObjectToPack>();
162 private final ObjectIdSubclassMap<ObjectToPack> objectsMap = new ObjectIdSubclassMap<ObjectToPack>();
164 // edge objects for thin packs
165 private final ObjectIdSubclassMap<ObjectId> edgeObjects = new ObjectIdSubclassMap<ObjectId>();
167 private final Repository db;
169 private DigestOutputStream out;
171 private CountingOutputStream countingOut;
173 private final Deflater deflater;
175 private ProgressMonitor initMonitor;
177 private ProgressMonitor writeMonitor;
179 private final byte[] buf = new byte[16384]; // 16 KB
181 private final WindowCursor windowCursor = new WindowCursor();
183 private List<ObjectToPack> sortedByName;
185 private byte packcsum[];
187 private boolean reuseDeltas = DEFAULT_REUSE_DELTAS;
189 private boolean reuseObjects = DEFAULT_REUSE_OBJECTS;
191 private boolean deltaBaseAsOffset = DEFAULT_DELTA_BASE_AS_OFFSET;
193 private int maxDeltaDepth = DEFAULT_MAX_DELTA_DEPTH;
195 private int outputVersion;
197 private boolean thin;
200 * Create writer for specified repository.
201 * <p>
202 * Objects for packing are specified in {@link #preparePack(Iterator)} or
203 * {@link #preparePack(Collection, Collection, boolean, boolean)}.
205 * @param repo
206 * repository where objects are stored.
207 * @param monitor
208 * operations progress monitor, used within
209 * {@link #preparePack(Iterator)},
210 * {@link #preparePack(Collection, Collection, boolean, boolean)}
211 * , or {@link #writePack(OutputStream)}.
213 public PackWriter(final Repository repo, final ProgressMonitor monitor) {
214 this(repo, monitor, monitor);
218 * Create writer for specified repository.
219 * <p>
220 * Objects for packing are specified in {@link #preparePack(Iterator)} or
221 * {@link #preparePack(Collection, Collection, boolean, boolean)}.
223 * @param repo
224 * repository where objects are stored.
225 * @param imonitor
226 * operations progress monitor, used within
227 * {@link #preparePack(Iterator)},
228 * {@link #preparePack(Collection, Collection, boolean, boolean)}
230 * @param wmonitor
231 * operations progress monitor, used within
232 * {@link #writePack(OutputStream)}.
234 public PackWriter(final Repository repo, final ProgressMonitor imonitor,
235 final ProgressMonitor wmonitor) {
236 this.db = repo;
237 initMonitor = imonitor;
238 writeMonitor = wmonitor;
239 this.deflater = new Deflater(db.getConfig().getCore().getCompression());
240 outputVersion = repo.getConfig().getCore().getPackIndexVersion();
244 * Check whether object is configured to reuse deltas existing in
245 * repository.
246 * <p>
247 * Default setting: {@value #DEFAULT_REUSE_DELTAS}
248 * </p>
250 * @return true if object is configured to reuse deltas; false otherwise.
252 public boolean isReuseDeltas() {
253 return reuseDeltas;
257 * Set reuse deltas configuration option for this writer. When enabled,
258 * writer will search for delta representation of object in repository and
259 * use it if possible. Normally, only deltas with base to another object
260 * existing in set of objects to pack will be used. Exception is however
261 * thin-pack (see
262 * {@link #preparePack(Collection, Collection, boolean, boolean)} and
263 * {@link #preparePack(Iterator)}) where base object must exist on other
264 * side machine.
265 * <p>
266 * When raw delta data is directly copied from a pack file, checksum is
267 * computed to verify data.
268 * </p>
269 * <p>
270 * Default setting: {@value #DEFAULT_REUSE_DELTAS}
271 * </p>
273 * @param reuseDeltas
274 * boolean indicating whether or not try to reuse deltas.
276 public void setReuseDeltas(boolean reuseDeltas) {
277 this.reuseDeltas = reuseDeltas;
281 * Checks whether object is configured to reuse existing objects
282 * representation in repository.
283 * <p>
284 * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
285 * </p>
287 * @return true if writer is configured to reuse objects representation from
288 * pack; false otherwise.
290 public boolean isReuseObjects() {
291 return reuseObjects;
295 * Set reuse objects configuration option for this writer. If enabled,
296 * writer searches for representation in a pack file. If possible,
297 * compressed data is directly copied from such a pack file. Data checksum
298 * is verified.
299 * <p>
300 * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
301 * </p>
303 * @param reuseObjects
304 * boolean indicating whether or not writer should reuse existing
305 * objects representation.
307 public void setReuseObjects(boolean reuseObjects) {
308 this.reuseObjects = reuseObjects;
312 * Check whether writer can store delta base as an offset (new style
313 * reducing pack size) or should store it as an object id (legacy style,
314 * compatible with old readers).
315 * <p>
316 * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
317 * </p>
319 * @return true if delta base is stored as an offset; false if it is stored
320 * as an object id.
322 public boolean isDeltaBaseAsOffset() {
323 return deltaBaseAsOffset;
327 * Set writer delta base format. Delta base can be written as an offset in a
328 * pack file (new approach reducing file size) or as an object id (legacy
329 * approach, compatible with old readers).
330 * <p>
331 * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
332 * </p>
334 * @param deltaBaseAsOffset
335 * boolean indicating whether delta base can be stored as an
336 * offset.
338 public void setDeltaBaseAsOffset(boolean deltaBaseAsOffset) {
339 this.deltaBaseAsOffset = deltaBaseAsOffset;
343 * Get maximum depth of delta chain set up for this writer. Generated chains
344 * are not longer than this value.
345 * <p>
346 * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
347 * </p>
349 * @return maximum delta chain depth.
351 public int getMaxDeltaDepth() {
352 return maxDeltaDepth;
356 * Set up maximum depth of delta chain for this writer. Generated chains are
357 * not longer than this value. Too low value causes low compression level,
358 * while too big makes unpacking (reading) longer.
359 * <p>
360 * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
361 * </p>
363 * @param maxDeltaDepth
364 * maximum delta chain depth.
366 public void setMaxDeltaDepth(int maxDeltaDepth) {
367 this.maxDeltaDepth = maxDeltaDepth;
371 * Set the pack index file format version this instance will create.
373 * @param version
374 * the version to write. The special version 0 designates the
375 * oldest (most compatible) format available for the objects.
376 * @see PackIndexWriter
378 public void setIndexVersion(final int version) {
379 outputVersion = version;
383 * Returns objects number in a pack file that was created by this writer.
385 * @return number of objects in pack.
387 public int getObjectsNumber() {
388 return objectsMap.size();
392 * Prepare the list of objects to be written to the pack stream.
393 * <p>
394 * Iterator <b>exactly</b> determines which objects are included in a pack
395 * and order they appear in pack (except that objects order by type is not
396 * needed at input). This order should conform general rules of ordering
397 * objects in git - by recency and path (type and delta-base first is
398 * internally secured) and responsibility for guaranteeing this order is on
399 * a caller side. Iterator must return each id of object to write exactly
400 * once.
401 * </p>
402 * <p>
403 * When iterator returns object that has {@link RevFlag#UNINTERESTING} flag,
404 * this object won't be included in an output pack. Instead, it is recorded
405 * as edge-object (known to remote repository) for thin-pack. In such a case
406 * writer may pack objects with delta base object not within set of objects
407 * to pack, but belonging to party repository - those marked with
408 * {@link RevFlag#UNINTERESTING} flag. This type of pack is used only for
409 * transport.
410 * </p>
412 * @param objectsSource
413 * iterator of object to store in a pack; order of objects within
414 * each type is important, ordering by type is not needed;
415 * allowed types for objects are {@link Constants#OBJ_COMMIT},
416 * {@link Constants#OBJ_TREE}, {@link Constants#OBJ_BLOB} and
417 * {@link Constants#OBJ_TAG}; objects returned by iterator may
418 * be later reused by caller as object id and type are internally
419 * copied in each iteration; if object returned by iterator has
420 * {@link RevFlag#UNINTERESTING} flag set, it won't be included
421 * in a pack, but is considered as edge-object for thin-pack.
422 * @throws IOException
423 * when some I/O problem occur during reading objects.
425 public void preparePack(final Iterator<RevObject> objectsSource)
426 throws IOException {
427 while (objectsSource.hasNext()) {
428 addObject(objectsSource.next());
433 * Prepare the list of objects to be written to the pack stream.
434 * <p>
435 * Basing on these 2 sets, another set of objects to put in a pack file is
436 * created: this set consists of all objects reachable (ancestors) from
437 * interesting objects, except uninteresting objects and their ancestors.
438 * This method uses class {@link ObjectWalk} extensively to find out that
439 * appropriate set of output objects and their optimal order in output pack.
440 * Order is consistent with general git in-pack rules: sort by object type,
441 * recency, path and delta-base first.
442 * </p>
444 * @param interestingObjects
445 * collection of objects to be marked as interesting (start
446 * points of graph traversal).
447 * @param uninterestingObjects
448 * collection of objects to be marked as uninteresting (end
449 * points of graph traversal).
450 * @param packthin
451 * a boolean indicating whether writer may pack objects with
452 * delta base object not within set of objects to pack, but
453 * belonging to party repository (uninteresting/boundary) as
454 * determined by set; this kind of pack is used only for
455 * transport; true - to produce thin pack, false - otherwise.
456 * @param ignoreMissingUninteresting
457 * true if writer should ignore non existing uninteresting
458 * objects during construction set of objects to pack; false
459 * otherwise - non existing uninteresting objects may cause
460 * {@link MissingObjectException}
461 * @throws IOException
462 * when some I/O problem occur during reading objects.
464 public void preparePack(
465 final Collection<? extends ObjectId> interestingObjects,
466 final Collection<? extends ObjectId> uninterestingObjects,
467 final boolean packthin, final boolean ignoreMissingUninteresting)
468 throws IOException {
469 this.thin = packthin;
470 ObjectWalk walker = setUpWalker(interestingObjects,
471 uninterestingObjects, ignoreMissingUninteresting);
472 findObjectsToPack(walker);
476 * Determine if the pack file will contain the requested object.
478 * @param id
479 * the object to test the existence of.
480 * @return true if the object will appear in the output pack file.
482 public boolean willInclude(final AnyObjectId id) {
483 return objectsMap.get(id) != null;
487 * Computes SHA-1 of lexicographically sorted objects ids written in this
488 * pack, as used to name a pack file in repository.
490 * @return ObjectId representing SHA-1 name of a pack that was created.
492 public ObjectId computeName() {
493 final MessageDigest md = Constants.newMessageDigest();
494 for (ObjectToPack otp : sortByName()) {
495 otp.copyRawTo(buf, 0);
496 md.update(buf, 0, Constants.OBJECT_ID_LENGTH);
498 return ObjectId.fromRaw(md.digest());
502 * Create an index file to match the pack file just written.
503 * <p>
504 * This method can only be invoked after {@link #preparePack(Iterator)} or
505 * {@link #preparePack(Collection, Collection, boolean, boolean)} has been
506 * invoked and completed successfully. Writing a corresponding index is an
507 * optional feature that not all pack users may require.
509 * @param indexStream
510 * output for the index data. Caller is responsible for closing
511 * this stream.
512 * @throws IOException
513 * the index data could not be written to the supplied stream.
515 public void writeIndex(final OutputStream indexStream) throws IOException {
516 final List<ObjectToPack> list = sortByName();
517 final PackIndexWriter iw;
518 if (outputVersion <= 0)
519 iw = PackIndexWriter.createOldestPossible(indexStream, list);
520 else
521 iw = PackIndexWriter.createVersion(indexStream, outputVersion);
522 iw.write(list, packcsum);
525 private List<ObjectToPack> sortByName() {
526 if (sortedByName == null) {
527 sortedByName = new ArrayList<ObjectToPack>(objectsMap.size());
528 for (List<ObjectToPack> list : objectsLists) {
529 for (ObjectToPack otp : list)
530 sortedByName.add(otp);
532 Collections.sort(sortedByName);
534 return sortedByName;
538 * Write the prepared pack to the supplied stream.
539 * <p>
540 * At first, this method collects and sorts objects to pack, then deltas
541 * search is performed if set up accordingly, finally pack stream is
542 * written. {@link ProgressMonitor} tasks {@value #SEARCHING_REUSE_PROGRESS}
543 * (only if reuseDeltas or reuseObjects is enabled) and
544 * {@value #WRITING_OBJECTS_PROGRESS} are updated during packing.
545 * </p>
546 * <p>
547 * All reused objects data checksum (Adler32/CRC32) is computed and
548 * validated against existing checksum.
549 * </p>
551 * @param packStream
552 * output stream of pack data. If the stream is not buffered it
553 * will be buffered by the writer. Caller is responsible for
554 * closing the stream.
555 * @throws IOException
556 * an error occurred reading a local object's data to include in
557 * the pack, or writing compressed object data to the output
558 * stream.
560 public void writePack(OutputStream packStream) throws IOException {
561 if (reuseDeltas || reuseObjects)
562 searchForReuse();
564 if (!(packStream instanceof BufferedOutputStream))
565 packStream = new BufferedOutputStream(packStream);
566 countingOut = new CountingOutputStream(packStream);
567 out = new DigestOutputStream(countingOut, Constants.newMessageDigest());
569 writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber());
570 writeHeader();
571 writeObjects();
572 writeChecksum();
574 out.flush();
575 windowCursor.release();
576 writeMonitor.endTask();
579 private void searchForReuse() throws IOException {
580 initMonitor.beginTask(SEARCHING_REUSE_PROGRESS, getObjectsNumber());
581 final Collection<PackedObjectLoader> reuseLoaders = new ArrayList<PackedObjectLoader>();
582 for (List<ObjectToPack> list : objectsLists) {
583 for (ObjectToPack otp : list) {
584 if (initMonitor.isCancelled())
585 throw new IOException(
586 "Packing cancelled during objects writing");
587 reuseLoaders.clear();
588 db.openObjectInAllPacks(otp, reuseLoaders, windowCursor);
589 if (reuseDeltas) {
590 selectDeltaReuseForObject(otp, reuseLoaders);
592 // delta reuse is preferred over object reuse
593 if (reuseObjects && !otp.hasReuseLoader()) {
594 selectObjectReuseForObject(otp, reuseLoaders);
596 initMonitor.update(1);
600 initMonitor.endTask();
603 private void selectDeltaReuseForObject(final ObjectToPack otp,
604 final Collection<PackedObjectLoader> loaders) throws IOException {
605 PackedObjectLoader bestLoader = null;
606 ObjectId bestBase = null;
608 for (PackedObjectLoader loader : loaders) {
609 ObjectId idBase = loader.getDeltaBase();
610 if (idBase == null)
611 continue;
612 ObjectToPack otpBase = objectsMap.get(idBase);
614 // only if base is in set of objects to write or thin-pack's edge
615 if ((otpBase != null || (thin && edgeObjects.get(idBase) != null))
616 // select smallest possible delta if > 1 available
617 && isBetterDeltaReuseLoader(bestLoader, loader)) {
618 bestLoader = loader;
619 bestBase = (otpBase != null ? otpBase : idBase);
623 if (bestLoader != null) {
624 otp.setReuseLoader(bestLoader);
625 otp.setDeltaBase(bestBase);
629 private static boolean isBetterDeltaReuseLoader(
630 PackedObjectLoader currentLoader, PackedObjectLoader loader)
631 throws IOException {
632 if (currentLoader == null)
633 return true;
634 if (loader.getRawSize() < currentLoader.getRawSize())
635 return true;
636 return (loader.getRawSize() == currentLoader.getRawSize()
637 && loader.supportsFastCopyRawData() && !currentLoader
638 .supportsFastCopyRawData());
641 private void selectObjectReuseForObject(final ObjectToPack otp,
642 final Collection<PackedObjectLoader> loaders) {
643 for (final PackedObjectLoader loader : loaders) {
644 if (loader instanceof WholePackedObjectLoader) {
645 otp.setReuseLoader(loader);
646 return;
651 private void writeHeader() throws IOException {
652 out.write(Constants.PACK_SIGNATURE);
654 NB.encodeInt32(buf, 0, PACK_VERSION_GENERATED);
655 out.write(buf, 0, 4);
657 NB.encodeInt32(buf, 0, getObjectsNumber());
658 out.write(buf, 0, 4);
661 private void writeObjects() throws IOException {
662 for (List<ObjectToPack> list : objectsLists) {
663 for (ObjectToPack otp : list) {
664 if (writeMonitor.isCancelled())
665 throw new IOException(
666 "Packing cancelled during objects writing");
667 if (!otp.isWritten())
668 writeObject(otp);
673 private void writeObject(final ObjectToPack otp) throws IOException {
674 otp.markWantWrite();
675 if (otp.isDeltaRepresentation()) {
676 ObjectToPack deltaBase = otp.getDeltaBase();
677 assert deltaBase != null || thin;
678 if (deltaBase != null && !deltaBase.isWritten()) {
679 if (deltaBase.wantWrite()) {
680 otp.clearDeltaBase(); // cycle detected
681 otp.disposeLoader();
682 } else {
683 writeObject(deltaBase);
688 assert !otp.isWritten();
690 otp.setOffset(countingOut.getCount());
691 if (otp.isDeltaRepresentation())
692 writeDeltaObject(otp);
693 else
694 writeWholeObject(otp);
696 writeMonitor.update(1);
699 private void writeWholeObject(final ObjectToPack otp) throws IOException {
700 if (otp.hasReuseLoader()) {
701 final PackedObjectLoader loader = otp.getReuseLoader();
702 writeObjectHeader(otp.getType(), loader.getSize());
703 loader.copyRawData(out, buf);
704 otp.disposeLoader();
705 } else {
706 final ObjectLoader loader = db.openObject(windowCursor, otp);
707 final byte[] data = loader.getCachedBytes();
708 final DeflaterOutputStream deflaterOut = new DeflaterOutputStream(
709 out, deflater);
710 writeObjectHeader(otp.getType(), data.length);
711 deflaterOut.write(data);
712 deflaterOut.finish();
713 deflater.reset();
717 private void writeDeltaObject(final ObjectToPack otp) throws IOException {
718 final PackedObjectLoader loader = otp.getReuseLoader();
719 if (deltaBaseAsOffset && otp.getDeltaBase() != null) {
720 writeObjectHeader(Constants.OBJ_OFS_DELTA, loader.getRawSize());
722 final ObjectToPack deltaBase = otp.getDeltaBase();
723 long offsetDiff = otp.getOffset() - deltaBase.getOffset();
724 int pos = buf.length - 1;
725 buf[pos] = (byte) (offsetDiff & 0x7F);
726 while ((offsetDiff >>= 7) > 0) {
727 buf[--pos] = (byte) (0x80 | (--offsetDiff & 0x7F));
730 out.write(buf, pos, buf.length - pos);
731 } else {
732 writeObjectHeader(Constants.OBJ_REF_DELTA, loader.getRawSize());
733 otp.getDeltaBaseId().copyRawTo(buf, 0);
734 out.write(buf, 0, Constants.OBJECT_ID_LENGTH);
736 loader.copyRawData(out, buf);
737 otp.disposeLoader();
740 private void writeObjectHeader(final int objectType, long dataLength)
741 throws IOException {
742 long nextLength = dataLength >>> 4;
743 int size = 0;
744 buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00)
745 | (objectType << 4) | (dataLength & 0x0F));
746 dataLength = nextLength;
747 while (dataLength > 0) {
748 nextLength >>>= 7;
749 buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (dataLength & 0x7F));
750 dataLength = nextLength;
752 out.write(buf, 0, size);
755 private void writeChecksum() throws IOException {
756 out.on(false);
757 packcsum = out.getMessageDigest().digest();
758 out.write(packcsum);
761 private ObjectWalk setUpWalker(
762 final Collection<? extends ObjectId> interestingObjects,
763 final Collection<? extends ObjectId> uninterestingObjects,
764 final boolean ignoreMissingUninteresting)
765 throws MissingObjectException, IOException,
766 IncorrectObjectTypeException {
767 final ObjectWalk walker = new ObjectWalk(db);
768 walker.sort(RevSort.TOPO);
769 walker.sort(RevSort.COMMIT_TIME_DESC, true);
770 if (thin)
771 walker.sort(RevSort.BOUNDARY, true);
773 for (ObjectId id : interestingObjects) {
774 RevObject o = walker.parseAny(id);
775 walker.markStart(o);
777 for (ObjectId id : uninterestingObjects) {
778 final RevObject o;
779 try {
780 o = walker.parseAny(id);
781 } catch (MissingObjectException x) {
782 if (ignoreMissingUninteresting)
783 continue;
784 throw x;
786 walker.markUninteresting(o);
788 return walker;
791 private void findObjectsToPack(final ObjectWalk walker)
792 throws MissingObjectException, IncorrectObjectTypeException,
793 IOException {
794 initMonitor.beginTask(COUNTING_OBJECTS_PROGRESS,
795 ProgressMonitor.UNKNOWN);
796 RevObject o;
798 while ((o = walker.next()) != null) {
799 addObject(o);
800 o.dispose();
801 initMonitor.update(1);
803 while ((o = walker.nextObject()) != null) {
804 addObject(o);
805 o.dispose();
806 initMonitor.update(1);
808 initMonitor.endTask();
812 * Include one object to the output file.
813 * <p>
814 * Objects are written in the order they are added. If the same object is
815 * added twice, it may be written twice, creating a larger than necessary
816 * file.
818 * @param object
819 * the object to add.
820 * @throws IncorrectObjectTypeException
821 * the object is an unsupported type.
823 public void addObject(final RevObject object)
824 throws IncorrectObjectTypeException {
825 if (object.has(RevFlag.UNINTERESTING)) {
826 edgeObjects.add(object);
827 thin = true;
828 return;
831 final ObjectToPack otp = new ObjectToPack(object, object.getType());
832 try {
833 objectsLists[object.getType()].add(otp);
834 } catch (ArrayIndexOutOfBoundsException x) {
835 throw new IncorrectObjectTypeException(object,
836 "COMMIT nor TREE nor BLOB nor TAG");
837 } catch (UnsupportedOperationException x) {
838 // index pointing to "dummy" empty list
839 throw new IncorrectObjectTypeException(object,
840 "COMMIT nor TREE nor BLOB nor TAG");
842 objectsMap.add(otp);
846 * Class holding information about object that is going to be packed by
847 * {@link PackWriter}. Information include object representation in a
848 * pack-file and object status.
851 static class ObjectToPack extends PackedObjectInfo {
852 private ObjectId deltaBase;
854 private PackedObjectLoader reuseLoader;
857 * Bit field, from bit 0 to bit 31:
858 * <ul>
859 * <li>1 bit: wantWrite</li>
860 * <li>3 bits: type</li>
861 * <li>28 bits: deltaDepth</li>
862 * </ul>
864 private int flags;
867 * Construct object for specified object id. <br/> By default object is
868 * marked as not written and non-delta packed (as a whole object).
870 * @param src
871 * object id of object for packing
872 * @param type
873 * real type code of the object, not its in-pack type.
875 ObjectToPack(AnyObjectId src, final int type) {
876 super(src);
877 flags |= type << 1;
881 * @return delta base object id if object is going to be packed in delta
882 * representation; null otherwise - if going to be packed as a
883 * whole object.
885 ObjectId getDeltaBaseId() {
886 return deltaBase;
890 * @return delta base object to pack if object is going to be packed in
891 * delta representation and delta is specified as object to
892 * pack; null otherwise - if going to be packed as a whole
893 * object or delta base is specified only as id.
895 ObjectToPack getDeltaBase() {
896 if (deltaBase instanceof ObjectToPack)
897 return (ObjectToPack) deltaBase;
898 return null;
902 * Set delta base for the object. Delta base set by this method is used
903 * by {@link PackWriter} to write object - determines its representation
904 * in a created pack.
906 * @param deltaBase
907 * delta base object or null if object should be packed as a
908 * whole object.
911 void setDeltaBase(ObjectId deltaBase) {
912 this.deltaBase = deltaBase;
915 void clearDeltaBase() {
916 this.deltaBase = null;
920 * @return true if object is going to be written as delta; false
921 * otherwise.
923 boolean isDeltaRepresentation() {
924 return deltaBase != null;
928 * Check if object is already written in a pack. This information is
929 * used to achieve delta-base precedence in a pack file.
931 * @return true if object is already written; false otherwise.
933 boolean isWritten() {
934 return getOffset() != 0;
937 PackedObjectLoader getReuseLoader() {
938 return reuseLoader;
941 boolean hasReuseLoader() {
942 return reuseLoader != null;
945 void setReuseLoader(PackedObjectLoader reuseLoader) {
946 this.reuseLoader = reuseLoader;
949 void disposeLoader() {
950 this.reuseLoader = null;
953 int getType() {
954 return (flags>>1) & 0x7;
957 int getDeltaDepth() {
958 return flags >>> 4;
961 void updateDeltaDepth() {
962 final int d;
963 if (deltaBase instanceof ObjectToPack)
964 d = ((ObjectToPack) deltaBase).getDeltaDepth() + 1;
965 else if (deltaBase != null)
966 d = 1;
967 else
968 d = 0;
969 flags = (d << 4) | flags & 0x15;
972 boolean wantWrite() {
973 return (flags & 1) == 1;
976 void markWantWrite() {
977 flags |= 1;