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