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