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