1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 package org.eclipse.jgit.transport;
45
46 import static org.eclipse.jgit.lib.Constants.R_TAGS;
47 import static org.eclipse.jgit.lib.RefDatabase.ALL;
48 import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_FETCH;
49 import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_LS_REFS;
50 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
51 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_REACHABLE_SHA1_IN_WANT;
52 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_TIP_SHA1_IN_WANT;
53 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_DEEPEN_RELATIVE;
54 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER;
55 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG;
56 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_MULTI_ACK;
57 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_MULTI_ACK_DETAILED;
58 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_DONE;
59 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_PROGRESS;
60 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_OFS_DELTA;
61 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SHALLOW;
62 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND;
63 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K;
64 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK;
65
66 import java.io.ByteArrayOutputStream;
67 import java.io.EOFException;
68 import java.io.IOException;
69 import java.io.InputStream;
70 import java.io.OutputStream;
71 import java.text.MessageFormat;
72 import java.util.ArrayList;
73 import java.util.Collection;
74 import java.util.Collections;
75 import java.util.HashMap;
76 import java.util.HashSet;
77 import java.util.List;
78 import java.util.Map;
79 import java.util.Set;
80
81 import org.eclipse.jgit.annotations.Nullable;
82 import org.eclipse.jgit.errors.CorruptObjectException;
83 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
84 import org.eclipse.jgit.errors.MissingObjectException;
85 import org.eclipse.jgit.errors.PackProtocolException;
86 import org.eclipse.jgit.internal.JGitText;
87 import org.eclipse.jgit.internal.storage.pack.PackWriter;
88 import org.eclipse.jgit.lib.BitmapIndex;
89 import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
90 import org.eclipse.jgit.lib.Constants;
91 import org.eclipse.jgit.lib.NullProgressMonitor;
92 import org.eclipse.jgit.lib.ObjectId;
93 import org.eclipse.jgit.lib.ObjectReader;
94 import org.eclipse.jgit.lib.ProgressMonitor;
95 import org.eclipse.jgit.lib.Ref;
96 import org.eclipse.jgit.lib.Repository;
97 import org.eclipse.jgit.revwalk.AsyncRevObjectQueue;
98 import org.eclipse.jgit.revwalk.BitmapWalker;
99 import org.eclipse.jgit.revwalk.DepthWalk;
100 import org.eclipse.jgit.revwalk.ObjectWalk;
101 import org.eclipse.jgit.revwalk.RevCommit;
102 import org.eclipse.jgit.revwalk.RevFlag;
103 import org.eclipse.jgit.revwalk.RevFlagSet;
104 import org.eclipse.jgit.revwalk.RevObject;
105 import org.eclipse.jgit.revwalk.RevTag;
106 import org.eclipse.jgit.revwalk.RevWalk;
107 import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter;
108 import org.eclipse.jgit.storage.pack.PackConfig;
109 import org.eclipse.jgit.storage.pack.PackStatistics;
110 import org.eclipse.jgit.transport.GitProtocolConstants.MultiAck;
111 import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
112 import org.eclipse.jgit.transport.TransferConfig.ProtocolVersion;
113 import org.eclipse.jgit.util.io.InterruptTimer;
114 import org.eclipse.jgit.util.io.NullOutputStream;
115 import org.eclipse.jgit.util.io.TimeoutInputStream;
116 import org.eclipse.jgit.util.io.TimeoutOutputStream;
117
118
119
120
121 public class UploadPack {
122
123 public static enum RequestPolicy {
124
125 ADVERTISED,
126
127
128
129
130
131 REACHABLE_COMMIT,
132
133
134
135
136
137
138
139
140
141 TIP,
142
143
144
145
146
147
148
149 REACHABLE_COMMIT_TIP,
150
151
152 ANY;
153 }
154
155
156
157
158
159
160 public interface RequestValidator {
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175 void checkWants(UploadPack up, List<ObjectId> wants)
176 throws PackProtocolException, IOException;
177 }
178
179
180 public static class FirstLine {
181 private final String line;
182 private final Set<String> options;
183
184
185
186
187
188
189
190 public FirstLine(String line) {
191 if (line.length() > 45) {
192 final HashSet<String> opts = new HashSet<>();
193 String opt = line.substring(45);
194 if (opt.startsWith(" "))
195 opt = opt.substring(1);
196 for (String c : opt.split(" "))
197 opts.add(c);
198 this.line = line.substring(0, 45);
199 this.options = Collections.unmodifiableSet(opts);
200 } else {
201 this.line = line;
202 this.options = Collections.emptySet();
203 }
204 }
205
206
207 public String getLine() {
208 return line;
209 }
210
211
212 public Set<String> getOptions() {
213 return options;
214 }
215 }
216
217
218 private final Repository db;
219
220
221 private final RevWalk walk;
222
223
224 private PackConfig packConfig;
225
226
227 private TransferConfig transferConfig;
228
229
230 private int timeout;
231
232
233
234
235
236
237
238
239
240
241
242
243 private boolean biDirectionalPipe = true;
244
245
246 private InterruptTimer timer;
247
248
249
250
251
252 private boolean clientRequestedV2;
253
254 private InputStream rawIn;
255
256 private ResponseBufferedOutputStream rawOut;
257
258 private PacketLineIn pckIn;
259
260 private PacketLineOut pckOut;
261
262 private OutputStream msgOut = NullOutputStream.INSTANCE;
263
264
265 private Map<String, Ref> refs;
266
267
268 private AdvertiseRefsHook advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
269
270
271 private RefFilter refFilter = RefFilter.DEFAULT;
272
273
274 private PreUploadHook preUploadHook = PreUploadHook.NULL;
275
276
277 private PostUploadHook postUploadHook = PostUploadHook.NULL;
278
279
280 private Set<String> options;
281 String userAgent;
282
283
284 private final Set<ObjectId> wantIds = new HashSet<>();
285
286
287 private final Set<RevObject> wantAll = new HashSet<>();
288
289
290 private final Set<RevObject> commonBase = new HashSet<>();
291
292
293 private final Set<ObjectId> clientShallowCommits = new HashSet<>();
294
295
296 private int depth;
297
298
299
300
301
302 private int shallowSince;
303
304
305
306
307
308
309 private @Nullable List<String> shallowExcludeRefs;
310
311
312 private int oldestTime;
313
314
315 private Boolean okToGiveUp;
316
317 private boolean sentReady;
318
319
320 private Set<ObjectId> advertised;
321
322
323 private final RevFlag WANT;
324
325
326 private final RevFlag PEER_HAS;
327
328
329 private final RevFlag COMMON;
330
331
332 private final RevFlag SATISFIED;
333
334 private final RevFlagSet SAVE;
335
336 private RequestValidator requestValidator = new AdvertisedRequestValidator();
337
338 private MultiAck multiAck = MultiAck.OFF;
339
340 private boolean noDone;
341
342 private PackStatistics statistics;
343
344 private long filterBlobLimit = -1;
345
346
347
348
349
350
351
352 public UploadPack(Repository copyFrom) {
353 db = copyFrom;
354 walk = new RevWalk(db);
355 walk.setRetainBody(false);
356
357 WANT = walk.newFlag("WANT");
358 PEER_HAS = walk.newFlag("PEER_HAS");
359 COMMON = walk.newFlag("COMMON");
360 SATISFIED = walk.newFlag("SATISFIED");
361 walk.carry(PEER_HAS);
362
363 SAVE = new RevFlagSet();
364 SAVE.add(WANT);
365 SAVE.add(PEER_HAS);
366 SAVE.add(COMMON);
367 SAVE.add(SATISFIED);
368
369 setTransferConfig(null);
370 }
371
372
373
374
375
376
377 public final Repository getRepository() {
378 return db;
379 }
380
381
382
383
384
385
386 public final RevWalk getRevWalk() {
387 return walk;
388 }
389
390
391
392
393
394
395
396 public final Map<String, Ref> getAdvertisedRefs() {
397 return refs;
398 }
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413 public void setAdvertisedRefs(Map<String, Ref> allRefs) {
414 if (allRefs != null)
415 refs = allRefs;
416 else
417 refs = db.getAllRefs();
418 if (refFilter == RefFilter.DEFAULT)
419 refs = transferConfig.getRefFilter().filter(refs);
420 else
421 refs = refFilter.filter(refs);
422 }
423
424
425
426
427
428
429 public int getTimeout() {
430 return timeout;
431 }
432
433
434
435
436
437
438
439
440
441 public void setTimeout(int seconds) {
442 timeout = seconds;
443 }
444
445
446
447
448
449
450
451
452 public boolean isBiDirectionalPipe() {
453 return biDirectionalPipe;
454 }
455
456
457
458
459
460
461
462
463
464
465
466
467
468 public void setBiDirectionalPipe(boolean twoWay) {
469 biDirectionalPipe = twoWay;
470 }
471
472
473
474
475
476
477
478 public RequestPolicy getRequestPolicy() {
479 if (requestValidator instanceof AdvertisedRequestValidator)
480 return RequestPolicy.ADVERTISED;
481 if (requestValidator instanceof ReachableCommitRequestValidator)
482 return RequestPolicy.REACHABLE_COMMIT;
483 if (requestValidator instanceof TipRequestValidator)
484 return RequestPolicy.TIP;
485 if (requestValidator instanceof ReachableCommitTipRequestValidator)
486 return RequestPolicy.REACHABLE_COMMIT_TIP;
487 if (requestValidator instanceof AnyRequestValidator)
488 return RequestPolicy.ANY;
489 return null;
490 }
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509 public void setRequestPolicy(RequestPolicy policy) {
510 switch (policy) {
511 case ADVERTISED:
512 default:
513 requestValidator = new AdvertisedRequestValidator();
514 break;
515 case REACHABLE_COMMIT:
516 requestValidator = new ReachableCommitRequestValidator();
517 break;
518 case TIP:
519 requestValidator = new TipRequestValidator();
520 break;
521 case REACHABLE_COMMIT_TIP:
522 requestValidator = new ReachableCommitTipRequestValidator();
523 break;
524 case ANY:
525 requestValidator = new AnyRequestValidator();
526 break;
527 }
528 }
529
530
531
532
533
534
535
536
537 public void setRequestValidator(RequestValidator validator) {
538 requestValidator = validator != null ? validator
539 : new AdvertisedRequestValidator();
540 }
541
542
543
544
545
546
547 public AdvertiseRefsHook getAdvertiseRefsHook() {
548 return advertiseRefsHook;
549 }
550
551
552
553
554
555
556 public RefFilter getRefFilter() {
557 return refFilter;
558 }
559
560
561
562
563
564
565
566
567
568
569
570
571 public void setAdvertiseRefsHook(AdvertiseRefsHook advertiseRefsHook) {
572 if (advertiseRefsHook != null)
573 this.advertiseRefsHook = advertiseRefsHook;
574 else
575 this.advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
576 }
577
578
579
580
581
582
583
584
585
586
587
588
589
590 public void setRefFilter(RefFilter refFilter) {
591 this.refFilter = refFilter != null ? refFilter : RefFilter.DEFAULT;
592 }
593
594
595
596
597
598
599 public PreUploadHook getPreUploadHook() {
600 return preUploadHook;
601 }
602
603
604
605
606
607
608
609 public void setPreUploadHook(PreUploadHook hook) {
610 preUploadHook = hook != null ? hook : PreUploadHook.NULL;
611 }
612
613
614
615
616
617
618
619 public PostUploadHook getPostUploadHook() {
620 return postUploadHook;
621 }
622
623
624
625
626
627
628
629
630 public void setPostUploadHook(PostUploadHook hook) {
631 postUploadHook = hook != null ? hook : PostUploadHook.NULL;
632 }
633
634
635
636
637
638
639
640
641 public void setPackConfig(PackConfig pc) {
642 this.packConfig = pc;
643 }
644
645
646
647
648
649
650
651
652
653 public void setTransferConfig(TransferConfig tc) {
654 this.transferConfig = tc != null ? tc : new TransferConfig(db);
655 if (transferConfig.isAllowTipSha1InWant()) {
656 setRequestPolicy(transferConfig.isAllowReachableSha1InWant()
657 ? RequestPolicy.REACHABLE_COMMIT_TIP : RequestPolicy.TIP);
658 } else {
659 setRequestPolicy(transferConfig.isAllowReachableSha1InWant()
660 ? RequestPolicy.REACHABLE_COMMIT : RequestPolicy.ADVERTISED);
661 }
662 }
663
664
665
666
667
668
669
670
671
672
673
674
675 public boolean isSideBand() throws RequestNotYetReadException {
676 if (options == null)
677 throw new RequestNotYetReadException();
678 return (options.contains(OPTION_SIDE_BAND)
679 || options.contains(OPTION_SIDE_BAND_64K));
680 }
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695 public void setExtraParameters(Collection<String> params) {
696 this.clientRequestedV2 = params.contains("version=2");
697 }
698
699 private boolean useProtocolV2() {
700 return ProtocolVersion.V2.equals(transferConfig.protocolVersion)
701 && clientRequestedV2;
702 }
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725 public void upload(final InputStream input, OutputStream output,
726 final OutputStream messages) throws IOException {
727 try {
728 rawIn = input;
729 if (messages != null)
730 msgOut = messages;
731
732 if (timeout > 0) {
733 final Thread caller = Thread.currentThread();
734 timer = new InterruptTimer(caller.getName() + "-Timer");
735 TimeoutInputStream i = new TimeoutInputStream(rawIn, timer);
736 @SuppressWarnings("resource")
737 TimeoutOutputStream o = new TimeoutOutputStream(output, timer);
738 i.setTimeout(timeout * 1000);
739 o.setTimeout(timeout * 1000);
740 rawIn = i;
741 output = o;
742 }
743
744 rawOut = new ResponseBufferedOutputStream(output);
745 if (biDirectionalPipe) {
746 rawOut.stopBuffering();
747 }
748
749 pckIn = new PacketLineIn(rawIn);
750 pckOut = new PacketLineOut(rawOut);
751 if (useProtocolV2()) {
752 serviceV2();
753 } else {
754 service();
755 }
756 } finally {
757 msgOut = NullOutputStream.INSTANCE;
758 walk.close();
759 if (timer != null) {
760 try {
761 timer.terminate();
762 } finally {
763 timer = null;
764 }
765 }
766 }
767 }
768
769
770
771
772
773
774
775
776
777 public PackStatistics getStatistics() {
778 return statistics;
779 }
780
781 private Map<String, Ref> getAdvertisedOrDefaultRefs() throws IOException {
782 if (refs == null)
783 setAdvertisedRefs(db.getRefDatabase().getRefs(ALL));
784 return refs;
785 }
786
787 private void service() throws IOException {
788 boolean sendPack = false;
789
790
791 PackStatistics.Accumulator accumulator = new PackStatistics.Accumulator();
792 List<ObjectId> unshallowCommits = new ArrayList<>();
793 try {
794 if (biDirectionalPipe)
795 sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
796 else if (requestValidator instanceof AnyRequestValidator)
797 advertised = Collections.emptySet();
798 else
799 advertised = refIdSet(getAdvertisedOrDefaultRefs().values());
800
801 long negotiateStart = System.currentTimeMillis();
802 accumulator.advertised = advertised.size();
803 recvWants();
804 if (wantIds.isEmpty()) {
805 preUploadHook.onBeginNegotiateRound(this, wantIds, 0);
806 preUploadHook.onEndNegotiateRound(this, wantIds, 0, 0, false);
807 return;
808 }
809 accumulator.wants = wantIds.size();
810
811 if (options.contains(OPTION_MULTI_ACK_DETAILED)) {
812 multiAck = MultiAck.DETAILED;
813 noDone = options.contains(OPTION_NO_DONE);
814 } else if (options.contains(OPTION_MULTI_ACK))
815 multiAck = MultiAck.CONTINUE;
816 else
817 multiAck = MultiAck.OFF;
818
819 if (!clientShallowCommits.isEmpty())
820 verifyClientShallow();
821 if (depth != 0)
822 processShallow(null, unshallowCommits, true);
823 if (!clientShallowCommits.isEmpty())
824 walk.assumeShallow(clientShallowCommits);
825 sendPack = negotiate(accumulator);
826 accumulator.timeNegotiating += System.currentTimeMillis()
827 - negotiateStart;
828
829 if (sendPack && !biDirectionalPipe) {
830
831
832 int eof = rawIn.read();
833 if (0 <= eof) {
834 sendPack = false;
835 throw new CorruptObjectException(MessageFormat.format(
836 JGitText.get().expectedEOFReceived,
837 "\\x" + Integer.toHexString(eof)));
838 }
839 }
840 } catch (ServiceMayNotContinueException err) {
841 if (!err.isOutput() && err.getMessage() != null) {
842 try {
843 pckOut.writeString("ERR " + err.getMessage() + "\n");
844 err.setOutput();
845 } catch (Throwable err2) {
846
847 }
848 }
849 throw err;
850 } catch (IOException | RuntimeException | Error err) {
851 boolean output = false;
852 try {
853 String msg = err instanceof PackProtocolException
854 ? err.getMessage()
855 : JGitText.get().internalServerError;
856 pckOut.writeString("ERR " + msg + "\n");
857 output = true;
858 } catch (Throwable err2) {
859
860 }
861 if (output) {
862 throw new UploadPackInternalServerErrorException(err);
863 }
864 throw err;
865 } finally {
866 if (!sendPack && !biDirectionalPipe) {
867 while (0 < rawIn.skip(2048) || 0 <= rawIn.read()) {
868
869 }
870 }
871 rawOut.stopBuffering();
872 }
873
874 if (sendPack) {
875 sendPack(accumulator, refs == null ? null : refs.values(), unshallowCommits);
876 }
877 }
878
879 private void lsRefsV2() throws IOException {
880 PacketLineOutRefAdvertiser adv = new PacketLineOutRefAdvertiser(pckOut);
881 String line;
882 ArrayList<String> refPrefixes = new ArrayList<>();
883 boolean needToFindSymrefs = false;
884
885 adv.setUseProtocolV2(true);
886
887 line = pckIn.readString();
888
889
890
891 if (line == PacketLineIn.DELIM) {
892 while ((line = pckIn.readString()) != PacketLineIn.END) {
893 if (line.equals("peel")) {
894 adv.setDerefTags(true);
895 } else if (line.equals("symrefs")) {
896 needToFindSymrefs = true;
897 } else if (line.startsWith("ref-prefix ")) {
898 refPrefixes.add(line.substring("ref-prefix ".length()));
899 } else {
900 throw new PackProtocolException(MessageFormat
901 .format(JGitText.get().unexpectedPacketLine, line));
902 }
903 }
904 } else if (line != PacketLineIn.END) {
905 throw new PackProtocolException(MessageFormat
906 .format(JGitText.get().unexpectedPacketLine, line));
907 }
908 rawOut.stopBuffering();
909
910 Map<String, Ref> refsToSend;
911 if (refPrefixes.isEmpty()) {
912 refsToSend = getAdvertisedOrDefaultRefs();
913 } else {
914 refsToSend = new HashMap<>();
915 for (String refPrefix : refPrefixes) {
916 for (Ref ref : db.getRefDatabase().getRefsByPrefix(refPrefix)) {
917 refsToSend.put(ref.getName(), ref);
918 }
919 }
920 }
921
922 if (needToFindSymrefs) {
923 findSymrefs(adv, refsToSend);
924 }
925
926 adv.send(refsToSend);
927 adv.end();
928 }
929
930 private void fetchV2() throws IOException {
931 options = new HashSet<>();
932
933
934
935 options.add(OPTION_SIDE_BAND_64K);
936
937
938
939
940
941 if (requestValidator instanceof TipRequestValidator ||
942 requestValidator instanceof ReachableCommitTipRequestValidator ||
943 requestValidator instanceof AnyRequestValidator) {
944 advertised = Collections.emptySet();
945 } else {
946 advertised = refIdSet(getAdvertisedOrDefaultRefs().values());
947 }
948
949 String line;
950 List<ObjectId> peerHas = new ArrayList<>();
951 boolean doneReceived = false;
952
953
954
955 if ((line = pckIn.readString()) != PacketLineIn.DELIM) {
956 throw new PackProtocolException(MessageFormat
957 .format(JGitText.get().unexpectedPacketLine, line));
958 }
959
960 boolean includeTag = false;
961 boolean filterReceived = false;
962 while ((line = pckIn.readString()) != PacketLineIn.END) {
963 if (line.startsWith("want ")) {
964 wantIds.add(ObjectId.fromString(line.substring(5)));
965 } else if (line.startsWith("have ")) {
966 peerHas.add(ObjectId.fromString(line.substring(5)));
967 } else if (line.equals("done")) {
968 doneReceived = true;
969 } else if (line.equals(OPTION_THIN_PACK)) {
970 options.add(OPTION_THIN_PACK);
971 } else if (line.equals(OPTION_NO_PROGRESS)) {
972 options.add(OPTION_NO_PROGRESS);
973 } else if (line.equals(OPTION_INCLUDE_TAG)) {
974 options.add(OPTION_INCLUDE_TAG);
975 includeTag = true;
976 } else if (line.equals(OPTION_OFS_DELTA)) {
977 options.add(OPTION_OFS_DELTA);
978 } else if (line.startsWith("shallow ")) {
979 clientShallowCommits.add(ObjectId.fromString(line.substring(8)));
980 } else if (line.startsWith("deepen ")) {
981 depth = Integer.parseInt(line.substring(7));
982 if (depth <= 0) {
983 throw new PackProtocolException(
984 MessageFormat.format(JGitText.get().invalidDepth,
985 Integer.valueOf(depth)));
986 }
987 if (shallowSince != 0) {
988 throw new PackProtocolException(
989 JGitText.get().deepenSinceWithDeepen);
990 }
991 if (shallowExcludeRefs != null) {
992 throw new PackProtocolException(
993 JGitText.get().deepenNotWithDeepen);
994 }
995 } else if (line.startsWith("deepen-not ")) {
996 List<String> exclude = shallowExcludeRefs;
997 if (exclude == null) {
998 exclude = shallowExcludeRefs = new ArrayList<>();
999 }
1000 exclude.add(line.substring(11));
1001 if (depth != 0) {
1002 throw new PackProtocolException(
1003 JGitText.get().deepenNotWithDeepen);
1004 }
1005 } else if (line.equals(OPTION_DEEPEN_RELATIVE)) {
1006 options.add(OPTION_DEEPEN_RELATIVE);
1007 } else if (line.startsWith("deepen-since ")) {
1008 shallowSince = Integer.parseInt(line.substring(13));
1009 if (shallowSince <= 0) {
1010 throw new PackProtocolException(
1011 MessageFormat.format(
1012 JGitText.get().invalidTimestamp, line));
1013 }
1014 if (depth != 0) {
1015 throw new PackProtocolException(
1016 JGitText.get().deepenSinceWithDeepen);
1017 }
1018 } else if (transferConfig.isAllowFilter()
1019 && line.startsWith(OPTION_FILTER + ' ')) {
1020 if (filterReceived) {
1021 throw new PackProtocolException(JGitText.get().tooManyFilters);
1022 }
1023 filterReceived = true;
1024 parseFilter(line.substring(OPTION_FILTER.length() + 1));
1025 } else {
1026 throw new PackProtocolException(MessageFormat
1027 .format(JGitText.get().unexpectedPacketLine, line));
1028 }
1029 }
1030 rawOut.stopBuffering();
1031
1032 boolean sectionSent = false;
1033 @Nullable List<ObjectId> shallowCommits = null;
1034 List<ObjectId> unshallowCommits = new ArrayList<>();
1035
1036 if (!clientShallowCommits.isEmpty()) {
1037 verifyClientShallow();
1038 }
1039 if (depth != 0 || shallowSince != 0 || shallowExcludeRefs != null) {
1040 shallowCommits = new ArrayList<ObjectId>();
1041 processShallow(shallowCommits, unshallowCommits, false);
1042 }
1043 if (!clientShallowCommits.isEmpty())
1044 walk.assumeShallow(clientShallowCommits);
1045
1046 if (doneReceived) {
1047 processHaveLines(peerHas, ObjectId.zeroId(), new PacketLineOut(NullOutputStream.INSTANCE));
1048 } else {
1049 pckOut.writeString("acknowledgments\n");
1050 for (ObjectId id : peerHas) {
1051 if (walk.getObjectReader().has(id)) {
1052 pckOut.writeString("ACK " + id.getName() + "\n");
1053 }
1054 }
1055 processHaveLines(peerHas, ObjectId.zeroId(), new PacketLineOut(NullOutputStream.INSTANCE));
1056 if (okToGiveUp()) {
1057 pckOut.writeString("ready\n");
1058 } else if (commonBase.isEmpty()) {
1059 pckOut.writeString("NAK\n");
1060 }
1061 sectionSent = true;
1062 }
1063
1064 if (doneReceived || okToGiveUp()) {
1065 if (shallowCommits != null) {
1066 if (sectionSent)
1067 pckOut.writeDelim();
1068 pckOut.writeString("shallow-info\n");
1069 for (ObjectId o : shallowCommits) {
1070 pckOut.writeString("shallow " + o.getName() + '\n');
1071 }
1072 for (ObjectId o : unshallowCommits) {
1073 pckOut.writeString("unshallow " + o.getName() + '\n');
1074 }
1075 sectionSent = true;
1076 }
1077
1078 if (sectionSent)
1079 pckOut.writeDelim();
1080 pckOut.writeString("packfile\n");
1081 sendPack(new PackStatistics.Accumulator(),
1082 includeTag
1083 ? db.getRefDatabase().getRefsByPrefix(R_TAGS)
1084 : null,
1085 new ArrayList<ObjectId>());
1086 }
1087 pckOut.end();
1088 }
1089
1090
1091
1092
1093
1094 private boolean serveOneCommandV2() throws IOException {
1095 String command;
1096 try {
1097 command = pckIn.readString();
1098 } catch (EOFException eof) {
1099
1100 return true;
1101 }
1102 if (command == PacketLineIn.END) {
1103
1104
1105
1106 return true;
1107 }
1108 if (command.equals("command=" + COMMAND_LS_REFS)) {
1109 lsRefsV2();
1110 return false;
1111 }
1112 if (command.equals("command=" + COMMAND_FETCH)) {
1113 fetchV2();
1114 return false;
1115 }
1116 throw new PackProtocolException(MessageFormat
1117 .format(JGitText.get().unknownTransportCommand, command));
1118 }
1119
1120 private List<String> getV2CapabilityAdvertisement() {
1121 ArrayList<String> caps = new ArrayList<>();
1122 caps.add("version 2");
1123 caps.add(COMMAND_LS_REFS);
1124 caps.add(
1125 COMMAND_FETCH + '=' +
1126 (transferConfig.isAllowFilter() ? OPTION_FILTER + ' ' : "") +
1127 OPTION_SHALLOW);
1128 return caps;
1129 }
1130
1131 private void serviceV2() throws IOException {
1132 if (biDirectionalPipe) {
1133
1134
1135
1136
1137 for (String s : getV2CapabilityAdvertisement()) {
1138 pckOut.writeString(s + "\n");
1139 }
1140 pckOut.end();
1141
1142 while (!serveOneCommandV2()) {
1143
1144 }
1145 return;
1146 }
1147
1148 try {
1149 serveOneCommandV2();
1150 } finally {
1151 while (0 < rawIn.skip(2048) || 0 <= rawIn.read()) {
1152
1153 }
1154 rawOut.stopBuffering();
1155 }
1156 }
1157
1158 private static Set<ObjectId> refIdSet(Collection<Ref> refs) {
1159 Set<ObjectId> ids = new HashSet<>(refs.size());
1160 for (Ref ref : refs) {
1161 ObjectId id = ref.getObjectId();
1162 if (id != null) {
1163 ids.add(id);
1164 }
1165 id = ref.getPeeledObjectId();
1166 if (id != null) {
1167 ids.add(id);
1168 }
1169 }
1170 return ids;
1171 }
1172
1173
1174
1175
1176
1177
1178
1179 private void processShallow(@Nullable List<ObjectId> shallowCommits,
1180 List<ObjectId> unshallowCommits,
1181 boolean writeToPckOut) throws IOException {
1182 if (options.contains(OPTION_DEEPEN_RELATIVE) ||
1183 shallowSince != 0 ||
1184 shallowExcludeRefs != null) {
1185
1186
1187 throw new UnsupportedOperationException();
1188 }
1189
1190 int walkDepth = depth - 1;
1191 try (DepthWalk.RevWalk depthWalk = new DepthWalk.RevWalk(
1192 walk.getObjectReader(), walkDepth)) {
1193
1194
1195 for (ObjectId o : wantIds) {
1196 try {
1197 depthWalk.markRoot(depthWalk.parseCommit(o));
1198 } catch (IncorrectObjectTypeException notCommit) {
1199
1200 }
1201 }
1202
1203 RevCommit o;
1204 while ((o = depthWalk.next()) != null) {
1205 DepthWalk.Commit c = (DepthWalk.Commit) o;
1206
1207
1208
1209 if (c.getDepth() == walkDepth
1210 && !clientShallowCommits.contains(c)) {
1211 if (shallowCommits != null) {
1212 shallowCommits.add(c.copy());
1213 }
1214 if (writeToPckOut) {
1215 pckOut.writeString("shallow " + o.name());
1216 }
1217 }
1218
1219
1220
1221 if (c.getDepth() < walkDepth
1222 && clientShallowCommits.remove(c)) {
1223 unshallowCommits.add(c.copy());
1224 if (writeToPckOut) {
1225 pckOut.writeString("unshallow " + c.name());
1226 }
1227 }
1228 }
1229 }
1230 if (writeToPckOut) {
1231 pckOut.end();
1232 }
1233 }
1234
1235 private void verifyClientShallow()
1236 throws IOException, PackProtocolException {
1237 AsyncRevObjectQueue q = walk.parseAny(clientShallowCommits, true);
1238 try {
1239 for (;;) {
1240 try {
1241
1242 RevObject o = q.next();
1243 if (o == null) {
1244 break;
1245 }
1246 if (!(o instanceof RevCommit)) {
1247 throw new PackProtocolException(
1248 MessageFormat.format(
1249 JGitText.get().invalidShallowObject,
1250 o.name()));
1251 }
1252 } catch (MissingObjectException notCommit) {
1253
1254
1255 clientShallowCommits.remove(notCommit.getObjectId());
1256 continue;
1257 }
1258 }
1259 } finally {
1260 q.release();
1261 }
1262 }
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274 public void sendAdvertisedRefs(RefAdvertiser adv) throws IOException,
1275 ServiceMayNotContinueException {
1276 sendAdvertisedRefs(adv, null);
1277 }
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295 public void sendAdvertisedRefs(RefAdvertiser adv,
1296 @Nullable String serviceName) throws IOException,
1297 ServiceMayNotContinueException {
1298 if (useProtocolV2()) {
1299
1300
1301 for (String s : getV2CapabilityAdvertisement()) {
1302 adv.writeOne(s);
1303 }
1304 adv.end();
1305 return;
1306 }
1307
1308 try {
1309 advertiseRefsHook.advertiseRefs(this);
1310 } catch (ServiceMayNotContinueException fail) {
1311 if (fail.getMessage() != null) {
1312 adv.writeOne("ERR " + fail.getMessage());
1313 fail.setOutput();
1314 }
1315 throw fail;
1316 }
1317
1318 if (serviceName != null) {
1319 adv.writeOne("# service=" + serviceName + '\n');
1320 adv.end();
1321 }
1322 adv.init(db);
1323 adv.advertiseCapability(OPTION_INCLUDE_TAG);
1324 adv.advertiseCapability(OPTION_MULTI_ACK_DETAILED);
1325 adv.advertiseCapability(OPTION_MULTI_ACK);
1326 adv.advertiseCapability(OPTION_OFS_DELTA);
1327 adv.advertiseCapability(OPTION_SIDE_BAND);
1328 adv.advertiseCapability(OPTION_SIDE_BAND_64K);
1329 adv.advertiseCapability(OPTION_THIN_PACK);
1330 adv.advertiseCapability(OPTION_NO_PROGRESS);
1331 adv.advertiseCapability(OPTION_SHALLOW);
1332 if (!biDirectionalPipe)
1333 adv.advertiseCapability(OPTION_NO_DONE);
1334 RequestPolicy policy = getRequestPolicy();
1335 if (policy == RequestPolicy.TIP
1336 || policy == RequestPolicy.REACHABLE_COMMIT_TIP
1337 || policy == null)
1338 adv.advertiseCapability(OPTION_ALLOW_TIP_SHA1_IN_WANT);
1339 if (policy == RequestPolicy.REACHABLE_COMMIT
1340 || policy == RequestPolicy.REACHABLE_COMMIT_TIP
1341 || policy == null)
1342 adv.advertiseCapability(OPTION_ALLOW_REACHABLE_SHA1_IN_WANT);
1343 adv.advertiseCapability(OPTION_AGENT, UserAgent.get());
1344 if (transferConfig.isAllowFilter()) {
1345 adv.advertiseCapability(OPTION_FILTER);
1346 }
1347 adv.setDerefTags(true);
1348 Map<String, Ref> advertisedOrDefaultRefs = getAdvertisedOrDefaultRefs();
1349 findSymrefs(adv, advertisedOrDefaultRefs);
1350 advertised = adv.send(advertisedOrDefaultRefs);
1351 if (adv.isEmpty())
1352 adv.advertiseId(ObjectId.zeroId(), "capabilities^{}");
1353 adv.end();
1354 }
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367 public void sendMessage(String what) {
1368 try {
1369 msgOut.write(Constants.encode(what + "\n"));
1370 } catch (IOException e) {
1371
1372 }
1373 }
1374
1375
1376
1377
1378
1379
1380
1381 public OutputStream getMessageOutputStream() {
1382 return msgOut;
1383 }
1384
1385 private void parseFilter(String arg) throws PackProtocolException {
1386 if (arg.equals("blob:none")) {
1387 filterBlobLimit = 0;
1388 } else if (arg.startsWith("blob:limit=")) {
1389 try {
1390 filterBlobLimit = Long.parseLong(
1391 arg.substring("blob:limit=".length()));
1392 } catch (NumberFormatException e) {
1393 throw new PackProtocolException(
1394 MessageFormat.format(JGitText.get().invalidFilter,
1395 arg));
1396 }
1397 }
1398
1399
1400
1401
1402
1403
1404
1405 if (filterBlobLimit < 0) {
1406 throw new PackProtocolException(
1407 MessageFormat.format(JGitText.get().invalidFilter,
1408 arg));
1409 }
1410 }
1411
1412 private void recvWants() throws IOException {
1413 boolean isFirst = true;
1414 boolean filterReceived = false;
1415 for (;;) {
1416 String line;
1417 try {
1418 line = pckIn.readString();
1419 } catch (EOFException eof) {
1420 if (isFirst)
1421 break;
1422 throw eof;
1423 }
1424
1425 if (line == PacketLineIn.END)
1426 break;
1427
1428 if (line.startsWith("deepen ")) {
1429 depth = Integer.parseInt(line.substring(7));
1430 if (depth <= 0) {
1431 throw new PackProtocolException(
1432 MessageFormat.format(JGitText.get().invalidDepth,
1433 Integer.valueOf(depth)));
1434 }
1435 continue;
1436 }
1437
1438 if (line.startsWith("shallow ")) {
1439 clientShallowCommits.add(ObjectId.fromString(line.substring(8)));
1440 continue;
1441 }
1442
1443 if (transferConfig.isAllowFilter()
1444 && line.startsWith(OPTION_FILTER + " ")) {
1445 String arg = line.substring(OPTION_FILTER.length() + 1);
1446
1447 if (filterReceived) {
1448 throw new PackProtocolException(JGitText.get().tooManyFilters);
1449 }
1450 filterReceived = true;
1451
1452 parseFilter(arg);
1453 continue;
1454 }
1455
1456 if (!line.startsWith("want ") || line.length() < 45)
1457 throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedGot, "want", line));
1458
1459 if (isFirst) {
1460 if (line.length() > 45) {
1461 FirstLine firstLine = new FirstLine(line);
1462 options = firstLine.getOptions();
1463 line = firstLine.getLine();
1464 } else
1465 options = Collections.emptySet();
1466 }
1467
1468 wantIds.add(ObjectId.fromString(line.substring(5)));
1469 isFirst = false;
1470 }
1471 }
1472
1473
1474
1475
1476
1477
1478
1479
1480 public int getDepth() {
1481 if (options == null)
1482 throw new RequestNotYetReadException();
1483 return depth;
1484 }
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501 public String getPeerUserAgent() {
1502 return UserAgent.getAgent(options, userAgent);
1503 }
1504
1505 private boolean negotiate(PackStatistics.Accumulator accumulator)
1506 throws IOException {
1507 okToGiveUp = Boolean.FALSE;
1508
1509 ObjectId last = ObjectId.zeroId();
1510 List<ObjectId> peerHas = new ArrayList<>(64);
1511 for (;;) {
1512 String line;
1513 try {
1514 line = pckIn.readString();
1515 } catch (EOFException eof) {
1516
1517
1518
1519
1520
1521 if (!biDirectionalPipe && depth > 0)
1522 return false;
1523 throw eof;
1524 }
1525
1526 if (line == PacketLineIn.END) {
1527 last = processHaveLines(peerHas, last, pckOut);
1528 if (commonBase.isEmpty() || multiAck != MultiAck.OFF)
1529 pckOut.writeString("NAK\n");
1530 if (noDone && sentReady) {
1531 pckOut.writeString("ACK " + last.name() + "\n");
1532 return true;
1533 }
1534 if (!biDirectionalPipe)
1535 return false;
1536 pckOut.flush();
1537
1538 } else if (line.startsWith("have ") && line.length() == 45) {
1539 peerHas.add(ObjectId.fromString(line.substring(5)));
1540 accumulator.haves++;
1541 } else if (line.equals("done")) {
1542 last = processHaveLines(peerHas, last, pckOut);
1543
1544 if (commonBase.isEmpty())
1545 pckOut.writeString("NAK\n");
1546
1547 else if (multiAck != MultiAck.OFF)
1548 pckOut.writeString("ACK " + last.name() + "\n");
1549
1550 return true;
1551
1552 } else {
1553 throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedGot, "have", line));
1554 }
1555 }
1556 }
1557
1558 private ObjectId processHaveLines(List<ObjectId> peerHas, ObjectId last, PacketLineOut out)
1559 throws IOException {
1560 preUploadHook.onBeginNegotiateRound(this, wantIds, peerHas.size());
1561 if (wantAll.isEmpty() && !wantIds.isEmpty())
1562 parseWants();
1563 if (peerHas.isEmpty())
1564 return last;
1565
1566 sentReady = false;
1567 int haveCnt = 0;
1568 walk.getObjectReader().setAvoidUnreachableObjects(true);
1569 AsyncRevObjectQueue q = walk.parseAny(peerHas, false);
1570 try {
1571 for (;;) {
1572 RevObject obj;
1573 try {
1574 obj = q.next();
1575 } catch (MissingObjectException notFound) {
1576 continue;
1577 }
1578 if (obj == null)
1579 break;
1580
1581 last = obj;
1582 haveCnt++;
1583
1584 if (obj instanceof RevCommit) {
1585 RevCommit c = (RevCommit) obj;
1586 if (oldestTime == 0 || c.getCommitTime() < oldestTime)
1587 oldestTime = c.getCommitTime();
1588 }
1589
1590 if (obj.has(PEER_HAS))
1591 continue;
1592
1593 obj.add(PEER_HAS);
1594 if (obj instanceof RevCommit)
1595 ((RevCommit) obj).carry(PEER_HAS);
1596 addCommonBase(obj);
1597
1598
1599
1600 switch (multiAck) {
1601 case OFF:
1602 if (commonBase.size() == 1)
1603 out.writeString("ACK " + obj.name() + "\n");
1604 break;
1605 case CONTINUE:
1606 out.writeString("ACK " + obj.name() + " continue\n");
1607 break;
1608 case DETAILED:
1609 out.writeString("ACK " + obj.name() + " common\n");
1610 break;
1611 }
1612 }
1613 } finally {
1614 q.release();
1615 walk.getObjectReader().setAvoidUnreachableObjects(false);
1616 }
1617
1618 int missCnt = peerHas.size() - haveCnt;
1619
1620
1621
1622
1623
1624 boolean didOkToGiveUp = false;
1625 if (0 < missCnt) {
1626 for (int i = peerHas.size() - 1; i >= 0; i--) {
1627 ObjectId id = peerHas.get(i);
1628 if (walk.lookupOrNull(id) == null) {
1629 didOkToGiveUp = true;
1630 if (okToGiveUp()) {
1631 switch (multiAck) {
1632 case OFF:
1633 break;
1634 case CONTINUE:
1635 out.writeString("ACK " + id.name() + " continue\n");
1636 break;
1637 case DETAILED:
1638 out.writeString("ACK " + id.name() + " ready\n");
1639 sentReady = true;
1640 break;
1641 }
1642 }
1643 break;
1644 }
1645 }
1646 }
1647
1648 if (multiAck == MultiAck.DETAILED && !didOkToGiveUp && okToGiveUp()) {
1649 ObjectId id = peerHas.get(peerHas.size() - 1);
1650 out.writeString("ACK " + id.name() + " ready\n");
1651 sentReady = true;
1652 }
1653
1654 preUploadHook.onEndNegotiateRound(this, wantAll, haveCnt, missCnt, sentReady);
1655 peerHas.clear();
1656 return last;
1657 }
1658
1659 private void parseWants() throws IOException {
1660 List<ObjectId> notAdvertisedWants = null;
1661 for (ObjectId obj : wantIds) {
1662 if (!advertised.contains(obj)) {
1663 if (notAdvertisedWants == null)
1664 notAdvertisedWants = new ArrayList<>();
1665 notAdvertisedWants.add(obj);
1666 }
1667 }
1668 if (notAdvertisedWants != null)
1669 requestValidator.checkWants(this, notAdvertisedWants);
1670
1671 AsyncRevObjectQueue q = walk.parseAny(wantIds, true);
1672 try {
1673 RevObject obj;
1674 while ((obj = q.next()) != null) {
1675 want(obj);
1676
1677 if (!(obj instanceof RevCommit))
1678 obj.add(SATISFIED);
1679 if (obj instanceof RevTag) {
1680 obj = walk.peel(obj);
1681 if (obj instanceof RevCommit)
1682 want(obj);
1683 }
1684 }
1685 wantIds.clear();
1686 } catch (MissingObjectException notFound) {
1687 throw new WantNotValidException(notFound.getObjectId(), notFound);
1688 } finally {
1689 q.release();
1690 }
1691 }
1692
1693 private void want(RevObject obj) {
1694 if (!obj.has(WANT)) {
1695 obj.add(WANT);
1696 wantAll.add(obj);
1697 }
1698 }
1699
1700
1701
1702
1703
1704
1705 public static final class AdvertisedRequestValidator
1706 implements RequestValidator {
1707 @Override
1708 public void checkWants(UploadPack up, List<ObjectId> wants)
1709 throws PackProtocolException, IOException {
1710 if (!up.isBiDirectionalPipe())
1711 new ReachableCommitRequestValidator().checkWants(up, wants);
1712 else if (!wants.isEmpty())
1713 throw new WantNotValidException(wants.iterator().next());
1714 }
1715 }
1716
1717
1718
1719
1720
1721
1722 public static final class ReachableCommitRequestValidator
1723 implements RequestValidator {
1724 @Override
1725 public void checkWants(UploadPack up, List<ObjectId> wants)
1726 throws PackProtocolException, IOException {
1727 checkNotAdvertisedWants(up, wants,
1728 refIdSet(up.getAdvertisedRefs().values()));
1729 }
1730 }
1731
1732
1733
1734
1735
1736
1737 public static final class TipRequestValidator implements RequestValidator {
1738 @Override
1739 public void checkWants(UploadPack up, List<ObjectId> wants)
1740 throws PackProtocolException, IOException {
1741 if (!up.isBiDirectionalPipe())
1742 new ReachableCommitTipRequestValidator().checkWants(up, wants);
1743 else if (!wants.isEmpty()) {
1744 Set<ObjectId> refIds =
1745 refIdSet(up.getRepository().getRefDatabase().getRefs());
1746 for (ObjectId obj : wants) {
1747 if (!refIds.contains(obj))
1748 throw new WantNotValidException(obj);
1749 }
1750 }
1751 }
1752 }
1753
1754
1755
1756
1757
1758
1759 public static final class ReachableCommitTipRequestValidator
1760 implements RequestValidator {
1761 @Override
1762 public void checkWants(UploadPack up, List<ObjectId> wants)
1763 throws PackProtocolException, IOException {
1764 checkNotAdvertisedWants(up, wants,
1765 refIdSet(up.getRepository().getRefDatabase().getRefs()));
1766 }
1767 }
1768
1769
1770
1771
1772
1773
1774 public static final class AnyRequestValidator implements RequestValidator {
1775 @Override
1776 public void checkWants(UploadPack up, List<ObjectId> wants)
1777 throws PackProtocolException, IOException {
1778
1779 }
1780 }
1781
1782 private static void checkNotAdvertisedWantsUsingBitmap(ObjectReader reader,
1783 BitmapIndex bitmapIndex, List<ObjectId> notAdvertisedWants,
1784 Set<ObjectId> reachableFrom) throws IOException {
1785 BitmapWalker bitmapWalker = new BitmapWalker(new ObjectWalk(reader), bitmapIndex, null);
1786 BitmapBuilder reachables = bitmapWalker.findObjects(reachableFrom, null, false);
1787 for (ObjectId oid : notAdvertisedWants) {
1788 if (!reachables.contains(oid)) {
1789 throw new WantNotValidException(oid);
1790 }
1791 }
1792 }
1793
1794 private static void checkNotAdvertisedWants(UploadPack up,
1795 List<ObjectId> notAdvertisedWants, Set<ObjectId> reachableFrom)
1796 throws MissingObjectException, IncorrectObjectTypeException, IOException {
1797
1798
1799
1800
1801
1802
1803 ObjectReader reader = up.getRevWalk().getObjectReader();
1804 try (RevWalk walk = new RevWalk(reader)) {
1805 AsyncRevObjectQueue q = walk.parseAny(notAdvertisedWants, true);
1806 try {
1807 RevObject obj;
1808 while ((obj = q.next()) != null) {
1809 if (!(obj instanceof RevCommit)) {
1810
1811
1812
1813
1814 BitmapIndex bitmapIndex = reader.getBitmapIndex();
1815 if (bitmapIndex != null) {
1816 checkNotAdvertisedWantsUsingBitmap(
1817 reader,
1818 bitmapIndex,
1819 notAdvertisedWants,
1820 reachableFrom);
1821 return;
1822 }
1823 throw new WantNotValidException(obj);
1824 }
1825 walk.markStart((RevCommit) obj);
1826 }
1827 } catch (MissingObjectException notFound) {
1828 throw new WantNotValidException(notFound.getObjectId(),
1829 notFound);
1830 } finally {
1831 q.release();
1832 }
1833 for (ObjectId id : reachableFrom) {
1834 try {
1835 walk.markUninteresting(walk.parseCommit(id));
1836 } catch (IncorrectObjectTypeException notCommit) {
1837 continue;
1838 }
1839 }
1840
1841 RevCommit bad = walk.next();
1842 if (bad != null) {
1843 throw new WantNotValidException(bad);
1844 }
1845 }
1846 }
1847
1848 private void addCommonBase(RevObject o) {
1849 if (!o.has(COMMON)) {
1850 o.add(COMMON);
1851 commonBase.add(o);
1852 okToGiveUp = null;
1853 }
1854 }
1855
1856 private boolean okToGiveUp() throws PackProtocolException {
1857 if (okToGiveUp == null)
1858 okToGiveUp = Boolean.valueOf(okToGiveUpImp());
1859 return okToGiveUp.booleanValue();
1860 }
1861
1862 private boolean okToGiveUpImp() throws PackProtocolException {
1863 if (commonBase.isEmpty())
1864 return false;
1865
1866 try {
1867 for (RevObject obj : wantAll) {
1868 if (!wantSatisfied(obj))
1869 return false;
1870 }
1871 return true;
1872 } catch (IOException e) {
1873 throw new PackProtocolException(JGitText.get().internalRevisionError, e);
1874 }
1875 }
1876
1877 private boolean wantSatisfied(RevObject want) throws IOException {
1878 if (want.has(SATISFIED))
1879 return true;
1880
1881 walk.resetRetain(SAVE);
1882 walk.markStart((RevCommit) want);
1883 if (oldestTime != 0)
1884 walk.setRevFilter(CommitTimeRevFilter.after(oldestTime * 1000L));
1885 for (;;) {
1886 final RevCommit c = walk.next();
1887 if (c == null)
1888 break;
1889 if (c.has(PEER_HAS)) {
1890 addCommonBase(c);
1891 want.add(SATISFIED);
1892 return true;
1893 }
1894 }
1895 return false;
1896 }
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913 private void sendPack(PackStatistics.Accumulator accumulator,
1914 @Nullable Collection<Ref> allTags,
1915 List<ObjectId> unshallowCommits) throws IOException {
1916 final boolean sideband = options.contains(OPTION_SIDE_BAND)
1917 || options.contains(OPTION_SIDE_BAND_64K);
1918 if (sideband) {
1919 try {
1920 sendPack(true, accumulator, allTags, unshallowCommits);
1921 } catch (ServiceMayNotContinueException noPack) {
1922
1923 throw noPack;
1924 } catch (IOException err) {
1925 if (reportInternalServerErrorOverSideband())
1926 throw new UploadPackInternalServerErrorException(err);
1927 else
1928 throw err;
1929 } catch (RuntimeException err) {
1930 if (reportInternalServerErrorOverSideband())
1931 throw new UploadPackInternalServerErrorException(err);
1932 else
1933 throw err;
1934 } catch (Error err) {
1935 if (reportInternalServerErrorOverSideband())
1936 throw new UploadPackInternalServerErrorException(err);
1937 else
1938 throw err;
1939 }
1940 } else {
1941 sendPack(false, accumulator, allTags, unshallowCommits);
1942 }
1943 }
1944
1945 private boolean reportInternalServerErrorOverSideband() {
1946 try {
1947 @SuppressWarnings("resource" )
1948 SideBandOutputStream err = new SideBandOutputStream(
1949 SideBandOutputStream.CH_ERROR,
1950 SideBandOutputStream.SMALL_BUF,
1951 rawOut);
1952 err.write(Constants.encode(JGitText.get().internalServerError));
1953 err.flush();
1954 return true;
1955 } catch (Throwable cannotReport) {
1956
1957 return false;
1958 }
1959 }
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979 private void sendPack(final boolean sideband,
1980 PackStatistics.Accumulator accumulator,
1981 @Nullable Collection<Ref> allTags,
1982 List<ObjectId> unshallowCommits) throws IOException {
1983 ProgressMonitor pm = NullProgressMonitor.INSTANCE;
1984 OutputStream packOut = rawOut;
1985
1986 if (sideband) {
1987 int bufsz = SideBandOutputStream.SMALL_BUF;
1988 if (options.contains(OPTION_SIDE_BAND_64K))
1989 bufsz = SideBandOutputStream.MAX_BUF;
1990
1991 packOut = new SideBandOutputStream(SideBandOutputStream.CH_DATA,
1992 bufsz, rawOut);
1993 if (!options.contains(OPTION_NO_PROGRESS)) {
1994 msgOut = new SideBandOutputStream(
1995 SideBandOutputStream.CH_PROGRESS, bufsz, rawOut);
1996 pm = new SideBandProgressMonitor(msgOut);
1997 }
1998 }
1999
2000 try {
2001 if (wantAll.isEmpty()) {
2002 preUploadHook.onSendPack(this, wantIds, commonBase);
2003 } else {
2004 preUploadHook.onSendPack(this, wantAll, commonBase);
2005 }
2006 msgOut.flush();
2007 } catch (ServiceMayNotContinueException noPack) {
2008 if (sideband && noPack.getMessage() != null) {
2009 noPack.setOutput();
2010 @SuppressWarnings("resource" )
2011 SideBandOutputStream err = new SideBandOutputStream(
2012 SideBandOutputStream.CH_ERROR,
2013 SideBandOutputStream.SMALL_BUF, rawOut);
2014 err.write(Constants.encode(noPack.getMessage()));
2015 err.flush();
2016 }
2017 throw noPack;
2018 }
2019
2020 PackConfig cfg = packConfig;
2021 if (cfg == null)
2022 cfg = new PackConfig(db);
2023 @SuppressWarnings("resource")
2024
2025 final PackWriter pw = new PackWriter(cfg, walk.getObjectReader(),
2026 accumulator);
2027 try {
2028 pw.setIndexDisabled(true);
2029 if (filterBlobLimit >= 0) {
2030 pw.setFilterBlobLimit(filterBlobLimit);
2031 pw.setUseCachedPacks(false);
2032 } else {
2033 pw.setUseCachedPacks(true);
2034 }
2035 pw.setUseBitmaps(depth == 0 && clientShallowCommits.isEmpty());
2036 pw.setClientShallowCommits(clientShallowCommits);
2037 pw.setReuseDeltaCommits(true);
2038 pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA));
2039 pw.setThin(options.contains(OPTION_THIN_PACK));
2040 pw.setReuseValidatingObjects(false);
2041
2042
2043
2044 if (commonBase.isEmpty() && refs != null) {
2045 Set<ObjectId> tagTargets = new HashSet<>();
2046 for (Ref ref : refs.values()) {
2047 if (ref.getPeeledObjectId() != null)
2048 tagTargets.add(ref.getPeeledObjectId());
2049 else if (ref.getObjectId() == null)
2050 continue;
2051 else if (ref.getName().startsWith(Constants.R_HEADS))
2052 tagTargets.add(ref.getObjectId());
2053 }
2054 pw.setTagTargets(tagTargets);
2055 }
2056
2057 RevWalk rw = walk;
2058 if (depth > 0) {
2059 pw.setShallowPack(depth, unshallowCommits);
2060 rw = new DepthWalk.RevWalk(walk.getObjectReader(), depth - 1);
2061 rw.assumeShallow(clientShallowCommits);
2062 }
2063
2064 if (wantAll.isEmpty()) {
2065 pw.preparePack(pm, wantIds, commonBase, clientShallowCommits);
2066 } else {
2067 walk.reset();
2068
2069 ObjectWalk ow = rw.toObjectWalkWithSameObjects();
2070 pw.preparePack(pm, ow, wantAll, commonBase, PackWriter.NONE);
2071 rw = ow;
2072 }
2073
2074 if (options.contains(OPTION_INCLUDE_TAG) && allTags != null) {
2075 for (Ref ref : allTags) {
2076 ObjectId objectId = ref.getObjectId();
2077 if (objectId == null) {
2078
2079 continue;
2080 }
2081
2082
2083 if (wantAll.isEmpty()) {
2084 if (wantIds.contains(objectId))
2085 continue;
2086 } else {
2087 RevObject obj = rw.lookupOrNull(objectId);
2088 if (obj != null && obj.has(WANT))
2089 continue;
2090 }
2091
2092 if (!ref.isPeeled())
2093 ref = db.getRefDatabase().peel(ref);
2094
2095 ObjectId peeledId = ref.getPeeledObjectId();
2096 objectId = ref.getObjectId();
2097 if (peeledId == null || objectId == null)
2098 continue;
2099
2100 if (pw.willInclude(peeledId) && !pw.willInclude(objectId)) {
2101 pw.addObject(rw.parseAny(objectId));
2102 }
2103 }
2104 }
2105
2106 pw.writePack(pm, NullProgressMonitor.INSTANCE, packOut);
2107
2108 if (msgOut != NullOutputStream.INSTANCE) {
2109 String msg = pw.getStatistics().getMessage() + '\n';
2110 msgOut.write(Constants.encode(msg));
2111 msgOut.flush();
2112 }
2113
2114 } finally {
2115 statistics = pw.getStatistics();
2116 if (statistics != null) {
2117 postUploadHook.onPostUpload(statistics);
2118 }
2119 pw.close();
2120 }
2121
2122 if (sideband)
2123 pckOut.end();
2124 }
2125
2126 private static void findSymrefs(
2127 final RefAdvertiser adv, final Map<String, Ref> refs) {
2128 Ref head = refs.get(Constants.HEAD);
2129 if (head != null && head.isSymbolic()) {
2130 adv.addSymref(Constants.HEAD, head.getLeaf().getName());
2131 }
2132 }
2133
2134 private static class ResponseBufferedOutputStream extends OutputStream {
2135 private final OutputStream rawOut;
2136
2137 private OutputStream out;
2138
2139 ResponseBufferedOutputStream(OutputStream rawOut) {
2140 this.rawOut = rawOut;
2141 this.out = new ByteArrayOutputStream();
2142 }
2143
2144 @Override
2145 public void write(int b) throws IOException {
2146 out.write(b);
2147 }
2148
2149 @Override
2150 public void write(byte b[]) throws IOException {
2151 out.write(b);
2152 }
2153
2154 @Override
2155 public void write(byte b[], int off, int len) throws IOException {
2156 out.write(b, off, len);
2157 }
2158
2159 @Override
2160 public void flush() throws IOException {
2161 out.flush();
2162 }
2163
2164 @Override
2165 public void close() throws IOException {
2166 out.close();
2167 }
2168
2169 void stopBuffering() throws IOException {
2170 if (out != rawOut) {
2171 ((ByteArrayOutputStream) out).writeTo(rawOut);
2172 out = rawOut;
2173 }
2174 }
2175 }
2176 }