1
2
3
4
5
6
7
8
9
10
11
12 package org.eclipse.jgit.internal.storage.pack;
13
14 import static java.util.Objects.requireNonNull;
15 import static org.eclipse.jgit.internal.storage.pack.StoredObjectRepresentation.PACK_DELTA;
16 import static org.eclipse.jgit.internal.storage.pack.StoredObjectRepresentation.PACK_WHOLE;
17 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
18 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
19 import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
20 import static org.eclipse.jgit.lib.Constants.OBJ_TAG;
21 import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
22
23 import java.io.IOException;
24 import java.io.OutputStream;
25 import java.lang.ref.WeakReference;
26 import java.security.MessageDigest;
27 import java.text.MessageFormat;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.NoSuchElementException;
38 import java.util.Set;
39 import java.util.concurrent.ConcurrentHashMap;
40 import java.util.concurrent.ExecutionException;
41 import java.util.concurrent.Executor;
42 import java.util.concurrent.ExecutorService;
43 import java.util.concurrent.Executors;
44 import java.util.concurrent.Future;
45 import java.util.concurrent.TimeUnit;
46 import java.util.zip.CRC32;
47 import java.util.zip.CheckedOutputStream;
48 import java.util.zip.Deflater;
49 import java.util.zip.DeflaterOutputStream;
50
51 import org.eclipse.jgit.annotations.NonNull;
52 import org.eclipse.jgit.annotations.Nullable;
53 import org.eclipse.jgit.errors.CorruptObjectException;
54 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
55 import org.eclipse.jgit.errors.LargeObjectException;
56 import org.eclipse.jgit.errors.MissingObjectException;
57 import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
58 import org.eclipse.jgit.internal.JGitText;
59 import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder;
60 import org.eclipse.jgit.internal.storage.file.PackBitmapIndexWriterV1;
61 import org.eclipse.jgit.internal.storage.file.PackIndexWriter;
62 import org.eclipse.jgit.lib.AnyObjectId;
63 import org.eclipse.jgit.lib.AsyncObjectSizeQueue;
64 import org.eclipse.jgit.lib.BatchingProgressMonitor;
65 import org.eclipse.jgit.lib.BitmapIndex;
66 import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
67 import org.eclipse.jgit.lib.BitmapObject;
68 import org.eclipse.jgit.lib.Constants;
69 import org.eclipse.jgit.lib.NullProgressMonitor;
70 import org.eclipse.jgit.lib.ObjectId;
71 import org.eclipse.jgit.lib.ObjectIdOwnerMap;
72 import org.eclipse.jgit.lib.ObjectIdSet;
73 import org.eclipse.jgit.lib.ObjectLoader;
74 import org.eclipse.jgit.lib.ObjectReader;
75 import org.eclipse.jgit.lib.ProgressMonitor;
76 import org.eclipse.jgit.lib.Repository;
77 import org.eclipse.jgit.lib.ThreadSafeProgressMonitor;
78 import org.eclipse.jgit.revwalk.AsyncRevObjectQueue;
79 import org.eclipse.jgit.revwalk.BitmapWalker;
80 import org.eclipse.jgit.revwalk.DepthWalk;
81 import org.eclipse.jgit.revwalk.ObjectWalk;
82 import org.eclipse.jgit.revwalk.RevCommit;
83 import org.eclipse.jgit.revwalk.RevFlag;
84 import org.eclipse.jgit.revwalk.RevObject;
85 import org.eclipse.jgit.revwalk.RevSort;
86 import org.eclipse.jgit.revwalk.RevTag;
87 import org.eclipse.jgit.revwalk.RevTree;
88 import org.eclipse.jgit.storage.pack.PackConfig;
89 import org.eclipse.jgit.storage.pack.PackStatistics;
90 import org.eclipse.jgit.transport.FilterSpec;
91 import org.eclipse.jgit.transport.ObjectCountCallback;
92 import org.eclipse.jgit.transport.PacketLineOut;
93 import org.eclipse.jgit.transport.WriteAbortedException;
94 import org.eclipse.jgit.util.BlockList;
95 import org.eclipse.jgit.util.TemporaryBuffer;
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136 public class PackWriter implements AutoCloseable {
137 private static final int PACK_VERSION_GENERATED = 2;
138
139
140 public static final Set<ObjectId> NONE = Collections.emptySet();
141
142 private static final Map<WeakReference<PackWriter>, Boolean> instances =
143 new ConcurrentHashMap<>();
144
145 private static final Iterable<PackWriter> instancesIterable = () -> new Iterator<PackWriter>() {
146
147 private final Iterator<WeakReference<PackWriter>> it = instances
148 .keySet().iterator();
149
150 private PackWriter next;
151
152 @Override
153 public boolean hasNext() {
154 if (next != null) {
155 return true;
156 }
157 while (it.hasNext()) {
158 WeakReference<PackWriter> ref = it.next();
159 next = ref.get();
160 if (next != null) {
161 return true;
162 }
163 it.remove();
164 }
165 return false;
166 }
167
168 @Override
169 public PackWriter next() {
170 if (hasNext()) {
171 PackWriter result = next;
172 next = null;
173 return result;
174 }
175 throw new NoSuchElementException();
176 }
177
178 @Override
179 public void remove() {
180 throw new UnsupportedOperationException();
181 }
182 };
183
184
185
186
187
188
189 public static Iterable<PackWriter> getInstances() {
190 return instancesIterable;
191 }
192
193 @SuppressWarnings("unchecked")
194 BlockList<ObjectToPack>[] objectsLists = new BlockList[OBJ_TAG + 1];
195 {
196 objectsLists[OBJ_COMMIT] = new BlockList<>();
197 objectsLists[OBJ_TREE] = new BlockList<>();
198 objectsLists[OBJ_BLOB] = new BlockList<>();
199 objectsLists[OBJ_TAG] = new BlockList<>();
200 }
201
202 private ObjectIdOwnerMap<ObjectToPack> objectsMap = new ObjectIdOwnerMap<>();
203
204
205 private List<ObjectToPack> edgeObjects = new BlockList<>();
206
207
208 private BitmapBuilder haveObjects;
209
210 private List<CachedPack> cachedPacks = new ArrayList<>(2);
211
212 private Set<ObjectId> tagTargets = NONE;
213
214 private Set<? extends ObjectId> excludeFromBitmapSelection = NONE;
215
216 private ObjectIdSet[] excludeInPacks;
217
218 private ObjectIdSet excludeInPackLast;
219
220 private Deflater myDeflater;
221
222 private final ObjectReader reader;
223
224
225 private final ObjectReuseAsIs reuseSupport;
226
227 final PackConfig config;
228
229 private final PackStatistics.Accumulator stats;
230
231 private final MutableState state;
232
233 private final WeakReference<PackWriter> selfRef;
234
235 private PackStatistics.ObjectType.Accumulator typeStats;
236
237 private List<ObjectToPack> sortedByName;
238
239 private byte[] packcsum;
240
241 private boolean deltaBaseAsOffset;
242
243 private boolean reuseDeltas;
244
245 private boolean reuseDeltaCommits;
246
247 private boolean reuseValidate;
248
249 private boolean thin;
250
251 private boolean useCachedPacks;
252
253 private boolean useBitmaps;
254
255 private boolean ignoreMissingUninteresting = true;
256
257 private boolean pruneCurrentObjectList;
258
259 private boolean shallowPack;
260
261 private boolean canBuildBitmaps;
262
263 private boolean indexDisabled;
264
265 private int depth;
266
267 private Collection<? extends ObjectId> unshallowObjects;
268
269 private PackBitmapIndexBuilder writeBitmaps;
270
271 private CRC32 crc32;
272
273 private ObjectCountCallback callback;
274
275 private FilterSpec filterSpec = FilterSpec.NO_FILTER;
276
277 private PackfileUriConfig packfileUriConfig;
278
279
280
281
282
283
284
285
286
287
288 public PackWriter(Repository repo) {
289 this(repo, repo.newObjectReader());
290 }
291
292
293
294
295
296
297
298
299
300
301 public PackWriter(ObjectReader reader) {
302 this(new PackConfig(), reader);
303 }
304
305
306
307
308
309
310
311
312
313
314
315
316 public PackWriter(Repository repo, ObjectReader reader) {
317 this(new PackConfig(repo), reader);
318 }
319
320
321
322
323
324
325
326
327
328
329
330
331 public PackWriter(PackConfig config, ObjectReader reader) {
332 this(config, reader, null);
333 }
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348 public PackWriter(PackConfig config, final ObjectReader reader,
349 @Nullable PackStatistics.Accumulator statsAccumulator) {
350 this.config = config;
351 this.reader = reader;
352 if (reader instanceof ObjectReuseAsIs)
353 reuseSupport = ((ObjectReuseAsIs) reader);
354 else
355 reuseSupport = null;
356
357 deltaBaseAsOffset = config.isDeltaBaseAsOffset();
358 reuseDeltas = config.isReuseDeltas();
359 reuseValidate = true;
360 stats = statsAccumulator != null ? statsAccumulator
361 : new PackStatistics.Accumulator();
362 state = new MutableState();
363 selfRef = new WeakReference<>(this);
364 instances.put(selfRef, Boolean.TRUE);
365 }
366
367
368
369
370
371
372
373
374
375
376
377 public PackWriter setObjectCountCallback(ObjectCountCallback callback) {
378 this.callback = callback;
379 return this;
380 }
381
382
383
384
385
386
387
388 public void setClientShallowCommits(Set<ObjectId> clientShallowCommits) {
389 stats.clientShallowCommits = Collections
390 .unmodifiableSet(new HashSet<>(clientShallowCommits));
391 }
392
393
394
395
396
397
398
399
400
401
402
403 public boolean isDeltaBaseAsOffset() {
404 return deltaBaseAsOffset;
405 }
406
407
408
409
410
411
412
413
414
415
416
417
418 public void setDeltaBaseAsOffset(boolean deltaBaseAsOffset) {
419 this.deltaBaseAsOffset = deltaBaseAsOffset;
420 }
421
422
423
424
425
426
427
428 public boolean isReuseDeltaCommits() {
429 return reuseDeltaCommits;
430 }
431
432
433
434
435
436
437
438
439 public void setReuseDeltaCommits(boolean reuse) {
440 reuseDeltaCommits = reuse;
441 }
442
443
444
445
446
447
448
449 public boolean isReuseValidatingObjects() {
450 return reuseValidate;
451 }
452
453
454
455
456
457
458
459
460
461
462 public void setReuseValidatingObjects(boolean validate) {
463 reuseValidate = validate;
464 }
465
466
467
468
469
470
471 public boolean isThin() {
472 return thin;
473 }
474
475
476
477
478
479
480
481
482
483
484
485
486 public void setThin(boolean packthin) {
487 thin = packthin;
488 }
489
490
491
492
493
494
495
496 public boolean isUseCachedPacks() {
497 return useCachedPacks;
498 }
499
500
501
502
503
504
505
506
507
508
509
510
511 public void setUseCachedPacks(boolean useCached) {
512 useCachedPacks = useCached;
513 }
514
515
516
517
518
519
520 public boolean isUseBitmaps() {
521 return useBitmaps;
522 }
523
524
525
526
527
528
529
530 public void setUseBitmaps(boolean useBitmaps) {
531 this.useBitmaps = useBitmaps;
532 }
533
534
535
536
537
538
539
540 public boolean isIndexDisabled() {
541 return indexDisabled || !cachedPacks.isEmpty();
542 }
543
544
545
546
547
548
549
550 public void setIndexDisabled(boolean noIndex) {
551 this.indexDisabled = noIndex;
552 }
553
554
555
556
557
558
559
560
561
562
563
564 public boolean isIgnoreMissingUninteresting() {
565 return ignoreMissingUninteresting;
566 }
567
568
569
570
571
572
573
574
575
576
577 public void setIgnoreMissingUninteresting(boolean ignore) {
578 ignoreMissingUninteresting = ignore;
579 }
580
581
582
583
584
585
586
587
588
589
590
591
592
593 public void setTagTargets(Set<ObjectId> objects) {
594 tagTargets = objects;
595 }
596
597
598
599
600
601
602
603
604
605
606
607 public void setShallowPack(int depth,
608 Collection<? extends ObjectId> unshallow) {
609 this.shallowPack = true;
610 this.depth = depth;
611 this.unshallowObjects = unshallow;
612 }
613
614
615
616
617
618 public void setFilterSpec(@NonNull FilterSpec filter) {
619 filterSpec = requireNonNull(filter);
620 }
621
622
623
624
625
626 public void setPackfileUriConfig(PackfileUriConfig config) {
627 packfileUriConfig = config;
628 }
629
630
631
632
633
634
635
636
637 public long getObjectCount() throws IOException {
638 if (stats.totalObjects == 0) {
639 long objCnt = 0;
640
641 objCnt += objectsLists[OBJ_COMMIT].size();
642 objCnt += objectsLists[OBJ_TREE].size();
643 objCnt += objectsLists[OBJ_BLOB].size();
644 objCnt += objectsLists[OBJ_TAG].size();
645
646 for (CachedPack pack : cachedPacks)
647 objCnt += pack.getObjectCount();
648 return objCnt;
649 }
650 return stats.totalObjects;
651 }
652
653 private long getUnoffloadedObjectCount() throws IOException {
654 long objCnt = 0;
655
656 objCnt += objectsLists[OBJ_COMMIT].size();
657 objCnt += objectsLists[OBJ_TREE].size();
658 objCnt += objectsLists[OBJ_BLOB].size();
659 objCnt += objectsLists[OBJ_TAG].size();
660
661 for (CachedPack pack : cachedPacks) {
662 CachedPackUriProvider.PackInfo packInfo =
663 packfileUriConfig.cachedPackUriProvider.getInfo(
664 pack, packfileUriConfig.protocolsSupported);
665 if (packInfo == null) {
666 objCnt += pack.getObjectCount();
667 }
668 }
669
670 return objCnt;
671 }
672
673
674
675
676
677
678
679
680
681
682
683
684 public ObjectIdOwnerMap<ObjectIdOwnerMap.Entry> getObjectSet()
685 throws IOException {
686 if (!cachedPacks.isEmpty())
687 throw new IOException(
688 JGitText.get().cachedPacksPreventsListingObjects);
689
690 if (writeBitmaps != null) {
691 return writeBitmaps.getObjectSet();
692 }
693
694 ObjectIdOwnerMap<ObjectIdOwnerMap.Entry> r = new ObjectIdOwnerMap<>();
695 for (BlockList<ObjectToPack> objList : objectsLists) {
696 if (objList != null) {
697 for (ObjectToPack otp : objList)
698 r.add(new ObjectIdOwnerMap.Entry(otp) {
699
700 });
701 }
702 }
703 return r;
704 }
705
706
707
708
709
710
711
712 public void excludeObjects(ObjectIdSet idx) {
713 if (excludeInPacks == null) {
714 excludeInPacks = new ObjectIdSet[] { idx };
715 excludeInPackLast = idx;
716 } else {
717 int cnt = excludeInPacks.length;
718 ObjectIdSet[] newList = new ObjectIdSet[cnt + 1];
719 System.arraycopy(excludeInPacks, 0, newList, 0, cnt);
720 newList[cnt] = idx;
721 excludeInPacks = newList;
722 }
723 }
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750 public void preparePack(@NonNull Iterator<RevObject> objectsSource)
751 throws IOException {
752 while (objectsSource.hasNext()) {
753 addObject(objectsSource.next());
754 }
755 }
756
757
758
759
760
761
762
763
764
765
766 public void preparePack(Collection<? extends CachedPack> c) {
767 cachedPacks.addAll(c);
768 }
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795 public void preparePack(ProgressMonitor countingMonitor,
796 @NonNull Set<? extends ObjectId> want,
797 @NonNull Set<? extends ObjectId> have) throws IOException {
798 preparePack(countingMonitor, want, have, NONE, NONE);
799 }
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830 public void preparePack(ProgressMonitor countingMonitor,
831 @NonNull Set<? extends ObjectId> want,
832 @NonNull Set<? extends ObjectId> have,
833 @NonNull Set<? extends ObjectId> shallow) throws IOException {
834 preparePack(countingMonitor, want, have, shallow, NONE);
835 }
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869 public void preparePack(ProgressMonitor countingMonitor,
870 @NonNull Set<? extends ObjectId> want,
871 @NonNull Set<? extends ObjectId> have,
872 @NonNull Set<? extends ObjectId> shallow,
873 @NonNull Set<? extends ObjectId> noBitmaps) throws IOException {
874 try (ObjectWalk ow = getObjectWalk()) {
875 ow.assumeShallow(shallow);
876 preparePack(countingMonitor, ow, want, have, noBitmaps);
877 }
878 }
879
880 private ObjectWalk getObjectWalk() {
881 return shallowPack ? new DepthWalk.ObjectWalk(reader, depth - 1)
882 : new ObjectWalk(reader);
883 }
884
885
886
887
888
889
890
891 private static class DepthAwareVisitationPolicy
892 implements ObjectWalk.VisitationPolicy {
893 private final Map<ObjectId, Integer> lowestDepthVisited = new HashMap<>();
894
895 private final ObjectWalk walk;
896
897 DepthAwareVisitationPolicy(ObjectWalk walk) {
898 this.walk = requireNonNull(walk);
899 }
900
901 @Override
902 public boolean shouldVisit(RevObject o) {
903 Integer lastDepth = lowestDepthVisited.get(o);
904 if (lastDepth == null) {
905 return true;
906 }
907 return walk.getTreeDepth() < lastDepth.intValue();
908 }
909
910 @Override
911 public void visited(RevObject o) {
912 lowestDepthVisited.put(o, Integer.valueOf(walk.getTreeDepth()));
913 }
914 }
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946 public void preparePack(ProgressMonitor countingMonitor,
947 @NonNull ObjectWalk walk,
948 @NonNull Set<? extends ObjectId> interestingObjects,
949 @NonNull Set<? extends ObjectId> uninterestingObjects,
950 @NonNull Set<? extends ObjectId> noBitmaps)
951 throws IOException {
952 if (countingMonitor == null)
953 countingMonitor = NullProgressMonitor.INSTANCE;
954 if (shallowPack && !(walk instanceof DepthWalk.ObjectWalk))
955 throw new IllegalArgumentException(
956 JGitText.get().shallowPacksRequireDepthWalk);
957 if (filterSpec.getTreeDepthLimit() >= 0) {
958 walk.setVisitationPolicy(new DepthAwareVisitationPolicy(walk));
959 }
960 findObjectsToPack(countingMonitor, walk, interestingObjects,
961 uninterestingObjects, noBitmaps);
962 }
963
964
965
966
967
968
969
970
971
972
973 public boolean willInclude(AnyObjectId id) throws IOException {
974 ObjectToPack obj = objectsMap.get(id);
975 return obj != null && !obj.isEdge();
976 }
977
978
979
980
981
982
983
984
985 public ObjectToPack get(AnyObjectId id) {
986 ObjectToPack obj = objectsMap.get(id);
987 return obj != null && !obj.isEdge() ? obj : null;
988 }
989
990
991
992
993
994
995
996 public ObjectId computeName() {
997 final byte[] buf = new byte[OBJECT_ID_LENGTH];
998 final MessageDigest md = Constants.newMessageDigest();
999 for (ObjectToPack otp : sortByName()) {
1000 otp.copyRawTo(buf, 0);
1001 md.update(buf, 0, OBJECT_ID_LENGTH);
1002 }
1003 return ObjectId.fromRaw(md.digest());
1004 }
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015 public int getIndexVersion() {
1016 int indexVersion = config.getIndexVersion();
1017 if (indexVersion <= 0) {
1018 for (BlockList<ObjectToPack> objs : objectsLists)
1019 indexVersion = Math.max(indexVersion,
1020 PackIndexWriter.oldestPossibleFormat(objs));
1021 }
1022 return indexVersion;
1023 }
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040 public void writeIndex(OutputStream indexStream) throws IOException {
1041 if (isIndexDisabled())
1042 throw new IOException(JGitText.get().cachedPacksPreventsIndexCreation);
1043
1044 long writeStart = System.currentTimeMillis();
1045 final PackIndexWriter iw = PackIndexWriter.createVersion(
1046 indexStream, getIndexVersion());
1047 iw.write(sortByName(), packcsum);
1048 stats.timeWriting += System.currentTimeMillis() - writeStart;
1049 }
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062 public void writeBitmapIndex(OutputStream bitmapIndexStream)
1063 throws IOException {
1064 if (writeBitmaps == null)
1065 throw new IOException(JGitText.get().bitmapsMustBePrepared);
1066
1067 long writeStart = System.currentTimeMillis();
1068 final PackBitmapIndexWriterV1ile/PackBitmapIndexWriterV1.html#PackBitmapIndexWriterV1">PackBitmapIndexWriterV1 iw = new PackBitmapIndexWriterV1(bitmapIndexStream);
1069 iw.write(writeBitmaps, packcsum);
1070 stats.timeWriting += System.currentTimeMillis() - writeStart;
1071 }
1072
1073 private List<ObjectToPack> sortByName() {
1074 if (sortedByName == null) {
1075 int cnt = 0;
1076 cnt += objectsLists[OBJ_COMMIT].size();
1077 cnt += objectsLists[OBJ_TREE].size();
1078 cnt += objectsLists[OBJ_BLOB].size();
1079 cnt += objectsLists[OBJ_TAG].size();
1080
1081 sortedByName = new BlockList<>(cnt);
1082 sortedByName.addAll(objectsLists[OBJ_COMMIT]);
1083 sortedByName.addAll(objectsLists[OBJ_TREE]);
1084 sortedByName.addAll(objectsLists[OBJ_BLOB]);
1085 sortedByName.addAll(objectsLists[OBJ_TAG]);
1086 Collections.sort(sortedByName);
1087 }
1088 return sortedByName;
1089 }
1090
1091 private void beginPhase(PackingPhase phase, ProgressMonitor monitor,
1092 long cnt) {
1093 state.phase = phase;
1094 String task;
1095 switch (phase) {
1096 case COUNTING:
1097 task = JGitText.get().countingObjects;
1098 break;
1099 case GETTING_SIZES:
1100 task = JGitText.get().searchForSizes;
1101 break;
1102 case FINDING_SOURCES:
1103 task = JGitText.get().searchForReuse;
1104 break;
1105 case COMPRESSING:
1106 task = JGitText.get().compressingObjects;
1107 break;
1108 case WRITING:
1109 task = JGitText.get().writingObjects;
1110 break;
1111 case BUILDING_BITMAPS:
1112 task = JGitText.get().buildingBitmaps;
1113 break;
1114 default:
1115 throw new IllegalArgumentException(
1116 MessageFormat.format(JGitText.get().illegalPackingPhase, phase));
1117 }
1118 monitor.beginTask(task, (int) cnt);
1119 }
1120
1121 private void endPhase(ProgressMonitor monitor) {
1122 monitor.endTask();
1123 }
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152 public void writePack(ProgressMonitor compressMonitor,
1153 ProgressMonitor writeMonitor, OutputStream packStream)
1154 throws IOException {
1155 if (compressMonitor == null)
1156 compressMonitor = NullProgressMonitor.INSTANCE;
1157 if (writeMonitor == null)
1158 writeMonitor = NullProgressMonitor.INSTANCE;
1159
1160 excludeInPacks = null;
1161 excludeInPackLast = null;
1162
1163 boolean needSearchForReuse = reuseSupport != null && (
1164 reuseDeltas
1165 || config.isReuseObjects()
1166 || !cachedPacks.isEmpty());
1167
1168 if (compressMonitor instanceof BatchingProgressMonitor) {
1169 long delay = 1000;
1170 if (needSearchForReuse && config.isDeltaCompress())
1171 delay = 500;
1172 ((BatchingProgressMonitor) compressMonitor).setDelayStart(
1173 delay,
1174 TimeUnit.MILLISECONDS);
1175 }
1176
1177 if (needSearchForReuse)
1178 searchForReuse(compressMonitor);
1179 if (config.isDeltaCompress())
1180 searchForDeltas(compressMonitor);
1181
1182 crc32 = new CRC32();
1183 final PackOutputStreamrage/pack/PackOutputStream.html#PackOutputStream">PackOutputStream out = new PackOutputStream(
1184 writeMonitor,
1185 isIndexDisabled()
1186 ? packStream
1187 : new CheckedOutputStream(packStream, crc32),
1188 this);
1189
1190 long objCnt = packfileUriConfig == null ? getObjectCount() :
1191 getUnoffloadedObjectCount();
1192 stats.totalObjects = objCnt;
1193 if (callback != null)
1194 callback.setObjectCount(objCnt);
1195 beginPhase(PackingPhase.WRITING, writeMonitor, objCnt);
1196 long writeStart = System.currentTimeMillis();
1197 try {
1198 List<CachedPack> unwrittenCachedPacks;
1199
1200 if (packfileUriConfig != null) {
1201 unwrittenCachedPacks = new ArrayList<>();
1202 CachedPackUriProvider p = packfileUriConfig.cachedPackUriProvider;
1203 PacketLineOut o = packfileUriConfig.pckOut;
1204
1205 o.writeString("packfile-uris\n");
1206 for (CachedPack pack : cachedPacks) {
1207 CachedPackUriProvider.PackInfo packInfo = p.getInfo(
1208 pack, packfileUriConfig.protocolsSupported);
1209 if (packInfo != null) {
1210 o.writeString(packInfo.getHash() + ' ' +
1211 packInfo.getUri() + '\n');
1212 stats.offloadedPackfiles += 1;
1213 stats.offloadedPackfileSize += packInfo.getSize();
1214 } else {
1215 unwrittenCachedPacks.add(pack);
1216 }
1217 }
1218 packfileUriConfig.pckOut.writeDelim();
1219 packfileUriConfig.pckOut.writeString("packfile\n");
1220 } else {
1221 unwrittenCachedPacks = cachedPacks;
1222 }
1223
1224 out.writeFileHeader(PACK_VERSION_GENERATED, objCnt);
1225 out.flush();
1226
1227 writeObjects(out);
1228 if (!edgeObjects.isEmpty() || !cachedPacks.isEmpty()) {
1229 for (PackStatistics.ObjectType.Accumulator typeStat : stats.objectTypes) {
1230 if (typeStat == null)
1231 continue;
1232 stats.thinPackBytes += typeStat.bytes;
1233 }
1234 }
1235
1236 stats.reusedPacks = Collections.unmodifiableList(cachedPacks);
1237 for (CachedPack pack : unwrittenCachedPacks) {
1238 long deltaCnt = pack.getDeltaCount();
1239 stats.reusedObjects += pack.getObjectCount();
1240 stats.reusedDeltas += deltaCnt;
1241 stats.totalDeltas += deltaCnt;
1242 reuseSupport.copyPackAsIs(out, pack);
1243 }
1244 writeChecksum(out);
1245 out.flush();
1246 } finally {
1247 stats.timeWriting = System.currentTimeMillis() - writeStart;
1248 stats.depth = depth;
1249
1250 for (PackStatistics.ObjectType.Accumulator typeStat : stats.objectTypes) {
1251 if (typeStat == null)
1252 continue;
1253 typeStat.cntDeltas += typeStat.reusedDeltas;
1254 stats.reusedObjects += typeStat.reusedObjects;
1255 stats.reusedDeltas += typeStat.reusedDeltas;
1256 stats.totalDeltas += typeStat.cntDeltas;
1257 }
1258 }
1259
1260 stats.totalBytes = out.length();
1261 reader.close();
1262 endPhase(writeMonitor);
1263 }
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273 public PackStatistics getStatistics() {
1274 return new PackStatistics(stats);
1275 }
1276
1277
1278
1279
1280
1281
1282 public State getState() {
1283 return state.snapshot();
1284 }
1285
1286
1287
1288
1289
1290
1291 @Override
1292 public void close() {
1293 reader.close();
1294 if (myDeflater != null) {
1295 myDeflater.end();
1296 myDeflater = null;
1297 }
1298 instances.remove(selfRef);
1299 }
1300
1301 private void searchForReuse(ProgressMonitor monitor) throws IOException {
1302 long cnt = 0;
1303 cnt += objectsLists[OBJ_COMMIT].size();
1304 cnt += objectsLists[OBJ_TREE].size();
1305 cnt += objectsLists[OBJ_BLOB].size();
1306 cnt += objectsLists[OBJ_TAG].size();
1307
1308 long start = System.currentTimeMillis();
1309 beginPhase(PackingPhase.FINDING_SOURCES, monitor, cnt);
1310 if (cnt <= 4096) {
1311
1312 BlockList<ObjectToPack> tmp = new BlockList<>((int) cnt);
1313 tmp.addAll(objectsLists[OBJ_TAG]);
1314 tmp.addAll(objectsLists[OBJ_COMMIT]);
1315 tmp.addAll(objectsLists[OBJ_TREE]);
1316 tmp.addAll(objectsLists[OBJ_BLOB]);
1317 searchForReuse(monitor, tmp);
1318 if (pruneCurrentObjectList) {
1319
1320 pruneEdgesFromObjectList(objectsLists[OBJ_COMMIT]);
1321 pruneEdgesFromObjectList(objectsLists[OBJ_TREE]);
1322 pruneEdgesFromObjectList(objectsLists[OBJ_BLOB]);
1323 pruneEdgesFromObjectList(objectsLists[OBJ_TAG]);
1324 }
1325 } else {
1326 searchForReuse(monitor, objectsLists[OBJ_TAG]);
1327 searchForReuse(monitor, objectsLists[OBJ_COMMIT]);
1328 searchForReuse(monitor, objectsLists[OBJ_TREE]);
1329 searchForReuse(monitor, objectsLists[OBJ_BLOB]);
1330 }
1331 endPhase(monitor);
1332 stats.timeSearchingForReuse = System.currentTimeMillis() - start;
1333
1334 if (config.isReuseDeltas() && config.getCutDeltaChains()) {
1335 cutDeltaChains(objectsLists[OBJ_TREE]);
1336 cutDeltaChains(objectsLists[OBJ_BLOB]);
1337 }
1338 }
1339
1340 private void searchForReuse(ProgressMonitor monitor, List<ObjectToPack> list)
1341 throws IOException, MissingObjectException {
1342 pruneCurrentObjectList = false;
1343 reuseSupport.selectObjectRepresentation(this, monitor, list);
1344 if (pruneCurrentObjectList)
1345 pruneEdgesFromObjectList(list);
1346 }
1347
1348 private void cutDeltaChains(BlockList<ObjectToPack> list)
1349 throws IOException {
1350 int max = config.getMaxDeltaDepth();
1351 for (int idx = list.size() - 1; idx >= 0; idx--) {
1352 int d = 0;
1353 ObjectToPack b = list.get(idx).getDeltaBase();
1354 while (b != null) {
1355 if (d < b.getChainLength())
1356 break;
1357 b.setChainLength(++d);
1358 if (d >= max && b.isDeltaRepresentation()) {
1359 reselectNonDelta(b);
1360 break;
1361 }
1362 b = b.getDeltaBase();
1363 }
1364 }
1365 if (config.isDeltaCompress()) {
1366 for (ObjectToPack otp : list)
1367 otp.clearChainLength();
1368 }
1369 }
1370
1371 private void searchForDeltas(ProgressMonitor monitor)
1372 throws MissingObjectException, IncorrectObjectTypeException,
1373 IOException {
1374
1375
1376
1377
1378 ObjectToPack[] list = new ObjectToPack[
1379 objectsLists[OBJ_TREE].size()
1380 + objectsLists[OBJ_BLOB].size()
1381 + edgeObjects.size()];
1382 int cnt = 0;
1383 cnt = findObjectsNeedingDelta(list, cnt, OBJ_TREE);
1384 cnt = findObjectsNeedingDelta(list, cnt, OBJ_BLOB);
1385 if (cnt == 0)
1386 return;
1387 int nonEdgeCnt = cnt;
1388
1389
1390
1391
1392
1393 for (ObjectToPack eo : edgeObjects) {
1394 eo.setWeight(0);
1395 list[cnt++] = eo;
1396 }
1397
1398
1399
1400
1401
1402
1403
1404
1405 final long sizingStart = System.currentTimeMillis();
1406 beginPhase(PackingPhase.GETTING_SIZES, monitor, cnt);
1407 AsyncObjectSizeQueue<ObjectToPack> sizeQueue = reader.getObjectSize(
1408 Arrays.<ObjectToPack> asList(list).subList(0, cnt), false);
1409 try {
1410 final long limit = Math.min(
1411 config.getBigFileThreshold(),
1412 Integer.MAX_VALUE);
1413 for (;;) {
1414 try {
1415 if (!sizeQueue.next())
1416 break;
1417 } catch (MissingObjectException notFound) {
1418 monitor.update(1);
1419 if (ignoreMissingUninteresting) {
1420 ObjectToPack otp = sizeQueue.getCurrent();
1421 if (otp != null && otp.isEdge()) {
1422 otp.setDoNotDelta();
1423 continue;
1424 }
1425
1426 otp = objectsMap.get(notFound.getObjectId());
1427 if (otp != null && otp.isEdge()) {
1428 otp.setDoNotDelta();
1429 continue;
1430 }
1431 }
1432 throw notFound;
1433 }
1434
1435 ObjectToPack otp = sizeQueue.getCurrent();
1436 if (otp == null)
1437 otp = objectsMap.get(sizeQueue.getObjectId());
1438
1439 long sz = sizeQueue.getSize();
1440 if (DeltaIndex.BLKSZ < sz && sz < limit)
1441 otp.setWeight((int) sz);
1442 else
1443 otp.setDoNotDelta();
1444 monitor.update(1);
1445 }
1446 } finally {
1447 sizeQueue.release();
1448 }
1449 endPhase(monitor);
1450 stats.timeSearchingForSizes = System.currentTimeMillis() - sizingStart;
1451
1452
1453
1454
1455
1456
1457 Arrays.sort(list, 0, cnt, (ObjectToPack"../../../../../../org/eclipse/jgit/internal/storage/pack/ObjectToPack.html#ObjectToPack">ObjectToPack a, ObjectToPack b) -> {
1458 int cmp = (a.isDoNotDelta() ? 1 : 0) - (b.isDoNotDelta() ? 1 : 0);
1459 if (cmp != 0) {
1460 return cmp;
1461 }
1462
1463 cmp = a.getType() - b.getType();
1464 if (cmp != 0) {
1465 return cmp;
1466 }
1467
1468 cmp = (a.getPathHash() >>> 1) - (b.getPathHash() >>> 1);
1469 if (cmp != 0) {
1470 return cmp;
1471 }
1472
1473 cmp = (a.getPathHash() & 1) - (b.getPathHash() & 1);
1474 if (cmp != 0) {
1475 return cmp;
1476 }
1477
1478 cmp = (a.isEdge() ? 0 : 1) - (b.isEdge() ? 0 : 1);
1479 if (cmp != 0) {
1480 return cmp;
1481 }
1482
1483 return b.getWeight() - a.getWeight();
1484 });
1485
1486
1487
1488 while (0 < cnt && list[cnt - 1].isDoNotDelta()) {
1489 if (!list[cnt - 1].isEdge())
1490 nonEdgeCnt--;
1491 cnt--;
1492 }
1493 if (cnt == 0)
1494 return;
1495
1496 final long searchStart = System.currentTimeMillis();
1497 searchForDeltas(monitor, list, cnt);
1498 stats.deltaSearchNonEdgeObjects = nonEdgeCnt;
1499 stats.timeCompressing = System.currentTimeMillis() - searchStart;
1500
1501 for (int i = 0; i < cnt; i++)
1502 if (!list[i].isEdge() && list[i].isDeltaRepresentation())
1503 stats.deltasFound++;
1504 }
1505
1506 private int findObjectsNeedingDelta(ObjectToPack[] list, int cnt, int type) {
1507 for (ObjectToPack otp : objectsLists[type]) {
1508 if (otp.isDoNotDelta())
1509 continue;
1510 if (otp.isDeltaRepresentation())
1511 continue;
1512 otp.setWeight(0);
1513 list[cnt++] = otp;
1514 }
1515 return cnt;
1516 }
1517
1518 private void reselectNonDelta(ObjectToPack otp) throws IOException {
1519 otp.clearDeltaBase();
1520 otp.clearReuseAsIs();
1521 boolean old = reuseDeltas;
1522 reuseDeltas = false;
1523 reuseSupport.selectObjectRepresentation(this,
1524 NullProgressMonitor.INSTANCE,
1525 Collections.singleton(otp));
1526 reuseDeltas = old;
1527 }
1528
1529 private void searchForDeltas(final ProgressMonitor monitor,
1530 final ObjectToPack[] list, final int cnt)
1531 throws MissingObjectException, IncorrectObjectTypeException,
1532 LargeObjectException, IOException {
1533 int threads = config.getThreads();
1534 if (threads == 0)
1535 threads = Runtime.getRuntime().availableProcessors();
1536 if (threads <= 1 || cnt <= config.getDeltaSearchWindowSize())
1537 singleThreadDeltaSearch(monitor, list, cnt);
1538 else
1539 parallelDeltaSearch(monitor, list, cnt, threads);
1540 }
1541
1542 private void singleThreadDeltaSearch(ProgressMonitor monitor,
1543 ObjectToPack[] list, int cnt) throws IOException {
1544 long totalWeight = 0;
1545 for (int i = 0; i < cnt; i++) {
1546 ObjectToPack o = list[i];
1547 totalWeight += DeltaTask.getAdjustedWeight(o);
1548 }
1549
1550 long bytesPerUnit = 1;
1551 while (DeltaTask.MAX_METER <= (totalWeight / bytesPerUnit))
1552 bytesPerUnit <<= 10;
1553 int cost = (int) (totalWeight / bytesPerUnit);
1554 if (totalWeight % bytesPerUnit != 0)
1555 cost++;
1556
1557 beginPhase(PackingPhase.COMPRESSING, monitor, cost);
1558 new DeltaWindow(config, new DeltaCache(config), reader,
1559 monitor, bytesPerUnit,
1560 list, 0, cnt).search();
1561 endPhase(monitor);
1562 }
1563
1564 @SuppressWarnings("Finally")
1565 private void parallelDeltaSearch(ProgressMonitor monitor,
1566 ObjectToPack[] list, int cnt, int threads) throws IOException {
1567 DeltaCache dc = new ThreadSafeDeltaCache(config);
1568 ThreadSafeProgressMonitor pm = new ThreadSafeProgressMonitor(monitor);
1569 DeltaTask.Block taskBlock = new DeltaTask.Block(threads, config,
1570 reader, dc, pm,
1571 list, 0, cnt);
1572 taskBlock.partitionTasks();
1573 beginPhase(PackingPhase.COMPRESSING, monitor, taskBlock.cost());
1574 pm.startWorkers(taskBlock.tasks.size());
1575
1576 Executor executor = config.getExecutor();
1577 final List<Throwable> errors =
1578 Collections.synchronizedList(new ArrayList<>(threads));
1579 if (executor instanceof ExecutorService) {
1580
1581 runTasks((ExecutorService) executor, pm, taskBlock, errors);
1582 } else if (executor == null) {
1583
1584
1585 ExecutorService pool = Executors.newFixedThreadPool(threads);
1586 Throwable e1 = null;
1587 try {
1588 runTasks(pool, pm, taskBlock, errors);
1589 } catch (Exception e) {
1590 e1 = e;
1591 } finally {
1592 pool.shutdown();
1593 for (;;) {
1594 try {
1595 if (pool.awaitTermination(60, TimeUnit.SECONDS)) {
1596 break;
1597 }
1598 } catch (InterruptedException e) {
1599 if (e1 != null) {
1600 e.addSuppressed(e1);
1601 }
1602 throw new IOException(JGitText
1603 .get().packingCancelledDuringObjectsWriting, e);
1604 }
1605 }
1606 }
1607 } else {
1608
1609
1610
1611 for (DeltaTask task : taskBlock.tasks) {
1612 executor.execute(() -> {
1613 try {
1614 task.call();
1615 } catch (Throwable failure) {
1616 errors.add(failure);
1617 }
1618 });
1619 }
1620 try {
1621 pm.waitForCompletion();
1622 } catch (InterruptedException ie) {
1623
1624
1625
1626 throw new IOException(
1627 JGitText.get().packingCancelledDuringObjectsWriting,
1628 ie);
1629 }
1630 }
1631
1632
1633
1634
1635 if (!errors.isEmpty()) {
1636 Throwable err = errors.get(0);
1637 if (err instanceof Error)
1638 throw (Error) err;
1639 if (err instanceof RuntimeException)
1640 throw (RuntimeException) err;
1641 if (err instanceof IOException)
1642 throw (IOException) err;
1643
1644 throw new IOException(err.getMessage(), err);
1645 }
1646 endPhase(monitor);
1647 }
1648
1649 private static void runTasks(ExecutorService pool,
1650 ThreadSafeProgressMonitor pm,
1651 DeltaTask.Block tb, List<Throwable> errors) throws IOException {
1652 List<Future<?>> futures = new ArrayList<>(tb.tasks.size());
1653 for (DeltaTask task : tb.tasks)
1654 futures.add(pool.submit(task));
1655
1656 try {
1657 pm.waitForCompletion();
1658 for (Future<?> f : futures) {
1659 try {
1660 f.get();
1661 } catch (ExecutionException failed) {
1662 errors.add(failed.getCause());
1663 }
1664 }
1665 } catch (InterruptedException ie) {
1666 for (Future<?> f : futures)
1667 f.cancel(true);
1668 throw new IOException(
1669 JGitText.get().packingCancelledDuringObjectsWriting, ie);
1670 }
1671 }
1672
1673 private void writeObjects(PackOutputStream out) throws IOException {
1674 writeObjects(out, objectsLists[OBJ_COMMIT]);
1675 writeObjects(out, objectsLists[OBJ_TAG]);
1676 writeObjects(out, objectsLists[OBJ_TREE]);
1677 writeObjects(out, objectsLists[OBJ_BLOB]);
1678 }
1679
1680 private void writeObjects(PackOutputStream out, List<ObjectToPack> list)
1681 throws IOException {
1682 if (list.isEmpty())
1683 return;
1684
1685 typeStats = stats.objectTypes[list.get(0).getType()];
1686 long beginOffset = out.length();
1687
1688 if (reuseSupport != null) {
1689 reuseSupport.writeObjects(out, list);
1690 } else {
1691 for (ObjectToPack otp : list)
1692 out.writeObject(otp);
1693 }
1694
1695 typeStats.bytes += out.length() - beginOffset;
1696 typeStats.cntObjects = list.size();
1697 }
1698
1699 void writeObject(PackOutputStream out, ObjectToPack otp) throws IOException {
1700 if (!otp.isWritten())
1701 writeObjectImpl(out, otp);
1702 }
1703
1704 private void writeObjectImpl(PackOutputStream out, ObjectToPack otp)
1705 throws IOException {
1706 if (otp.wantWrite()) {
1707
1708
1709
1710
1711
1712 reselectNonDelta(otp);
1713 }
1714 otp.markWantWrite();
1715
1716 while (otp.isReuseAsIs()) {
1717 writeBase(out, otp.getDeltaBase());
1718 if (otp.isWritten())
1719 return;
1720
1721 crc32.reset();
1722 otp.setOffset(out.length());
1723 try {
1724 reuseSupport.copyObjectAsIs(out, otp, reuseValidate);
1725 out.endObject();
1726 otp.setCRC((int) crc32.getValue());
1727 typeStats.reusedObjects++;
1728 if (otp.isDeltaRepresentation()) {
1729 typeStats.reusedDeltas++;
1730 typeStats.deltaBytes += out.length() - otp.getOffset();
1731 }
1732 return;
1733 } catch (StoredObjectRepresentationNotAvailableException gone) {
1734 if (otp.getOffset() == out.length()) {
1735 otp.setOffset(0);
1736 otp.clearDeltaBase();
1737 otp.clearReuseAsIs();
1738 reuseSupport.selectObjectRepresentation(this,
1739 NullProgressMonitor.INSTANCE,
1740 Collections.singleton(otp));
1741 continue;
1742 }
1743
1744
1745 CorruptObjectException coe;
1746 coe = new CorruptObjectException(otp, "");
1747 coe.initCause(gone);
1748 throw coe;
1749 }
1750 }
1751
1752
1753
1754 if (otp.isDeltaRepresentation()) {
1755 writeDeltaObjectDeflate(out, otp);
1756 } else {
1757 writeWholeObjectDeflate(out, otp);
1758 }
1759 out.endObject();
1760 otp.setCRC((int) crc32.getValue());
1761 }
1762
1763 private void writeBase(PackOutputStream out, ObjectToPack base)
1764 throws IOException {
1765 if (base != null && !base.isWritten() && !base.isEdge())
1766 writeObjectImpl(out, base);
1767 }
1768
1769 private void writeWholeObjectDeflate(PackOutputStream out,
1770 final ObjectToPack otp) throws IOException {
1771 final Deflater deflater = deflater();
1772 final ObjectLoader ldr = reader.open(otp, otp.getType());
1773
1774 crc32.reset();
1775 otp.setOffset(out.length());
1776 out.writeHeader(otp, ldr.getSize());
1777
1778 deflater.reset();
1779 DeflaterOutputStream dst = new DeflaterOutputStream(out, deflater);
1780 ldr.copyTo(dst);
1781 dst.finish();
1782 }
1783
1784 private void writeDeltaObjectDeflate(PackOutputStream out,
1785 final ObjectToPack otp) throws IOException {
1786 writeBase(out, otp.getDeltaBase());
1787
1788 crc32.reset();
1789 otp.setOffset(out.length());
1790
1791 DeltaCache.Ref ref = otp.popCachedDelta();
1792 if (ref != null) {
1793 byte[] zbuf = ref.get();
1794 if (zbuf != null) {
1795 out.writeHeader(otp, otp.getCachedSize());
1796 out.write(zbuf);
1797 typeStats.cntDeltas++;
1798 typeStats.deltaBytes += out.length() - otp.getOffset();
1799 return;
1800 }
1801 }
1802
1803 try (TemporaryBuffer.Heap delta = delta(otp)) {
1804 out.writeHeader(otp, delta.length());
1805
1806 Deflater deflater = deflater();
1807 deflater.reset();
1808 DeflaterOutputStream dst = new DeflaterOutputStream(out, deflater);
1809 delta.writeTo(dst, null);
1810 dst.finish();
1811 }
1812 typeStats.cntDeltas++;
1813 typeStats.deltaBytes += out.length() - otp.getOffset();
1814 }
1815
1816 private TemporaryBuffer.Heap delta(ObjectToPack otp)
1817 throws IOException {
1818 DeltaIndex index = new DeltaIndex(buffer(otp.getDeltaBaseId()));
1819 byte[] res = buffer(otp);
1820
1821
1822
1823
1824
1825 TemporaryBuffer.Heap delta = new TemporaryBuffer.Heap(res.length);
1826 index.encode(delta, res);
1827 return delta;
1828 }
1829
1830 private byte[] buffer(AnyObjectId objId) throws IOException {
1831 return buffer(config, reader, objId);
1832 }
1833
1834 static byte[] buffer(PackConfig config, ObjectReader or, AnyObjectId objId)
1835 throws IOException {
1836
1837
1838
1839
1840
1841 return or.open(objId).getCachedBytes(config.getBigFileThreshold());
1842 }
1843
1844 private Deflater deflater() {
1845 if (myDeflater == null)
1846 myDeflater = new Deflater(config.getCompressionLevel());
1847 return myDeflater;
1848 }
1849
1850 private void writeChecksum(PackOutputStream out) throws IOException {
1851 packcsum = out.getDigest();
1852 out.write(packcsum);
1853 }
1854
1855 private void findObjectsToPack(@NonNull ProgressMonitor countingMonitor,
1856 @NonNull ObjectWalk walker, @NonNull Set<? extends ObjectId> want,
1857 @NonNull Set<? extends ObjectId> have,
1858 @NonNull Set<? extends ObjectId> noBitmaps) throws IOException {
1859 final long countingStart = System.currentTimeMillis();
1860 beginPhase(PackingPhase.COUNTING, countingMonitor, ProgressMonitor.UNKNOWN);
1861
1862 stats.interestingObjects = Collections.unmodifiableSet(new HashSet<ObjectId>(want));
1863 stats.uninterestingObjects = Collections.unmodifiableSet(new HashSet<ObjectId>(have));
1864 excludeFromBitmapSelection = noBitmaps;
1865
1866 canBuildBitmaps = config.isBuildBitmaps()
1867 && !shallowPack
1868 && have.isEmpty()
1869 && (excludeInPacks == null || excludeInPacks.length == 0);
1870 if (!shallowPack && useBitmaps) {
1871 BitmapIndex bitmapIndex = reader.getBitmapIndex();
1872 if (bitmapIndex != null) {
1873 BitmapWalker bitmapWalker = new BitmapWalker(
1874 walker, bitmapIndex, countingMonitor);
1875 findObjectsToPackUsingBitmaps(bitmapWalker, want, have);
1876 endPhase(countingMonitor);
1877 stats.timeCounting = System.currentTimeMillis() - countingStart;
1878 stats.bitmapIndexMisses = bitmapWalker.getCountOfBitmapIndexMisses();
1879 return;
1880 }
1881 }
1882
1883 List<ObjectId> all = new ArrayList<>(want.size() + have.size());
1884 all.addAll(want);
1885 all.addAll(have);
1886
1887 final RevFlag include = walker.newFlag("include");
1888 final RevFlag added = walker.newFlag("added");
1889
1890 walker.carry(include);
1891
1892 int haveEst = have.size();
1893 if (have.isEmpty()) {
1894 walker.sort(RevSort.COMMIT_TIME_DESC);
1895 } else {
1896 walker.sort(RevSort.TOPO);
1897 if (thin)
1898 walker.sort(RevSort.BOUNDARY, true);
1899 }
1900
1901 List<RevObject> wantObjs = new ArrayList<>(want.size());
1902 List<RevObject> haveObjs = new ArrayList<>(haveEst);
1903 List<RevTag> wantTags = new ArrayList<>(want.size());
1904
1905
1906
1907 AsyncRevObjectQueue q = walker.parseAny(all, true);
1908 try {
1909 for (;;) {
1910 try {
1911 RevObject o = q.next();
1912 if (o == null)
1913 break;
1914 if (have.contains(o))
1915 haveObjs.add(o);
1916 if (want.contains(o)) {
1917 o.add(include);
1918 wantObjs.add(o);
1919 if (o instanceof RevTag)
1920 wantTags.add((RevTag) o);
1921 }
1922 } catch (MissingObjectException e) {
1923 if (ignoreMissingUninteresting
1924 && have.contains(e.getObjectId()))
1925 continue;
1926 throw e;
1927 }
1928 }
1929 } finally {
1930 q.release();
1931 }
1932
1933 if (!wantTags.isEmpty()) {
1934 all = new ArrayList<>(wantTags.size());
1935 for (RevTag tag : wantTags)
1936 all.add(tag.getObject());
1937 q = walker.parseAny(all, true);
1938 try {
1939 while (q.next() != null) {
1940
1941 }
1942 } finally {
1943 q.release();
1944 }
1945 }
1946
1947 if (walker instanceof DepthWalk.ObjectWalk) {
1948 DepthWalk.ObjectWalk depthWalk = (DepthWalk.ObjectWalk) walker;
1949 for (RevObject obj : wantObjs) {
1950 depthWalk.markRoot(obj);
1951 }
1952
1953
1954
1955
1956
1957 for (RevObject obj : haveObjs) {
1958 if (obj instanceof RevCommit) {
1959 RevTree t = ((RevCommit) obj).getTree();
1960 depthWalk.markUninteresting(t);
1961 }
1962 }
1963
1964 if (unshallowObjects != null) {
1965 for (ObjectId id : unshallowObjects) {
1966 depthWalk.markUnshallow(walker.parseAny(id));
1967 }
1968 }
1969 } else {
1970 for (RevObject obj : wantObjs)
1971 walker.markStart(obj);
1972 }
1973 for (RevObject obj : haveObjs)
1974 walker.markUninteresting(obj);
1975
1976 final int maxBases = config.getDeltaSearchWindowSize();
1977 Set<RevTree> baseTrees = new HashSet<>();
1978 BlockList<RevCommit> commits = new BlockList<>();
1979 Set<ObjectId> roots = new HashSet<>();
1980 RevCommit c;
1981 while ((c = walker.next()) != null) {
1982 if (exclude(c))
1983 continue;
1984 if (c.has(RevFlag.UNINTERESTING)) {
1985 if (baseTrees.size() <= maxBases)
1986 baseTrees.add(c.getTree());
1987 continue;
1988 }
1989
1990 commits.add(c);
1991 if (c.getParentCount() == 0) {
1992 roots.add(c.copy());
1993 }
1994 countingMonitor.update(1);
1995 }
1996 stats.rootCommits = Collections.unmodifiableSet(roots);
1997
1998 if (shallowPack) {
1999 for (RevCommit cmit : commits) {
2000 addObject(cmit, 0);
2001 }
2002 } else {
2003 int commitCnt = 0;
2004 boolean putTagTargets = false;
2005 for (RevCommit cmit : commits) {
2006 if (!cmit.has(added)) {
2007 cmit.add(added);
2008 addObject(cmit, 0);
2009 commitCnt++;
2010 }
2011
2012 for (int i = 0; i < cmit.getParentCount(); i++) {
2013 RevCommit p = cmit.getParent(i);
2014 if (!p.has(added) && !p.has(RevFlag.UNINTERESTING)
2015 && !exclude(p)) {
2016 p.add(added);
2017 addObject(p, 0);
2018 commitCnt++;
2019 }
2020 }
2021
2022 if (!putTagTargets && 4096 < commitCnt) {
2023 for (ObjectId id : tagTargets) {
2024 RevObject obj = walker.lookupOrNull(id);
2025 if (obj instanceof RevCommit
2026 && obj.has(include)
2027 && !obj.has(RevFlag.UNINTERESTING)
2028 && !obj.has(added)) {
2029 obj.add(added);
2030 addObject(obj, 0);
2031 }
2032 }
2033 putTagTargets = true;
2034 }
2035 }
2036 }
2037 commits = null;
2038
2039 if (thin && !baseTrees.isEmpty()) {
2040 BaseSearch bases = new BaseSearch(countingMonitor, baseTrees,
2041 objectsMap, edgeObjects, reader);
2042 RevObject o;
2043 while ((o = walker.nextObject()) != null) {
2044 if (o.has(RevFlag.UNINTERESTING))
2045 continue;
2046 if (exclude(o))
2047 continue;
2048
2049 int pathHash = walker.getPathHashCode();
2050 byte[] pathBuf = walker.getPathBuffer();
2051 int pathLen = walker.getPathLength();
2052 bases.addBase(o.getType(), pathBuf, pathLen, pathHash);
2053 if (!depthSkip(o, walker)) {
2054 filterAndAddObject(o, o.getType(), pathHash, want);
2055 }
2056 countingMonitor.update(1);
2057 }
2058 } else {
2059 RevObject o;
2060 while ((o = walker.nextObject()) != null) {
2061 if (o.has(RevFlag.UNINTERESTING))
2062 continue;
2063 if (exclude(o))
2064 continue;
2065 if (!depthSkip(o, walker)) {
2066 filterAndAddObject(o, o.getType(), walker.getPathHashCode(),
2067 want);
2068 }
2069 countingMonitor.update(1);
2070 }
2071 }
2072
2073 for (CachedPack pack : cachedPacks)
2074 countingMonitor.update((int) pack.getObjectCount());
2075 endPhase(countingMonitor);
2076 stats.timeCounting = System.currentTimeMillis() - countingStart;
2077 stats.bitmapIndexMisses = -1;
2078 }
2079
2080 private void findObjectsToPackUsingBitmaps(
2081 BitmapWalker bitmapWalker, Set<? extends ObjectId> want,
2082 Set<? extends ObjectId> have)
2083 throws MissingObjectException, IncorrectObjectTypeException,
2084 IOException {
2085 BitmapBuilder haveBitmap = bitmapWalker.findObjects(have, null, true);
2086 BitmapBuilder wantBitmap = bitmapWalker.findObjects(want, haveBitmap,
2087 false);
2088 BitmapBuilder needBitmap = wantBitmap.andNot(haveBitmap);
2089
2090 if (useCachedPacks && reuseSupport != null && !reuseValidate
2091 && (excludeInPacks == null || excludeInPacks.length == 0))
2092 cachedPacks.addAll(
2093 reuseSupport.getCachedPacksAndUpdate(needBitmap));
2094
2095 for (BitmapObject obj : needBitmap) {
2096 ObjectId objectId = obj.getObjectId();
2097 if (exclude(objectId)) {
2098 needBitmap.remove(objectId);
2099 continue;
2100 }
2101 filterAndAddObject(objectId, obj.getType(), 0, want);
2102 }
2103
2104 if (thin)
2105 haveObjects = haveBitmap;
2106 }
2107
2108 private static void pruneEdgesFromObjectList(List<ObjectToPack> list) {
2109 final int size = list.size();
2110 int src = 0;
2111 int dst = 0;
2112
2113 for (; src < size; src++) {
2114 ObjectToPack obj = list.get(src);
2115 if (obj.isEdge())
2116 continue;
2117 if (dst != src)
2118 list.set(dst, obj);
2119 dst++;
2120 }
2121
2122 while (dst < list.size())
2123 list.remove(list.size() - 1);
2124 }
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138 public void addObject(RevObject object)
2139 throws IncorrectObjectTypeException {
2140 if (!exclude(object))
2141 addObject(object, 0);
2142 }
2143
2144 private void addObject(RevObject object, int pathHashCode) {
2145 addObject(object, object.getType(), pathHashCode);
2146 }
2147
2148 private void addObject(
2149 final AnyObjectId src, final int type, final int pathHashCode) {
2150 final ObjectToPack otp;
2151 if (reuseSupport != null)
2152 otp = reuseSupport.newObjectToPack(src, type);
2153 else
2154 otp = new ObjectToPack(src, type);
2155 otp.setPathHash(pathHashCode);
2156 objectsLists[type].add(otp);
2157 objectsMap.add(otp);
2158 }
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176 private boolean depthSkip(@NonNull RevObject obj, ObjectWalk walker) {
2177 long treeDepth = walker.getTreeDepth();
2178
2179
2180
2181
2182
2183 if (obj.getType() == OBJ_BLOB) {
2184 treeDepth++;
2185 } else {
2186 stats.treesTraversed++;
2187 }
2188
2189 if (filterSpec.getTreeDepthLimit() < 0 ||
2190 treeDepth <= filterSpec.getTreeDepthLimit()) {
2191 return false;
2192 }
2193
2194 walker.skipTree();
2195 return true;
2196 }
2197
2198
2199
2200 private void filterAndAddObject(@NonNull AnyObjectId src, int type,
2201 int pathHashCode, @NonNull Set<? extends AnyObjectId> want)
2202 throws IOException {
2203
2204
2205
2206 boolean reject =
2207 (!filterSpec.allowsType(type) && !want.contains(src)) ||
2208 (filterSpec.getBlobLimit() >= 0 &&
2209 type == OBJ_BLOB &&
2210 !want.contains(src) &&
2211 reader.getObjectSize(src, OBJ_BLOB) > filterSpec.getBlobLimit());
2212 if (!reject) {
2213 addObject(src, type, pathHashCode);
2214 }
2215 }
2216
2217 private boolean exclude(AnyObjectId objectId) {
2218 if (excludeInPacks == null)
2219 return false;
2220 if (excludeInPackLast.contains(objectId))
2221 return true;
2222 for (ObjectIdSet idx : excludeInPacks) {
2223 if (idx.contains(objectId)) {
2224 excludeInPackLast = idx;
2225 return true;
2226 }
2227 }
2228 return false;
2229 }
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243 public void select(ObjectToPack otp, StoredObjectRepresentation next) {
2244 int nFmt = next.getFormat();
2245
2246 if (!cachedPacks.isEmpty()) {
2247 if (otp.isEdge())
2248 return;
2249 if (nFmt == PACK_WHOLE || nFmt == PACK_DELTA) {
2250 for (CachedPack pack : cachedPacks) {
2251 if (pack.hasObject(otp, next)) {
2252 otp.setEdge();
2253 otp.clearDeltaBase();
2254 otp.clearReuseAsIs();
2255 pruneCurrentObjectList = true;
2256 return;
2257 }
2258 }
2259 }
2260 }
2261
2262 if (nFmt == PACK_DELTA && reuseDeltas && reuseDeltaFor(otp)) {
2263 ObjectId baseId = next.getDeltaBase();
2264 ObjectToPack ptr = objectsMap.get(baseId);
2265 if (ptr != null && !ptr.isEdge()) {
2266 otp.setDeltaBase(ptr);
2267 otp.setReuseAsIs();
2268 } else if (thin && have(ptr, baseId)) {
2269 otp.setDeltaBase(baseId);
2270 otp.setReuseAsIs();
2271 } else {
2272 otp.clearDeltaBase();
2273 otp.clearReuseAsIs();
2274 }
2275 } else if (nFmt == PACK_WHOLE && config.isReuseObjects()) {
2276 int nWeight = next.getWeight();
2277 if (otp.isReuseAsIs() && !otp.isDeltaRepresentation()) {
2278
2279
2280
2281 if (otp.getWeight() <= nWeight)
2282 return;
2283 }
2284 otp.clearDeltaBase();
2285 otp.setReuseAsIs();
2286 otp.setWeight(nWeight);
2287 } else {
2288 otp.clearDeltaBase();
2289 otp.clearReuseAsIs();
2290 }
2291
2292 otp.setDeltaAttempted(reuseDeltas && next.wasDeltaAttempted());
2293 otp.select(next);
2294 }
2295
2296 private final boolean have(ObjectToPack ptr, AnyObjectId objectId) {
2297 return (ptr != null && ptr.isEdge())
2298 || (haveObjects != null && haveObjects.contains(objectId));
2299 }
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320 public boolean prepareBitmapIndex(ProgressMonitor pm) throws IOException {
2321 if (!canBuildBitmaps || getObjectCount() > Integer.MAX_VALUE
2322 || !cachedPacks.isEmpty())
2323 return false;
2324
2325 if (pm == null)
2326 pm = NullProgressMonitor.INSTANCE;
2327
2328 int numCommits = objectsLists[OBJ_COMMIT].size();
2329 List<ObjectToPack> byName = sortByName();
2330 sortedByName = null;
2331 objectsLists = null;
2332 objectsMap = null;
2333 writeBitmaps = new PackBitmapIndexBuilder(byName);
2334 byName = null;
2335
2336 PackWriterBitmapPreparer bitmapPreparer = new PackWriterBitmapPreparer(
2337 reader, writeBitmaps, pm, stats.interestingObjects, config);
2338
2339 Collection<BitmapCommit> selectedCommits = bitmapPreparer
2340 .selectCommits(numCommits, excludeFromBitmapSelection);
2341
2342 beginPhase(PackingPhase.BUILDING_BITMAPS, pm, selectedCommits.size());
2343
2344 BitmapWalker walker = bitmapPreparer.newBitmapWalker();
2345 AnyObjectId last = null;
2346 for (BitmapCommit cmit : selectedCommits) {
2347 if (!cmit.isReuseWalker()) {
2348 walker = bitmapPreparer.newBitmapWalker();
2349 }
2350 BitmapBuilder bitmap = walker.findObjects(
2351 Collections.singleton(cmit), null, false);
2352
2353 if (last != null && cmit.isReuseWalker() && !bitmap.contains(last))
2354 throw new IllegalStateException(MessageFormat.format(
2355 JGitText.get().bitmapMissingObject, cmit.name(),
2356 last.name()));
2357 last = BitmapCommit.copyFrom(cmit).build();
2358 writeBitmaps.processBitmapForWrite(cmit, bitmap.build(),
2359 cmit.getFlags());
2360
2361
2362
2363 walker.setPrevCommit(last);
2364 walker.setPrevBitmap(bitmap);
2365
2366 pm.update(1);
2367 }
2368
2369 endPhase(pm);
2370 return true;
2371 }
2372
2373 private boolean reuseDeltaFor(ObjectToPack otp) {
2374 int type = otp.getType();
2375 if ((type & 2) != 0)
2376 return true;
2377 if (type == OBJ_COMMIT)
2378 return reuseDeltaCommits;
2379 if (type == OBJ_TAG)
2380 return false;
2381 return true;
2382 }
2383
2384 private class MutableState {
2385
2386
2387 private static final long OBJECT_TO_PACK_SIZE =
2388 (2 * 8)
2389 + (2 * 8) + (2 * 8)
2390 + (8 + 8)
2391 + 8
2392 + 40
2393 + 8;
2394
2395 private final long totalDeltaSearchBytes;
2396
2397 private volatile PackingPhase phase;
2398
2399 MutableState() {
2400 phase = PackingPhase.COUNTING;
2401 if (config.isDeltaCompress()) {
2402 int threads = config.getThreads();
2403 if (threads <= 0)
2404 threads = Runtime.getRuntime().availableProcessors();
2405 totalDeltaSearchBytes = (threads * config.getDeltaSearchMemoryLimit())
2406 + config.getBigFileThreshold();
2407 } else
2408 totalDeltaSearchBytes = 0;
2409 }
2410
2411 State snapshot() {
2412 long objCnt = 0;
2413 BlockList<ObjectToPack>[] lists = objectsLists;
2414 if (lists != null) {
2415 objCnt += lists[OBJ_COMMIT].size();
2416 objCnt += lists[OBJ_TREE].size();
2417 objCnt += lists[OBJ_BLOB].size();
2418 objCnt += lists[OBJ_TAG].size();
2419
2420 }
2421
2422 long bytesUsed = OBJECT_TO_PACK_SIZE * objCnt;
2423 PackingPhase curr = phase;
2424 if (curr == PackingPhase.COMPRESSING)
2425 bytesUsed += totalDeltaSearchBytes;
2426 return new State(curr, bytesUsed);
2427 }
2428 }
2429
2430
2431 public enum PackingPhase {
2432
2433 COUNTING,
2434
2435
2436 GETTING_SIZES,
2437
2438
2439 FINDING_SOURCES,
2440
2441
2442 COMPRESSING,
2443
2444
2445 WRITING,
2446
2447
2448 BUILDING_BITMAPS;
2449 }
2450
2451
2452 public class State {
2453 private final PackingPhase phase;
2454
2455 private final long bytesUsed;
2456
2457 State(PackingPhase phase, long bytesUsed) {
2458 this.phase = phase;
2459 this.bytesUsed = bytesUsed;
2460 }
2461
2462
2463 public PackConfig getConfig() {
2464 return config;
2465 }
2466
2467
2468 public PackingPhase getPhase() {
2469 return phase;
2470 }
2471
2472
2473 public long estimateBytesUsed() {
2474 return bytesUsed;
2475 }
2476
2477 @SuppressWarnings("nls")
2478 @Override
2479 public String toString() {
2480 return "PackWriter.State[" + phase + ", memory=" + bytesUsed + "]";
2481 }
2482 }
2483
2484
2485
2486
2487
2488
2489 public static class PackfileUriConfig {
2490 @NonNull
2491 private final PacketLineOut pckOut;
2492
2493 @NonNull
2494 private final Collection<String> protocolsSupported;
2495
2496 @NonNull
2497 private final CachedPackUriProvider cachedPackUriProvider;
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508 public PackfileUriConfig(@NonNull PacketLineOut pckOut,
2509 @NonNull Collection<String> protocolsSupported,
2510 @NonNull CachedPackUriProvider cachedPackUriProvider) {
2511 this.pckOut = pckOut;
2512 this.protocolsSupported = protocolsSupported;
2513 this.cachedPackUriProvider = cachedPackUriProvider;
2514 }
2515 }
2516 }