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