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