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