1
2
3
4
5
6
7
8
9
10
11
12
13 package org.eclipse.jgit.transport;
14
15 import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DELIM;
16 import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN;
17 import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_NOT;
18 import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_SINCE;
19 import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DONE;
20 import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_END;
21 import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_ERR;
22 import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_HAVE;
23 import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW;
24 import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_UNSHALLOW;
25 import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_WANT;
26
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.text.MessageFormat;
31 import java.time.Instant;
32 import java.util.Arrays;
33 import java.util.Collection;
34 import java.util.Collections;
35 import java.util.Date;
36 import java.util.HashSet;
37 import java.util.LinkedHashSet;
38 import java.util.List;
39 import java.util.Set;
40
41 import org.eclipse.jgit.errors.PackProtocolException;
42 import org.eclipse.jgit.errors.RemoteRepositoryException;
43 import org.eclipse.jgit.errors.TransportException;
44 import org.eclipse.jgit.internal.JGitText;
45 import org.eclipse.jgit.lib.AnyObjectId;
46 import org.eclipse.jgit.lib.Config;
47 import org.eclipse.jgit.lib.MutableObjectId;
48 import org.eclipse.jgit.lib.NullProgressMonitor;
49 import org.eclipse.jgit.lib.ObjectDatabase;
50 import org.eclipse.jgit.lib.ObjectId;
51 import org.eclipse.jgit.lib.ObjectInserter;
52 import org.eclipse.jgit.lib.ProgressMonitor;
53 import org.eclipse.jgit.lib.Ref;
54 import org.eclipse.jgit.revwalk.RevCommit;
55 import org.eclipse.jgit.revwalk.RevCommitList;
56 import org.eclipse.jgit.revwalk.RevFlag;
57 import org.eclipse.jgit.revwalk.RevObject;
58 import org.eclipse.jgit.revwalk.RevSort;
59 import org.eclipse.jgit.revwalk.RevWalk;
60 import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter;
61 import org.eclipse.jgit.revwalk.filter.RevFilter;
62 import org.eclipse.jgit.transport.GitProtocolConstants.MultiAck;
63 import org.eclipse.jgit.transport.PacketLineIn.AckNackResult;
64 import org.eclipse.jgit.util.StringUtils;
65 import org.eclipse.jgit.util.TemporaryBuffer;
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89 public abstract class BasePackFetchConnection extends BasePackConnection
90 implements FetchConnection {
91
92
93
94
95
96
97
98 private static final int MAX_HAVES = 256;
99
100
101
102
103
104
105
106
107
108 protected static final int MIN_CLIENT_BUFFER = 2 * 32 * 46 + 8;
109
110
111
112
113
114 public static final String OPTION_INCLUDE_TAG = GitProtocolConstants.OPTION_INCLUDE_TAG;
115
116
117
118
119
120 public static final String OPTION_MULTI_ACK = GitProtocolConstants.OPTION_MULTI_ACK;
121
122
123
124
125
126 public static final String OPTION_MULTI_ACK_DETAILED = GitProtocolConstants.OPTION_MULTI_ACK_DETAILED;
127
128
129
130
131
132 public static final String OPTION_THIN_PACK = GitProtocolConstants.OPTION_THIN_PACK;
133
134
135
136
137
138 public static final String OPTION_SIDE_BAND = GitProtocolConstants.OPTION_SIDE_BAND;
139
140
141
142
143
144 public static final String OPTION_SIDE_BAND_64K = GitProtocolConstants.OPTION_SIDE_BAND_64K;
145
146
147
148
149
150 public static final String OPTION_OFS_DELTA = GitProtocolConstants.OPTION_OFS_DELTA;
151
152
153
154
155
156 public static final String OPTION_SHALLOW = GitProtocolConstants.OPTION_SHALLOW;
157
158
159
160
161
162 public static final String OPTION_NO_PROGRESS = GitProtocolConstants.OPTION_NO_PROGRESS;
163
164
165
166
167
168 public static final String OPTION_NO_DONE = GitProtocolConstants.OPTION_NO_DONE;
169
170
171
172
173
174
175 public static final String OPTION_ALLOW_TIP_SHA1_IN_WANT = GitProtocolConstants.OPTION_ALLOW_TIP_SHA1_IN_WANT;
176
177
178
179
180
181
182 public static final String OPTION_ALLOW_REACHABLE_SHA1_IN_WANT = GitProtocolConstants.OPTION_ALLOW_REACHABLE_SHA1_IN_WANT;
183
184
185
186
187
188
189 public static final String OPTION_FILTER = GitProtocolConstants.OPTION_FILTER;
190
191 private final RevWalk walk;
192
193
194 private RevCommitList<RevCommit> reachableCommits;
195
196
197 final RevFlag REACHABLE;
198
199
200 final RevFlag COMMON;
201
202
203 private final RevFlag STATE;
204
205
206 final RevFlag ADVERTISED;
207
208 private MultiAck multiAck = MultiAck.OFF;
209
210 private boolean thinPack;
211
212 private boolean sideband;
213
214 private boolean includeTags;
215
216 private boolean allowOfsDelta;
217
218 private boolean noDone;
219
220 private boolean noProgress;
221
222 private String lockMessage;
223
224 private PackLock packLock;
225
226 private int maxHaves;
227
228 private Integer depth;
229
230 private Instant deepenSince;
231
232 private List<String> deepenNots;
233
234
235
236
237
238 private TemporaryBuffer.Heap state;
239
240 private PacketLineOut pckState;
241
242
243
244
245
246
247 private final FilterSpec filterSpec;
248
249
250
251
252
253
254
255 public BasePackFetchConnection(PackTransport packTransport) {
256 super(packTransport);
257
258 if (local != null) {
259 final FetchConfig cfg = getFetchConfig();
260 allowOfsDelta = cfg.allowOfsDelta;
261 maxHaves = cfg.maxHaves;
262 } else {
263 allowOfsDelta = true;
264 maxHaves = Integer.MAX_VALUE;
265 }
266
267 includeTags = transport.getTagOpt() != TagOpt.NO_TAGS;
268 thinPack = transport.isFetchThin();
269 filterSpec = transport.getFilterSpec();
270 depth = transport.getDepth();
271 deepenSince = transport.getDeepenSince();
272 deepenNots = transport.getDeepenNots();
273
274 if (local != null) {
275 walk = new RevWalk(local);
276 walk.setRetainBody(false);
277 reachableCommits = new RevCommitList<>();
278 REACHABLE = walk.newFlag("REACHABLE");
279 COMMON = walk.newFlag("COMMON");
280 STATE = walk.newFlag("STATE");
281 ADVERTISED = walk.newFlag("ADVERTISED");
282
283 walk.carry(COMMON);
284 walk.carry(REACHABLE);
285 walk.carry(ADVERTISED);
286 } else {
287 walk = null;
288 REACHABLE = null;
289 COMMON = null;
290 STATE = null;
291 ADVERTISED = null;
292 }
293 }
294
295 static class FetchConfig {
296 final boolean allowOfsDelta;
297
298 final int maxHaves;
299
300 FetchConfig(Config c) {
301 allowOfsDelta = c.getBoolean("repack", "usedeltabaseoffset", true);
302 maxHaves = c.getInt("fetch", "maxhaves", Integer.MAX_VALUE);
303 }
304
305 FetchConfig(boolean allowOfsDelta, int maxHaves) {
306 this.allowOfsDelta = allowOfsDelta;
307 this.maxHaves = maxHaves;
308 }
309 }
310
311
312 @Override
313 public final void fetch(final ProgressMonitor monitor,
314 final Collection<Ref> want, final Set<ObjectId> have)
315 throws TransportException {
316 fetch(monitor, want, have, null);
317 }
318
319
320 @Override
321 public final void fetch(final ProgressMonitor monitor,
322 final Collection<Ref> want, final Set<ObjectId> have,
323 OutputStream outputStream) throws TransportException {
324 markStartedOperation();
325 doFetch(monitor, want, have, outputStream);
326 }
327
328
329 @Override
330 public boolean didFetchIncludeTags() {
331 return false;
332 }
333
334
335 @Override
336 public boolean didFetchTestConnectivity() {
337 return false;
338 }
339
340
341 @Override
342 public void setPackLockMessage(String message) {
343 lockMessage = message;
344 }
345
346
347 @Override
348 public Collection<PackLock> getPackLocks() {
349 if (packLock != null)
350 return Collections.singleton(packLock);
351 return Collections.<PackLock> emptyList();
352 }
353
354 private void clearState() {
355 walk.dispose();
356 reachableCommits = null;
357 state = null;
358 pckState = null;
359 }
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380 protected void doFetch(final ProgressMonitor monitor,
381 final Collection<Ref> want, final Set<ObjectId> have,
382 OutputStream outputStream) throws TransportException {
383 try {
384 noProgress = monitor == NullProgressMonitor.INSTANCE;
385
386 markRefsAdvertised();
387 markReachable(have, maxTimeWanted(want));
388
389 if (TransferConfig.ProtocolVersion.V2
390 .equals(getProtocolVersion())) {
391
392
393
394
395
396 state = new TemporaryBuffer.Heap(Integer.MAX_VALUE);
397 pckState = new PacketLineOut(state);
398 try {
399 doFetchV2(monitor, want, outputStream);
400 } finally {
401 clearState();
402 }
403 return;
404 }
405
406 if (statelessRPC) {
407 state = new TemporaryBuffer.Heap(Integer.MAX_VALUE);
408 pckState = new PacketLineOut(state);
409 }
410 PacketLineOut output = statelessRPC ? pckState : pckOut;
411 if (sendWants(want, output)) {
412 boolean mayHaveShallow = depth != null || deepenSince != null || !deepenNots.isEmpty();
413 Set<ObjectId> shallowCommits = local.getObjectDatabase().getShallowCommits();
414 if (isCapableOf(GitProtocolConstants.CAPABILITY_SHALLOW)) {
415 sendShallow(shallowCommits, output);
416 } else if (mayHaveShallow) {
417 throw new PackProtocolException(JGitText.get().shallowNotSupported);
418 }
419 output.end();
420 outNeedsEnd = false;
421
422 negotiate(monitor, mayHaveShallow, shallowCommits);
423
424 clearState();
425
426 receivePack(monitor, outputStream);
427 }
428 } catch (CancelledException ce) {
429 close();
430 return;
431 } catch (IOException | RuntimeException err) {
432 close();
433 throw new TransportException(err.getMessage(), err);
434 }
435 }
436
437 private void doFetchV2(ProgressMonitor monitor, Collection<Ref> want,
438 OutputStream outputStream) throws IOException, CancelledException {
439 sideband = true;
440 negotiateBegin();
441
442 pckState.writeString("command=" + GitProtocolConstants.COMMAND_FETCH);
443
444 String agent = UserAgent.get();
445 if (agent != null && isCapableOf(GitProtocolConstants.OPTION_AGENT)) {
446 pckState.writeString(
447 GitProtocolConstants.OPTION_AGENT + '=' + agent);
448 }
449 Set<String> capabilities = new HashSet<>();
450 String advertised = getCapability(GitProtocolConstants.COMMAND_FETCH);
451 if (!StringUtils.isEmptyOrNull(advertised)) {
452 capabilities.addAll(Arrays.asList(advertised.split("\\s+")));
453 }
454
455 pckState.writeDelim();
456 for (String capability : getCapabilitiesV2(capabilities)) {
457 pckState.writeString(capability);
458 }
459
460 if (!sendWants(want, pckState)) {
461
462 return;
463 }
464
465 Set<ObjectId> shallowCommits = local.getObjectDatabase().getShallowCommits();
466 if (capabilities.contains(GitProtocolConstants.CAPABILITY_SHALLOW)) {
467 sendShallow(shallowCommits, pckState);
468 } else if (depth != null || deepenSince != null || !deepenNots.isEmpty()) {
469 throw new PackProtocolException(JGitText.get().shallowNotSupported);
470 }
471
472 outNeedsEnd = false;
473
474 FetchStateV2 fetchState = new FetchStateV2();
475 boolean sentDone = false;
476 for (;;) {
477
478
479 state.writeTo(out, monitor);
480 sentDone = sendNextHaveBatch(fetchState, pckOut, monitor);
481 if (sentDone) {
482 break;
483 }
484 if (readAcknowledgments(fetchState, pckIn, monitor)) {
485
486 break;
487 }
488
489
490
491
492
493
494 }
495 clearState();
496 String line = pckIn.readString();
497
498 if (sentDone && line.startsWith(PACKET_ERR)) {
499 throw new RemoteRepositoryException(uri, line.substring(4));
500 }
501
502 if (GitProtocolConstants.SECTION_SHALLOW_INFO.equals(line)) {
503 line = handleShallowUnshallow(shallowCommits, pckIn);
504 if (!PacketLineIn.isDelimiter(line)) {
505 throw new PackProtocolException(MessageFormat
506 .format(JGitText.get().expectedGot, PACKET_DELIM,
507 line));
508 }
509 line = pckIn.readString();
510 }
511
512
513
514 if (!GitProtocolConstants.SECTION_PACKFILE.equals(line)) {
515 throw new PackProtocolException(
516 MessageFormat.format(JGitText.get().expectedGot,
517 GitProtocolConstants.SECTION_PACKFILE, line));
518 }
519 receivePack(monitor, outputStream);
520 }
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539 private boolean sendNextHaveBatch(FetchStateV2 fetchState,
540 PacketLineOut output, ProgressMonitor monitor)
541 throws IOException, CancelledException {
542 long n = 0;
543 while (n < fetchState.havesToSend) {
544 final RevCommit c = walk.next();
545 if (c == null) {
546 break;
547 }
548 output.writeString(PACKET_HAVE + c.getId().name() + '\n');
549 n++;
550 if (n % 10 == 0 && monitor.isCancelled()) {
551 throw new CancelledException();
552 }
553 }
554 fetchState.havesTotal += n;
555 if (n == 0
556 || (fetchState.hadAcks
557 && fetchState.havesWithoutAck > MAX_HAVES)
558 || fetchState.havesTotal > maxHaves) {
559 output.writeString(PACKET_DONE + '\n');
560 output.end();
561 return true;
562 }
563
564
565
566 fetchState.havesWithoutAck += n;
567 output.end();
568 fetchState.incHavesToSend(statelessRPC);
569 return false;
570 }
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589 private boolean readAcknowledgments(FetchStateV2 fetchState,
590 PacketLineIn input, ProgressMonitor monitor)
591 throws IOException, CancelledException {
592 String line = input.readString();
593 if (!GitProtocolConstants.SECTION_ACKNOWLEDGMENTS.equals(line)) {
594 throw new PackProtocolException(MessageFormat.format(
595 JGitText.get().expectedGot,
596 GitProtocolConstants.SECTION_ACKNOWLEDGMENTS, line));
597 }
598 MutableObjectId returnedId = new MutableObjectId();
599 line = input.readString();
600 boolean gotReady = false;
601 long n = 0;
602 while (!PacketLineIn.isEnd(line) && !PacketLineIn.isDelimiter(line)) {
603 AckNackResult ack = PacketLineIn.parseACKv2(line, returnedId);
604
605
606
607 if (!gotReady) {
608 if (ack == AckNackResult.ACK_COMMON) {
609
610 markCommon(walk.parseAny(returnedId), ack, true);
611 fetchState.havesWithoutAck = 0;
612 fetchState.hadAcks = true;
613 } else if (ack == AckNackResult.ACK_READY) {
614 gotReady = true;
615 }
616 }
617 n++;
618 if (n % 10 == 0 && monitor.isCancelled()) {
619 throw new CancelledException();
620 }
621 line = input.readString();
622 }
623 if (gotReady) {
624 if (!PacketLineIn.isDelimiter(line)) {
625 throw new PackProtocolException(MessageFormat
626 .format(JGitText.get().expectedGot, PACKET_DELIM,
627 line));
628 }
629 } else if (!PacketLineIn.isEnd(line)) {
630 throw new PackProtocolException(MessageFormat
631 .format(JGitText.get().expectedGot, PACKET_END, line));
632 }
633 return gotReady;
634 }
635
636
637 @Override
638 public void close() {
639 if (walk != null)
640 walk.close();
641 super.close();
642 }
643
644 FetchConfig getFetchConfig() {
645 return local.getConfig().get(FetchConfig::new);
646 }
647
648 private int maxTimeWanted(Collection<Ref> wants) {
649 int maxTime = 0;
650 for (Ref r : wants) {
651 try {
652 final RevObject obj = walk.parseAny(r.getObjectId());
653 if (obj instanceof RevCommit) {
654 final int cTime = ((RevCommit) obj).getCommitTime();
655 if (maxTime < cTime)
656 maxTime = cTime;
657 }
658 } catch (IOException error) {
659
660 }
661 }
662 return maxTime;
663 }
664
665 private void markReachable(Set<ObjectId> have, int maxTime)
666 throws IOException {
667 for (Ref r : local.getRefDatabase().getRefs()) {
668 ObjectId id = r.getPeeledObjectId();
669 if (id == null)
670 id = r.getObjectId();
671 if (id == null)
672 continue;
673 parseReachable(id);
674 }
675
676 for (ObjectId id : local.getAdditionalHaves())
677 parseReachable(id);
678
679 for (ObjectId id : have)
680 parseReachable(id);
681
682 if (maxTime > 0) {
683
684
685
686
687 final Date maxWhen = new Date(maxTime * 1000L);
688 walk.sort(RevSort.COMMIT_TIME_DESC);
689 walk.markStart(reachableCommits);
690 walk.setRevFilter(CommitTimeRevFilter.after(maxWhen));
691 for (;;) {
692 final RevCommit c = walk.next();
693 if (c == null)
694 break;
695 if (c.has(ADVERTISED) && !c.has(COMMON)) {
696
697
698
699 c.add(COMMON);
700 c.carry(COMMON);
701 reachableCommits.add(c);
702 }
703 }
704 }
705 }
706
707 private void parseReachable(ObjectId id) {
708 try {
709 RevCommit o = walk.parseCommit(id);
710 if (!o.has(REACHABLE)) {
711 o.add(REACHABLE);
712 reachableCommits.add(o);
713 }
714 } catch (IOException readError) {
715
716 }
717 }
718
719 private boolean sendWants(Collection<Ref> want, PacketLineOut p)
720 throws IOException {
721 boolean first = true;
722 for (Ref r : want) {
723 ObjectId objectId = r.getObjectId();
724 if (objectId == null) {
725 continue;
726 }
727
728 if (transport.getDepth() == null) {
729 try {
730 if (walk.parseAny(objectId).has(REACHABLE)) {
731
732
733
734 continue;
735 }
736 } catch (IOException err) {
737
738
739 }
740 }
741
742 final StringBuilder line = new StringBuilder(46);
743 line.append(PACKET_WANT).append(objectId.name());
744 if (first && TransferConfig.ProtocolVersion.V0
745 .equals(getProtocolVersion())) {
746 line.append(enableCapabilities());
747 }
748 first = false;
749 line.append('\n');
750 p.writeString(line.toString());
751 }
752 if (first) {
753 return false;
754 }
755 if (!filterSpec.isNoOp()) {
756 p.writeString(filterSpec.filterLine());
757 }
758 return true;
759 }
760
761 private Set<String> getCapabilitiesV2(Set<String> advertisedCapabilities)
762 throws TransportException {
763 Set<String> capabilities = new LinkedHashSet<>();
764
765 if (noProgress) {
766 capabilities.add(OPTION_NO_PROGRESS);
767 }
768 if (includeTags) {
769 capabilities.add(OPTION_INCLUDE_TAG);
770 }
771 if (allowOfsDelta) {
772 capabilities.add(OPTION_OFS_DELTA);
773 }
774 if (thinPack) {
775 capabilities.add(OPTION_THIN_PACK);
776 }
777 if (!filterSpec.isNoOp()
778 && !advertisedCapabilities.contains(OPTION_FILTER)) {
779 throw new PackProtocolException(uri,
780 JGitText.get().filterRequiresCapability);
781 }
782
783 return capabilities;
784 }
785
786 private String enableCapabilities() throws TransportException {
787 final StringBuilder line = new StringBuilder();
788 if (noProgress)
789 wantCapability(line, OPTION_NO_PROGRESS);
790 if (includeTags)
791 includeTags = wantCapability(line, OPTION_INCLUDE_TAG);
792 if (allowOfsDelta)
793 wantCapability(line, OPTION_OFS_DELTA);
794
795 if (wantCapability(line, OPTION_MULTI_ACK_DETAILED)) {
796 multiAck = MultiAck.DETAILED;
797 if (statelessRPC)
798 noDone = wantCapability(line, OPTION_NO_DONE);
799 } else if (wantCapability(line, OPTION_MULTI_ACK))
800 multiAck = MultiAck.CONTINUE;
801 else
802 multiAck = MultiAck.OFF;
803
804 if (thinPack)
805 thinPack = wantCapability(line, OPTION_THIN_PACK);
806 if (wantCapability(line, OPTION_SIDE_BAND_64K))
807 sideband = true;
808 else if (wantCapability(line, OPTION_SIDE_BAND))
809 sideband = true;
810
811 if (statelessRPC && multiAck != MultiAck.DETAILED) {
812
813
814
815
816 throw new PackProtocolException(uri, MessageFormat.format(
817 JGitText.get().statelessRPCRequiresOptionToBeEnabled,
818 OPTION_MULTI_ACK_DETAILED));
819 }
820
821 if (!filterSpec.isNoOp() && !wantCapability(line, OPTION_FILTER)) {
822 throw new PackProtocolException(uri,
823 JGitText.get().filterRequiresCapability);
824 }
825
826 addUserAgentCapability(line);
827 return line.toString();
828 }
829
830 private void negotiate(ProgressMonitor monitor, boolean mayHaveShallow, Set<ObjectId> shallowCommits)
831 throws IOException, CancelledException {
832 final MutableObjectId ackId = new MutableObjectId();
833 int resultsPending = 0;
834 int havesSent = 0;
835 int havesSinceLastContinue = 0;
836 boolean receivedContinue = false;
837 boolean receivedAck = false;
838 boolean receivedReady = false;
839
840 if (statelessRPC) {
841 state.writeTo(out, null);
842 }
843
844 negotiateBegin();
845 SEND_HAVES: for (;;) {
846 final RevCommit c = walk.next();
847 if (c == null) {
848 break SEND_HAVES;
849 }
850
851 ObjectId o = c.getId();
852 pckOut.writeString(PACKET_HAVE + o.name() + '\n');
853 havesSent++;
854 havesSinceLastContinue++;
855
856 if ((31 & havesSent) != 0) {
857
858
859
860
861 continue;
862 }
863
864 if (monitor.isCancelled()) {
865 throw new CancelledException();
866 }
867
868 pckOut.end();
869 resultsPending++;
870
871 if (havesSent == 32 && !statelessRPC) {
872
873
874
875
876
877 continue;
878 }
879
880 READ_RESULT: for (;;) {
881 final AckNackResult anr = pckIn.readACK(ackId);
882 switch (anr) {
883 case NAK:
884
885
886
887 resultsPending--;
888 break READ_RESULT;
889
890 case ACK:
891
892
893
894
895 multiAck = MultiAck.OFF;
896 resultsPending = 0;
897 receivedAck = true;
898 if (statelessRPC) {
899 state.writeTo(out, null);
900 }
901 break SEND_HAVES;
902
903 case ACK_CONTINUE:
904 case ACK_COMMON:
905 case ACK_READY:
906
907
908
909
910
911 markCommon(walk.parseAny(ackId), anr, statelessRPC);
912 receivedAck = true;
913 receivedContinue = true;
914 havesSinceLastContinue = 0;
915 if (anr == AckNackResult.ACK_READY) {
916 receivedReady = true;
917 }
918 break;
919 }
920
921 if (monitor.isCancelled()) {
922 throw new CancelledException();
923 }
924 }
925
926 if (noDone && receivedReady) {
927 break SEND_HAVES;
928 }
929 if (statelessRPC) {
930 state.writeTo(out, null);
931 }
932
933 if ((receivedContinue && havesSinceLastContinue > MAX_HAVES)
934 || havesSent >= maxHaves) {
935
936
937
938
939
940 break SEND_HAVES;
941 }
942 }
943
944
945
946 if (monitor.isCancelled()) {
947 throw new CancelledException();
948 }
949
950 if (!receivedReady || !noDone) {
951
952
953
954
955 pckOut.writeString(PACKET_DONE + '\n');
956 pckOut.flush();
957 }
958
959 if (!receivedAck) {
960
961
962
963
964 multiAck = MultiAck.OFF;
965 resultsPending++;
966 }
967
968 if (mayHaveShallow) {
969 String line = handleShallowUnshallow(shallowCommits, pckIn);
970 if (!PacketLineIn.isEnd(line)) {
971 throw new PackProtocolException(MessageFormat
972 .format(JGitText.get().expectedGot, PACKET_END, line));
973 }
974 }
975
976 READ_RESULT: while (resultsPending > 0 || multiAck != MultiAck.OFF) {
977 final AckNackResult anr = pckIn.readACK(ackId);
978 resultsPending--;
979 switch (anr) {
980 case NAK:
981
982
983
984 break;
985
986 case ACK:
987
988
989
990 break READ_RESULT;
991
992 case ACK_CONTINUE:
993 case ACK_COMMON:
994 case ACK_READY:
995
996
997 multiAck = MultiAck.CONTINUE;
998 break;
999 }
1000
1001 if (monitor.isCancelled()) {
1002 throw new CancelledException();
1003 }
1004 }
1005 }
1006
1007 private void negotiateBegin() throws IOException {
1008 walk.resetRetain(REACHABLE, ADVERTISED);
1009 walk.markStart(reachableCommits);
1010 walk.sort(RevSort.COMMIT_TIME_DESC);
1011 walk.setRevFilter(new RevFilter() {
1012 @Override
1013 public RevFilter clone() {
1014 return this;
1015 }
1016
1017 @Override
1018 public boolean include(RevWalk walker, RevCommit c) {
1019 final boolean remoteKnowsIsCommon = c.has(COMMON);
1020 if (c.has(ADVERTISED)) {
1021
1022
1023
1024
1025
1026 c.add(COMMON);
1027 }
1028 return !remoteKnowsIsCommon;
1029 }
1030
1031 @Override
1032 public boolean requiresCommitBody() {
1033 return false;
1034 }
1035 });
1036 }
1037
1038 private void markRefsAdvertised() {
1039 for (Ref r : getRefs()) {
1040 markAdvertised(r.getObjectId());
1041 if (r.getPeeledObjectId() != null)
1042 markAdvertised(r.getPeeledObjectId());
1043 }
1044 }
1045
1046 private void markAdvertised(AnyObjectId id) {
1047 try {
1048 walk.parseAny(id).add(ADVERTISED);
1049 } catch (IOException readError) {
1050
1051 }
1052 }
1053
1054 private void markCommon(RevObject obj, AckNackResult anr, boolean useState)
1055 throws IOException {
1056 if (useState && anr == AckNackResult.ACK_COMMON && !obj.has(STATE)) {
1057 pckState.writeString(PACKET_HAVE + obj.name() + '\n');
1058 obj.add(STATE);
1059 }
1060 obj.add(COMMON);
1061 if (obj instanceof RevCommit)
1062 ((RevCommit) obj).carry(COMMON);
1063 }
1064
1065 private void receivePack(final ProgressMonitor monitor,
1066 OutputStream outputStream) throws IOException {
1067 onReceivePack();
1068 InputStream input = in;
1069 SideBandInputStream sidebandIn = null;
1070 if (sideband) {
1071 sidebandIn = new SideBandInputStream(input, monitor,
1072 getMessageWriter(), outputStream);
1073 input = sidebandIn;
1074 }
1075
1076 try (ObjectInserter ins = local.newObjectInserter()) {
1077 PackParser parser = ins.newPackParser(input);
1078 parser.setAllowThin(thinPack);
1079 parser.setObjectChecker(transport.getObjectChecker());
1080 parser.setLockMessage(lockMessage);
1081 packLock = parser.parse(monitor);
1082 ins.flush();
1083 } finally {
1084 if (sidebandIn != null) {
1085 sidebandIn.drainMessages();
1086 }
1087 }
1088 }
1089
1090 private void sendShallow(Set<ObjectId> shallowCommits, PacketLineOut output)
1091 throws IOException {
1092 for (ObjectId shallowCommit : shallowCommits) {
1093 output.writeString(PACKET_SHALLOW + shallowCommit.name());
1094 }
1095
1096 if (depth != null) {
1097 output.writeString(PACKET_DEEPEN + depth);
1098 }
1099
1100 if (deepenSince != null) {
1101 output.writeString(
1102 PACKET_DEEPEN_SINCE + deepenSince.getEpochSecond());
1103 }
1104
1105 if (deepenNots != null) {
1106 for (String deepenNotRef : deepenNots) {
1107 output.writeString(PACKET_DEEPEN_NOT + deepenNotRef);
1108 }
1109 }
1110 }
1111
1112 private String handleShallowUnshallow(
1113 Set<ObjectId> advertisedShallowCommits, PacketLineIn input)
1114 throws IOException {
1115 String line = input.readString();
1116 ObjectDatabase objectDatabase = local.getObjectDatabase();
1117 HashSet<ObjectId> newShallowCommits = new HashSet<>(
1118 advertisedShallowCommits);
1119 while (!PacketLineIn.isDelimiter(line) && !PacketLineIn.isEnd(line)) {
1120 if (line.startsWith(PACKET_SHALLOW)) {
1121 newShallowCommits.add(ObjectId
1122 .fromString(line.substring(PACKET_SHALLOW.length())));
1123 } else if (line.startsWith(PACKET_UNSHALLOW)) {
1124 ObjectId unshallow = ObjectId
1125 .fromString(line.substring(PACKET_UNSHALLOW.length()));
1126 if (!advertisedShallowCommits.contains(unshallow)) {
1127 throw new PackProtocolException(MessageFormat.format(
1128 JGitText.get().notShallowedUnshallow,
1129 unshallow.name()));
1130 }
1131 newShallowCommits.remove(unshallow);
1132 }
1133 line = input.readString();
1134 }
1135 objectDatabase.setShallowCommits(newShallowCommits);
1136 return line;
1137 }
1138
1139
1140
1141
1142
1143
1144
1145
1146 protected void onReceivePack() {
1147
1148 }
1149
1150 private static class CancelledException extends Exception {
1151 private static final long serialVersionUID = 1L;
1152 }
1153
1154 private static class FetchStateV2 {
1155
1156 long havesToSend = 32;
1157
1158 long havesTotal;
1159
1160
1161 boolean hadAcks;
1162
1163
1164
1165 long havesWithoutAck;
1166
1167 void incHavesToSend(boolean statelessRPC) {
1168 if (statelessRPC) {
1169
1170 if (havesToSend < 16384) {
1171 havesToSend *= 2;
1172 } else {
1173 havesToSend = havesToSend * 11 / 10;
1174 }
1175 } else {
1176 havesToSend += 32;
1177 }
1178 }
1179 }
1180 }