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