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