Simplify the PackWriter.preparePack() API
[jgit.git] / org.spearce.jgit / src / org / spearce / jgit / lib / PackWriter.java
blobc2e6c4b8587e5a24ad6d5d1705008bceb077aefe
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.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;
60 /**
61 * <p>
62 * PackWriter class is responsible for generating pack files from specified set
63 * of objects from repository. This implementation produce pack files in format
64 * version 2.
65 * </p>
66 * <p>
67 * Source of objects may be specified in two ways:
68 * <ul>
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>
74 * </ul>
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)}.
80 * </p>
81 * <p>
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.
86 * </p>
87 * <p>
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
90 * undefined behavior.
91 * </p>
94 public class PackWriter {
95 /**
96 * Title of {@link ProgressMonitor} task used during counting objects to
97 * pack.
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
113 * (objects)
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.
198 * <p>
199 * Objects for packing are specified in {@link #preparePack(Iterator)} or
200 * {@link #preparePack(Collection, Collection)}.
202 * @param repo
203 * repository where objects are stored.
204 * @param monitor
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.
216 * <p>
217 * Objects for packing are specified in {@link #preparePack(Iterator)} or
218 * {@link #preparePack(Collection, Collection)}.
220 * @param repo
221 * repository where objects are stored.
222 * @param imonitor
223 * operations progress monitor, used within
224 * {@link #preparePack(Iterator)},
225 * {@link #preparePack(Collection, Collection)}
226 * @param wmonitor
227 * operations progress monitor, used within
228 * {@link #writePack(OutputStream)}.
230 public PackWriter(final Repository repo, final ProgressMonitor imonitor,
231 final ProgressMonitor wmonitor) {
232 this.db = repo;
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
241 * repository.
242 * <p>
243 * Default setting: {@value #DEFAULT_REUSE_DELTAS}
244 * </p>
246 * @return true if object is configured to reuse deltas; false otherwise.
248 public boolean isReuseDeltas() {
249 return reuseDeltas;
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
257 * thin-pack (see
258 * {@link #preparePack(Collection, Collection)} and
259 * {@link #preparePack(Iterator)}) where base object must exist on other
260 * side machine.
261 * <p>
262 * When raw delta data is directly copied from a pack file, checksum is
263 * computed to verify data.
264 * </p>
265 * <p>
266 * Default setting: {@value #DEFAULT_REUSE_DELTAS}
267 * </p>
269 * @param reuseDeltas
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.
279 * <p>
280 * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
281 * </p>
283 * @return true if writer is configured to reuse objects representation from
284 * pack; false otherwise.
286 public boolean isReuseObjects() {
287 return reuseObjects;
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
294 * is verified.
295 * <p>
296 * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
297 * </p>
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).
311 * <p>
312 * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
313 * </p>
315 * @return true if delta base is stored as an offset; false if it is stored
316 * as an object id.
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).
326 * <p>
327 * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
328 * </p>
330 * @param deltaBaseAsOffset
331 * boolean indicating whether delta base can be stored as an
332 * offset.
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.
341 * <p>
342 * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
343 * </p>
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.
355 * <p>
356 * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
357 * </p>
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() {
368 return thin;
372 * @param packthin
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) {
380 thin = 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;
395 * @param ignore
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.
408 * @param version
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.
428 * <p>
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
435 * once.
436 * </p>
437 * <p>
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
444 * transport.
445 * </p>
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)
461 throws IOException {
462 while (objectsSource.hasNext()) {
463 addObject(objectsSource.next());
468 * Prepare the list of objects to be written to the pack stream.
469 * <p>
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.
477 * </p>
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)
491 throws IOException {
492 ObjectWalk walker = setUpWalker(interestingObjects,
493 uninterestingObjects);
494 findObjectsToPack(walker);
498 * Determine if the pack file will contain the requested object.
500 * @param id
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.
525 * <p>
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.
531 * @param indexStream
532 * output for the index data. Caller is responsible for closing
533 * this stream.
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);
542 else
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);
556 return sortedByName;
560 * Write the prepared pack to the supplied stream.
561 * <p>
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.
567 * </p>
568 * <p>
569 * All reused objects data checksum (Adler32/CRC32) is computed and
570 * validated against existing checksum.
571 * </p>
573 * @param packStream
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
580 * stream.
582 public void writePack(OutputStream packStream) throws IOException {
583 if (reuseDeltas || reuseObjects)
584 searchForReuse();
586 if (!(packStream instanceof BufferedOutputStream))
587 packStream = new BufferedOutputStream(packStream);
588 out = new PackOutputStream(packStream);
590 writeMonitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber());
591 writeHeader();
592 writeObjects();
593 writeChecksum();
595 out.flush();
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);
610 if (reuseDeltas) {
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();
631 if (idBase == null)
632 continue;
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)) {
639 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)
652 throws IOException {
653 if (currentLoader == null)
654 return true;
655 if (loader.getRawSize() < currentLoader.getRawSize())
656 return true;
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);
667 return;
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())
686 writeObject(otp);
691 private void writeObject(final ObjectToPack otp) throws IOException {
692 otp.markWantWrite();
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
699 otp.disposeLoader();
700 } else {
701 writeObject(deltaBase);
706 assert !otp.isWritten();
708 out.resetCRC32();
709 otp.setOffset(out.length());
710 if (otp.isDeltaRepresentation())
711 writeDeltaObject(otp);
712 else
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);
724 otp.disposeLoader();
725 } else {
726 final ObjectLoader loader = db.openObject(windowCursor, otp);
727 final byte[] data = loader.getCachedBytes();
728 writeObjectHeader(otp.getType(), data.length);
729 deflater.reset();
730 deflater.setInput(data, 0, data.length);
731 deflater.finish();
732 do {
733 final int n = deflater.deflate(buf, 0, buf.length);
734 if (n > 0)
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);
754 } else {
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);
760 otp.disposeLoader();
763 private void writeObjectHeader(final int objectType, long dataLength)
764 throws IOException {
765 long nextLength = dataLength >>> 4;
766 int size = 0;
767 buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00)
768 | (objectType << 4) | (dataLength & 0x0F));
769 dataLength = nextLength;
770 while (dataLength > 0) {
771 nextLength >>>= 7;
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();
780 out.write(packcsum);
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);
791 if (thin)
792 walker.sort(RevSort.BOUNDARY, true);
794 for (ObjectId id : interestingObjects) {
795 RevObject o = walker.parseAny(id);
796 walker.markStart(o);
798 for (ObjectId id : uninterestingObjects) {
799 final RevObject o;
800 try {
801 o = walker.parseAny(id);
802 } catch (MissingObjectException x) {
803 if (ignoreMissingUninteresting)
804 continue;
805 throw x;
807 walker.markUninteresting(o);
809 return walker;
812 private void findObjectsToPack(final ObjectWalk walker)
813 throws MissingObjectException, IncorrectObjectTypeException,
814 IOException {
815 initMonitor.beginTask(COUNTING_OBJECTS_PROGRESS,
816 ProgressMonitor.UNKNOWN);
817 RevObject o;
819 while ((o = walker.next()) != null) {
820 addObject(o);
821 o.dispose();
822 initMonitor.update(1);
824 while ((o = walker.nextObject()) != null) {
825 addObject(o);
826 o.dispose();
827 initMonitor.update(1);
829 initMonitor.endTask();
833 * Include one object to the output file.
834 * <p>
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
837 * file.
839 * @param object
840 * the object to add.
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);
848 thin = true;
849 return;
852 final ObjectToPack otp = new ObjectToPack(object, object.getType());
853 try {
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");
863 objectsMap.add(otp);
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:
879 * <ul>
880 * <li>1 bit: wantWrite</li>
881 * <li>3 bits: type</li>
882 * <li>28 bits: deltaDepth</li>
883 * </ul>
885 private int flags;
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).
891 * @param src
892 * object id of object for packing
893 * @param type
894 * real type code of the object, not its in-pack type.
896 ObjectToPack(AnyObjectId src, final int type) {
897 super(src);
898 flags |= type << 1;
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
904 * whole object.
906 ObjectId getDeltaBaseId() {
907 return deltaBase;
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;
919 return null;
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
925 * in a created pack.
927 * @param deltaBase
928 * delta base object or null if object should be packed as a
929 * whole object.
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
942 * otherwise.
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() {
959 return reuseLoader;
962 boolean hasReuseLoader() {
963 return reuseLoader != null;
966 void setReuseLoader(PackedObjectLoader reuseLoader) {
967 this.reuseLoader = reuseLoader;
970 void disposeLoader() {
971 this.reuseLoader = null;
974 int getType() {
975 return (flags>>1) & 0x7;
978 int getDeltaDepth() {
979 return flags >>> 4;
982 void updateDeltaDepth() {
983 final int d;
984 if (deltaBase instanceof ObjectToPack)
985 d = ((ObjectToPack) deltaBase).getDeltaDepth() + 1;
986 else if (deltaBase != null)
987 d = 1;
988 else
989 d = 0;
990 flags = (d << 4) | flags & 0x15;
993 boolean wantWrite() {
994 return (flags & 1) == 1;
997 void markWantWrite() {
998 flags |= 1;