Allow PackWriter to prepare object list and compute name before writing
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / lib / PackWriter.java
blob9a427da3cc8a032e4dcb1c8aa3d8cf04793a9925
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.LinkedList;
50 import java.util.List;
51 import java.util.zip.Deflater;
52 import java.util.zip.DeflaterOutputStream;
54 import org.spearce.jgit.errors.IncorrectObjectTypeException;
55 import org.spearce.jgit.errors.MissingObjectException;
56 import org.spearce.jgit.revwalk.ObjectWalk;
57 import org.spearce.jgit.revwalk.RevFlag;
58 import org.spearce.jgit.revwalk.RevObject;
59 import org.spearce.jgit.revwalk.RevSort;
60 import org.spearce.jgit.transport.PackedObjectInfo;
61 import org.spearce.jgit.util.CountingOutputStream;
62 import org.spearce.jgit.util.NB;
64 /**
65 * <p>
66 * PackWriter class is responsible for generating pack files from specified set
67 * of objects from repository. This implementation produce pack files in format
68 * version 2.
69 * </p>
70 * <p>
71 * Source of objects may be specified in two ways:
72 * <ul>
73 * <li>(usually) by providing sets of interesting and uninteresting objects in
74 * repository - all interesting objects and their ancestors except uninteresting
75 * objects and their ancestors will be included in pack, or</li>
76 * <li>by providing iterator of {@link RevObject} specifying exact list and
77 * order of objects in pack</li>
78 * </ul>
79 * Typical usage consists of creating instance intended for some pack,
80 * configuring options, preparing the list of objects by calling
81 * {@link #preparePack(Iterator)} or
82 * {@link #preparePack(Collection, Collection, boolean, boolean)}, and finally
83 * producing the stream with {@link #writePack(OutputStream)}.
84 * </p>
85 * <p>
86 * Class provide set of configurable options and {@link ProgressMonitor}
87 * support, as operations may take a long time for big repositories. Deltas
88 * searching algorithm is <b>NOT IMPLEMENTED</b> yet - this implementation
89 * relies only on deltas and objects reuse.
90 * </p>
91 * <p>
92 * This class is not thread safe, it is intended to be used in one thread, with
93 * one instance per created pack. Subsequent calls to writePack result in
94 * undefined behavior.
95 * </p>
98 public class PackWriter {
99 /**
100 * Title of {@link ProgressMonitor} task used during counting objects to
101 * pack.
103 * @see #preparePack(Collection, Collection, boolean, boolean)
105 public static final String COUNTING_OBJECTS_PROGRESS = "Counting objects to pack";
108 * Title of {@link ProgressMonitor} task used during searching for objects
109 * reuse or delta reuse.
111 * @see #writePack(OutputStream)
113 public static final String SEARCHING_REUSE_PROGRESS = "Searching for delta and object reuse";
116 * Title of {@link ProgressMonitor} task used during writing out pack
117 * (objects)
119 * @see #writePack(OutputStream)
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 DigestOutputStream out;
172 private 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 List<ObjectToPack> sortedByName;
184 private byte packcsum[];
186 private boolean reuseDeltas = DEFAULT_REUSE_DELTAS;
188 private boolean reuseObjects = DEFAULT_REUSE_OBJECTS;
190 private boolean deltaBaseAsOffset = DEFAULT_DELTA_BASE_AS_OFFSET;
192 private int maxDeltaDepth = DEFAULT_MAX_DELTA_DEPTH;
194 private int outputVersion;
196 private boolean thin;
199 * Create writer for specified repository.
200 * <p>
201 * Objects for packing are specified in {@link #preparePack(Iterator)} or
202 * {@link #preparePack(Collection, Collection, boolean, boolean)}.
204 * @param repo
205 * repository where objects are stored.
206 * @param monitor
207 * operations progress monitor, used within
208 * {@link #preparePack(Iterator)},
209 * {@link #preparePack(Collection, Collection, boolean, boolean)},
210 * or {@link #writePack(OutputStream)}.
212 public PackWriter(final Repository repo, final ProgressMonitor monitor) {
213 this.db = repo;
214 this.monitor = monitor;
215 this.deflater = new Deflater(db.getConfig().getCore().getCompression());
219 * Check whether object is configured to reuse deltas existing in
220 * repository.
221 * <p>
222 * Default setting: {@value #DEFAULT_REUSE_DELTAS}
223 * </p>
225 * @return true if object is configured to reuse deltas; false otherwise.
227 public boolean isReuseDeltas() {
228 return reuseDeltas;
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
236 * thin-pack (see
237 * {@link #preparePack(Collection, Collection, boolean, boolean)} and
238 * {@link #preparePack(Iterator)}) where base object must exist on other
239 * side machine.
240 * <p>
241 * When raw delta data is directly copied from a pack file, checksum is
242 * computed to verify data.
243 * </p>
244 * <p>
245 * Default setting: {@value #DEFAULT_REUSE_DELTAS}
246 * </p>
248 * @param reuseDeltas
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.
258 * <p>
259 * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
260 * </p>
262 * @return true if writer is configured to reuse objects representation from
263 * pack; false otherwise.
265 public boolean isReuseObjects() {
266 return reuseObjects;
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
273 * is verified.
274 * <p>
275 * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
276 * </p>
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).
290 * <p>
291 * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
292 * </p>
294 * @return true if delta base is stored as an offset; false if it is stored
295 * as an object id.
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).
305 * <p>
306 * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
307 * </p>
309 * @param deltaBaseAsOffset
310 * boolean indicating whether delta base can be stored as an
311 * offset.
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.
320 * <p>
321 * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
322 * </p>
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.
334 * <p>
335 * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
336 * </p>
338 * @param maxDeltaDepth
339 * maximum delta chain depth.
341 public void setMaxDeltaDepth(int maxDeltaDepth) {
342 this.maxDeltaDepth = maxDeltaDepth;
346 * Set the pack index file format version this instance will create.
348 * @param version
349 * the version to write. The special version 0 designates the
350 * oldest (most compatible) format available for the objects.
351 * @see PackIndexWriter
353 public void setIndexVersion(final int version) {
354 outputVersion = version;
358 * Returns objects number in a pack file that was created by this writer.
360 * @return number of objects in pack.
362 public int getObjectsNumber() {
363 return objectsMap.size();
367 * Prepare the list of objects to be written to the pack stream.
368 * <p>
369 * Iterator <b>exactly</b> determines which objects are included in a pack
370 * and order they appear in pack (except that objects order by type is not
371 * needed at input). This order should conform general rules of ordering
372 * objects in git - by recency and path (type and delta-base first is
373 * internally secured) and responsibility for guaranteeing this order is on
374 * a caller side. Iterator must return each id of object to write exactly
375 * once.
376 * </p>
377 * <p>
378 * When iterator returns object that has {@link RevFlag#UNINTERESTING} flag,
379 * this object won't be included in an output pack. Instead, it is recorded
380 * as edge-object (known to remote repository) for thin-pack. In such a case
381 * writer may pack objects with delta base object not within set of objects
382 * to pack, but belonging to party repository - those marked with
383 * {@link RevFlag#UNINTERESTING} flag. This type of pack is used only for
384 * transport.
385 * </p>
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.
400 public void preparePack(final Iterator<RevObject> objectsSource)
401 throws IOException {
402 while (objectsSource.hasNext()) {
403 addObject(objectsSource.next());
408 * Prepare the list of objects to be written to the pack stream.
409 * <p>
410 * Basing on these 2 sets, another set of objects to put in a pack file is
411 * created: this set consists of all objects reachable (ancestors) from
412 * interesting objects, except uninteresting objects and their ancestors.
413 * This method uses class {@link ObjectWalk} extensively to find out that
414 * appropriate set of output objects and their optimal order in output pack.
415 * Order is consistent with general git in-pack rules: sort by object type,
416 * recency, path and delta-base first.
417 * </p>
419 * @param interestingObjects
420 * collection of objects to be marked as interesting (start
421 * points of graph traversal).
422 * @param uninterestingObjects
423 * collection of objects to be marked as uninteresting (end
424 * points of graph traversal).
425 * @param thin
426 * a boolean indicating whether writer may pack objects with
427 * delta base object not within set of objects to pack, but
428 * belonging to party repository (uninteresting/boundary) as
429 * determined by set; this kind of pack is used only for
430 * transport; true - to produce thin pack, false - otherwise.
431 * @param ignoreMissingUninteresting
432 * true if writer should ignore non existing uninteresting
433 * objects during construction set of objects to pack; false
434 * otherwise - non existing uninteresting objects may cause
435 * {@link MissingObjectException}
436 * @throws IOException
437 * when some I/O problem occur during reading objects.
439 public void preparePack(final Collection<ObjectId> interestingObjects,
440 final Collection<ObjectId> uninterestingObjects,
441 final boolean thin, final boolean ignoreMissingUninteresting)
442 throws IOException {
443 ObjectWalk walker = setUpWalker(interestingObjects,
444 uninterestingObjects, thin, ignoreMissingUninteresting);
445 findObjectsToPack(walker);
449 * Computes SHA-1 of lexicographically sorted objects ids written in this
450 * pack, as used to name a pack file in repository.
452 * @return ObjectId representing SHA-1 name of a pack that was created.
454 public ObjectId computeName() {
455 final MessageDigest md = Constants.newMessageDigest();
456 for (ObjectToPack otp : sortByName()) {
457 otp.copyRawTo(buf, 0);
458 md.update(buf, 0, Constants.OBJECT_ID_LENGTH);
460 return ObjectId.fromRaw(md.digest());
464 * Create an index file to match the pack file just written.
465 * <p>
466 * This method can only be invoked after {@link #preparePack(Iterator)} or
467 * {@link #preparePack(Collection, Collection, boolean, boolean)} has been
468 * invoked and completed successfully. Writing a corresponding index is an
469 * optional feature that not all pack users may require.
471 * @param indexStream
472 * output for the index data. Caller is responsible for closing
473 * this stream.
474 * @throws IOException
475 * the index data could not be written to the supplied stream.
477 public void writeIndex(final OutputStream indexStream) throws IOException {
478 final List<ObjectToPack> list = sortByName();
479 final PackIndexWriter iw;
480 if (outputVersion <= 0)
481 iw = PackIndexWriter.createOldestPossible(indexStream, list);
482 else
483 iw = PackIndexWriter.createVersion(indexStream, outputVersion);
484 iw.write(list, packcsum);
487 private List<ObjectToPack> sortByName() {
488 if (sortedByName == null) {
489 sortedByName = new ArrayList<ObjectToPack>(objectsMap.size());
490 for (List<ObjectToPack> list : objectsLists) {
491 for (ObjectToPack otp : list)
492 sortedByName.add(otp);
494 Collections.sort(sortedByName);
496 return sortedByName;
500 * Write the prepared pack to the supplied stream.
501 * <p>
502 * At first, this method collects and sorts objects to pack, then deltas
503 * search is performed if set up accordingly, finally pack stream is
504 * written. {@link ProgressMonitor} tasks {@value #SEARCHING_REUSE_PROGRESS}
505 * (only if resueDeltas or reuseObjects is enabled) and
506 * {@value #WRITING_OBJECTS_PROGRESS} are updated during packing.
507 * </p>
508 * <p>
509 * All reused objects data checksum (Adler32/CRC32) is computed and
510 * validated against existing checksum.
511 * </p>
513 * @param packStream
514 * output stream of pack data. If the stream is not buffered it
515 * will be buffered by the writer. Caller is responsible for
516 * closing the stream.
517 * @throws IOException
518 * an error occurred reading a local object's data to include in
519 * the pack, or writing compressed object data to the output
520 * stream.
522 public void writePack(OutputStream packStream) throws IOException {
523 if (reuseDeltas || reuseObjects)
524 searchForReuse();
526 if (!(packStream instanceof BufferedOutputStream))
527 packStream = new BufferedOutputStream(packStream);
528 countingOut = new CountingOutputStream(packStream);
529 out = new DigestOutputStream(countingOut, Constants.newMessageDigest());
531 monitor.beginTask(WRITING_OBJECTS_PROGRESS, getObjectsNumber());
532 writeHeader();
533 writeObjects();
534 writeChecksum();
536 out.flush();
537 windowCursor.release();
538 monitor.endTask();
541 private void searchForReuse() throws IOException {
542 monitor.beginTask(SEARCHING_REUSE_PROGRESS, getObjectsNumber());
543 final Collection<PackedObjectLoader> reuseLoaders = new LinkedList<PackedObjectLoader>();
545 for (List<ObjectToPack> list : objectsLists) {
546 for (ObjectToPack otp : list) {
547 if (monitor.isCancelled())
548 throw new IOException(
549 "Packing cancelled during objects writing");
550 reuseLoaders.clear();
551 db.openObjectInAllPacks(otp, reuseLoaders, windowCursor);
552 if (reuseDeltas) {
553 selectDeltaReuseForObject(otp, reuseLoaders);
555 // delta reuse is preferred over object reuse
556 if (reuseObjects && !otp.hasReuseLoader()) {
557 selectObjectReuseForObject(otp, reuseLoaders);
559 monitor.update(1);
563 monitor.endTask();
566 private void selectDeltaReuseForObject(final ObjectToPack otp,
567 final Collection<PackedObjectLoader> loaders) throws IOException {
568 PackedObjectLoader bestLoader = null;
569 ObjectId bestBase = null;
571 for (PackedObjectLoader loader : loaders) {
572 ObjectId idBase = loader.getDeltaBase();
573 if (idBase == null)
574 continue;
575 ObjectToPack otpBase = objectsMap.get(idBase);
577 // only if base is in set of objects to write or thin-pack's edge
578 if ((otpBase != null || (thin && edgeObjects.get(idBase) != null))
579 // select smallest possible delta if > 1 available
580 && isBetterDeltaReuseLoader(bestLoader, loader)) {
581 bestLoader = loader;
582 bestBase = (otpBase != null ? otpBase : idBase);
586 if (bestLoader != null) {
587 otp.setReuseLoader(bestLoader);
588 otp.setDeltaBase(bestBase);
592 private static boolean isBetterDeltaReuseLoader(
593 PackedObjectLoader currentLoader, PackedObjectLoader loader)
594 throws IOException {
595 if (currentLoader == null)
596 return true;
597 if (loader.getRawSize() < currentLoader.getRawSize())
598 return true;
599 return (loader.getRawSize() == currentLoader.getRawSize()
600 && loader.supportsFastCopyRawData() && !currentLoader
601 .supportsFastCopyRawData());
604 private void selectObjectReuseForObject(final ObjectToPack otp,
605 final Collection<PackedObjectLoader> loaders) {
606 for (final PackedObjectLoader loader : loaders) {
607 if (loader instanceof WholePackedObjectLoader) {
608 otp.setReuseLoader(loader);
609 return;
614 private void writeHeader() throws IOException {
615 out.write(Constants.PACK_SIGNATURE);
617 NB.encodeInt32(buf, 0, PACK_VERSION_GENERATED);
618 out.write(buf, 0, 4);
620 NB.encodeInt32(buf, 0, getObjectsNumber());
621 out.write(buf, 0, 4);
624 private void writeObjects() throws IOException {
625 for (List<ObjectToPack> list : objectsLists) {
626 for (ObjectToPack otp : list) {
627 if (monitor.isCancelled())
628 throw new IOException(
629 "Packing cancelled during objects writing");
630 if (!otp.isWritten())
631 writeObject(otp);
636 private void writeObject(final ObjectToPack otp) throws IOException {
637 otp.markWantWrite();
638 if (otp.isDeltaRepresentation()) {
639 ObjectToPack deltaBase = otp.getDeltaBase();
640 assert deltaBase != null || thin;
641 if (deltaBase != null && !deltaBase.isWritten()) {
642 if (deltaBase.wantWrite()) {
643 otp.clearDeltaBase(); // cycle detected
644 otp.disposeLoader();
645 } else {
646 writeObject(deltaBase);
650 otp.updateDeltaDepth();
651 if (otp.getDeltaDepth() > maxDeltaDepth) {
652 otp.clearDeltaBase();
653 otp.disposeLoader();
657 assert !otp.isWritten();
659 otp.setOffset(countingOut.getCount());
660 if (otp.isDeltaRepresentation())
661 writeDeltaObject(otp);
662 else
663 writeWholeObject(otp);
665 monitor.update(1);
668 private void writeWholeObject(final ObjectToPack otp) throws IOException {
669 if (otp.hasReuseLoader()) {
670 final PackedObjectLoader loader = otp.getReuseLoader();
671 writeObjectHeader(loader.getType(), loader.getSize());
672 loader.copyRawData(out, buf);
673 otp.disposeLoader();
674 } else {
675 final ObjectLoader loader = db.openObject(windowCursor, otp);
676 final DeflaterOutputStream deflaterOut = new DeflaterOutputStream(
677 out, deflater);
678 writeObjectHeader(loader.getType(), loader.getSize());
679 deflaterOut.write(loader.getCachedBytes());
680 deflaterOut.finish();
681 deflater.reset();
685 private void writeDeltaObject(final ObjectToPack otp) throws IOException {
686 final PackedObjectLoader loader = otp.getReuseLoader();
687 if (deltaBaseAsOffset && otp.getDeltaBase() != null) {
688 writeObjectHeader(Constants.OBJ_OFS_DELTA, loader.getRawSize());
690 final ObjectToPack deltaBase = otp.getDeltaBase();
691 long offsetDiff = otp.getOffset() - deltaBase.getOffset();
692 int pos = buf.length - 1;
693 buf[pos] = (byte) (offsetDiff & 0x7F);
694 while ((offsetDiff >>= 7) > 0) {
695 buf[--pos] = (byte) (0x80 | (--offsetDiff & 0x7F));
698 out.write(buf, pos, buf.length - pos);
699 } else {
700 writeObjectHeader(Constants.OBJ_REF_DELTA, loader.getRawSize());
701 otp.getDeltaBaseId().copyRawTo(buf, 0);
702 out.write(buf, 0, Constants.OBJECT_ID_LENGTH);
704 loader.copyRawData(out, buf);
705 otp.disposeLoader();
708 private void writeObjectHeader(final int objectType, long dataLength)
709 throws IOException {
710 long nextLength = dataLength >>> 4;
711 int size = 0;
712 buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00)
713 | (objectType << 4) | (dataLength & 0x0F));
714 dataLength = nextLength;
715 while (dataLength > 0) {
716 nextLength >>>= 7;
717 buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (dataLength & 0x7F));
718 dataLength = nextLength;
720 out.write(buf, 0, size);
723 private void writeChecksum() throws IOException {
724 out.on(false);
725 packcsum = out.getMessageDigest().digest();
726 out.write(packcsum);
729 private ObjectWalk setUpWalker(
730 final Collection<ObjectId> interestingObjects,
731 final Collection<ObjectId> uninterestingObjects,
732 final boolean thin, final boolean ignoreMissingUninteresting)
733 throws MissingObjectException, IOException,
734 IncorrectObjectTypeException {
735 final ObjectWalk walker = new ObjectWalk(db);
736 walker.sort(RevSort.TOPO, true);
737 walker.sort(RevSort.COMMIT_TIME_DESC, true);
738 if (thin)
739 walker.sort(RevSort.BOUNDARY);
741 for (ObjectId id : interestingObjects) {
742 RevObject o = walker.parseAny(id);
743 walker.markStart(o);
745 for (ObjectId id : uninterestingObjects) {
746 final RevObject o;
747 try {
748 o = walker.parseAny(id);
749 } catch (MissingObjectException x) {
750 if (ignoreMissingUninteresting)
751 continue;
752 throw x;
754 walker.markUninteresting(o);
756 return walker;
759 private void findObjectsToPack(final ObjectWalk walker)
760 throws MissingObjectException, IncorrectObjectTypeException,
761 IOException {
762 monitor.beginTask(COUNTING_OBJECTS_PROGRESS, ProgressMonitor.UNKNOWN);
763 RevObject o;
765 while ((o = walker.next()) != null) {
766 addObject(o);
767 monitor.update(1);
769 while ((o = walker.nextObject()) != null) {
770 addObject(o);
771 monitor.update(1);
773 monitor.endTask();
776 private void addObject(RevObject object)
777 throws IncorrectObjectTypeException {
778 if (object.has(RevFlag.UNINTERESTING)) {
779 edgeObjects.add(object);
780 thin = true;
781 return;
784 final ObjectToPack otp = new ObjectToPack(object);
785 try {
786 objectsLists[object.getType()].add(otp);
787 } catch (ArrayIndexOutOfBoundsException x) {
788 throw new IncorrectObjectTypeException(object,
789 "COMMIT nor TREE nor BLOB nor TAG");
790 } catch (UnsupportedOperationException x) {
791 // index pointing to "dummy" empty list
792 throw new IncorrectObjectTypeException(object,
793 "COMMIT nor TREE nor BLOB nor TAG");
795 objectsMap.add(otp);
799 * Class holding information about object that is going to be packed by
800 * {@link PackWriter}. Information include object representation in a
801 * pack-file and object status.
804 static class ObjectToPack extends PackedObjectInfo {
805 private ObjectId deltaBase;
807 private PackedObjectLoader reuseLoader;
809 private int deltaDepth;
811 private boolean wantWrite;
814 * Construct object for specified object id. <br/> By default object is
815 * marked as not written and non-delta packed (as a whole object).
817 * @param src
818 * object id of object for packing
820 ObjectToPack(AnyObjectId src) {
821 super(src);
825 * @return delta base object id if object is going to be packed in delta
826 * representation; null otherwise - if going to be packed as a
827 * whole object.
829 ObjectId getDeltaBaseId() {
830 return deltaBase;
834 * @return delta base object to pack if object is going to be packed in
835 * delta representation and delta is specified as object to
836 * pack; null otherwise - if going to be packed as a whole
837 * object or delta base is specified only as id.
839 ObjectToPack getDeltaBase() {
840 if (deltaBase instanceof ObjectToPack)
841 return (ObjectToPack) deltaBase;
842 return null;
846 * Set delta base for the object. Delta base set by this method is used
847 * by {@link PackWriter} to write object - determines its representation
848 * in a created pack.
850 * @param deltaBase
851 * delta base object or null if object should be packed as a
852 * whole object.
855 void setDeltaBase(ObjectId deltaBase) {
856 this.deltaBase = deltaBase;
859 void clearDeltaBase() {
860 this.deltaBase = null;
864 * @return true if object is going to be written as delta; false
865 * otherwise.
867 boolean isDeltaRepresentation() {
868 return deltaBase != null;
872 * Check if object is already written in a pack. This information is
873 * used to achieve delta-base precedence in a pack file.
875 * @return true if object is already written; false otherwise.
877 boolean isWritten() {
878 return getOffset() != 0;
881 PackedObjectLoader getReuseLoader() {
882 return reuseLoader;
885 boolean hasReuseLoader() {
886 return reuseLoader != null;
889 void setReuseLoader(PackedObjectLoader reuseLoader) {
890 this.reuseLoader = reuseLoader;
893 void disposeLoader() {
894 this.reuseLoader = null;
897 int getDeltaDepth() {
898 return deltaDepth;
901 void updateDeltaDepth() {
902 if (deltaBase instanceof ObjectToPack)
903 deltaDepth = ((ObjectToPack) deltaBase).deltaDepth + 1;
904 else if (deltaBase != null)
905 deltaDepth = 1;
908 boolean wantWrite() {
909 return wantWrite;
912 void markWantWrite() {
913 this.wantWrite = true;