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