1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 package org.eclipse.jgit.transport;
47
48 import static org.eclipse.jgit.lib.RefDatabase.ALL;
49
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.io.OutputStream;
53 import java.text.MessageFormat;
54 import java.util.Collection;
55 import java.util.Collections;
56 import java.util.Date;
57 import java.util.Map;
58 import java.util.Set;
59
60 import org.eclipse.jgit.errors.PackProtocolException;
61 import org.eclipse.jgit.errors.TransportException;
62 import org.eclipse.jgit.internal.JGitText;
63 import org.eclipse.jgit.internal.storage.file.PackLock;
64 import org.eclipse.jgit.lib.AnyObjectId;
65 import org.eclipse.jgit.lib.Config;
66 import org.eclipse.jgit.lib.Constants;
67 import org.eclipse.jgit.lib.MutableObjectId;
68 import org.eclipse.jgit.lib.NullProgressMonitor;
69 import org.eclipse.jgit.lib.ObjectId;
70 import org.eclipse.jgit.lib.ObjectInserter;
71 import org.eclipse.jgit.lib.ProgressMonitor;
72 import org.eclipse.jgit.lib.Ref;
73 import org.eclipse.jgit.revwalk.RevCommit;
74 import org.eclipse.jgit.revwalk.RevCommitList;
75 import org.eclipse.jgit.revwalk.RevFlag;
76 import org.eclipse.jgit.revwalk.RevObject;
77 import org.eclipse.jgit.revwalk.RevSort;
78 import org.eclipse.jgit.revwalk.RevWalk;
79 import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter;
80 import org.eclipse.jgit.revwalk.filter.RevFilter;
81 import org.eclipse.jgit.transport.GitProtocolConstants.MultiAck;
82 import org.eclipse.jgit.transport.PacketLineIn.AckNackResult;
83 import org.eclipse.jgit.util.TemporaryBuffer;
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107 public abstract class BasePackFetchConnection extends BasePackConnection
108 implements FetchConnection {
109
110
111
112
113
114
115
116 private static final int MAX_HAVES = 256;
117
118
119
120
121
122
123
124
125
126 protected static final int MIN_CLIENT_BUFFER = 2 * 32 * 46 + 8;
127
128
129
130
131
132 public static final String OPTION_INCLUDE_TAG = GitProtocolConstants.OPTION_INCLUDE_TAG;
133
134
135
136
137
138 public static final String OPTION_MULTI_ACK = GitProtocolConstants.OPTION_MULTI_ACK;
139
140
141
142
143
144 public static final String OPTION_MULTI_ACK_DETAILED = GitProtocolConstants.OPTION_MULTI_ACK_DETAILED;
145
146
147
148
149
150 public static final String OPTION_THIN_PACK = GitProtocolConstants.OPTION_THIN_PACK;
151
152
153
154
155
156 public static final String OPTION_SIDE_BAND = GitProtocolConstants.OPTION_SIDE_BAND;
157
158
159
160
161
162 public static final String OPTION_SIDE_BAND_64K = GitProtocolConstants.OPTION_SIDE_BAND_64K;
163
164
165
166
167
168 public static final String OPTION_OFS_DELTA = GitProtocolConstants.OPTION_OFS_DELTA;
169
170
171
172
173
174 public static final String OPTION_SHALLOW = GitProtocolConstants.OPTION_SHALLOW;
175
176
177
178
179
180 public static final String OPTION_NO_PROGRESS = GitProtocolConstants.OPTION_NO_PROGRESS;
181
182
183
184
185
186 public static final String OPTION_NO_DONE = GitProtocolConstants.OPTION_NO_DONE;
187
188
189
190
191
192
193 public static final String OPTION_ALLOW_TIP_SHA1_IN_WANT = GitProtocolConstants.OPTION_ALLOW_TIP_SHA1_IN_WANT;
194
195
196
197
198
199
200 public static final String OPTION_ALLOW_REACHABLE_SHA1_IN_WANT = GitProtocolConstants.OPTION_ALLOW_REACHABLE_SHA1_IN_WANT;
201
202 private final RevWalk walk;
203
204
205 private RevCommitList<RevCommit> reachableCommits;
206
207
208 final RevFlag REACHABLE;
209
210
211 final RevFlag COMMON;
212
213
214 private final RevFlag STATE;
215
216
217 final RevFlag ADVERTISED;
218
219 private MultiAck multiAck = MultiAck.OFF;
220
221 private boolean thinPack;
222
223 private boolean sideband;
224
225 private boolean includeTags;
226
227 private boolean allowOfsDelta;
228
229 private boolean noDone;
230
231 private boolean noProgress;
232
233 private String lockMessage;
234
235 private PackLock packLock;
236
237
238 private TemporaryBuffer.Heap state;
239
240 private PacketLineOut pckState;
241
242
243
244
245
246
247
248 public BasePackFetchConnection(final PackTransport packTransport) {
249 super(packTransport);
250
251 if (local != null) {
252 final FetchConfig cfg = local.getConfig().get(FetchConfig::new);
253 allowOfsDelta = cfg.allowOfsDelta;
254 } else {
255 allowOfsDelta = true;
256 }
257 includeTags = transport.getTagOpt() != TagOpt.NO_TAGS;
258 thinPack = transport.isFetchThin();
259
260 if (local != null) {
261 walk = new RevWalk(local);
262 reachableCommits = new RevCommitList<>();
263 REACHABLE = walk.newFlag("REACHABLE");
264 COMMON = walk.newFlag("COMMON");
265 STATE = walk.newFlag("STATE");
266 ADVERTISED = walk.newFlag("ADVERTISED");
267
268 walk.carry(COMMON);
269 walk.carry(REACHABLE);
270 walk.carry(ADVERTISED);
271 } else {
272 walk = null;
273 REACHABLE = null;
274 COMMON = null;
275 STATE = null;
276 ADVERTISED = null;
277 }
278 }
279
280 private static class FetchConfig {
281 final boolean allowOfsDelta;
282
283 FetchConfig(final Config c) {
284 allowOfsDelta = c.getBoolean("repack", "usedeltabaseoffset", true);
285 }
286 }
287
288
289 @Override
290 public final void fetch(final ProgressMonitor monitor,
291 final Collection<Ref> want, final Set<ObjectId> have)
292 throws TransportException {
293 fetch(monitor, want, have, null);
294 }
295
296
297 @Override
298 public final void fetch(final ProgressMonitor monitor,
299 final Collection<Ref> want, final Set<ObjectId> have,
300 OutputStream outputStream) throws TransportException {
301 markStartedOperation();
302 doFetch(monitor, want, have, outputStream);
303 }
304
305
306 @Override
307 public boolean didFetchIncludeTags() {
308 return false;
309 }
310
311
312 @Override
313 public boolean didFetchTestConnectivity() {
314 return false;
315 }
316
317
318 @Override
319 public void setPackLockMessage(final String message) {
320 lockMessage = message;
321 }
322
323
324 @Override
325 public Collection<PackLock> getPackLocks() {
326 if (packLock != null)
327 return Collections.singleton(packLock);
328 return Collections.<PackLock> emptyList();
329 }
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350 protected void doFetch(final ProgressMonitor monitor,
351 final Collection<Ref> want, final Set<ObjectId> have,
352 OutputStream outputStream) throws TransportException {
353 try {
354 noProgress = monitor == NullProgressMonitor.INSTANCE;
355
356 markRefsAdvertised();
357 markReachable(have, maxTimeWanted(want));
358
359 if (statelessRPC) {
360 state = new TemporaryBuffer.Heap(Integer.MAX_VALUE);
361 pckState = new PacketLineOut(state);
362 }
363
364 if (sendWants(want)) {
365 negotiate(monitor);
366
367 walk.dispose();
368 reachableCommits = null;
369 state = null;
370 pckState = null;
371
372 receivePack(monitor, outputStream);
373 }
374 } catch (CancelledException ce) {
375 close();
376 return;
377 } catch (IOException err) {
378 close();
379 throw new TransportException(err.getMessage(), err);
380 } catch (RuntimeException err) {
381 close();
382 throw new TransportException(err.getMessage(), err);
383 }
384 }
385
386
387 @Override
388 public void close() {
389 if (walk != null)
390 walk.close();
391 super.close();
392 }
393
394 private int maxTimeWanted(final Collection<Ref> wants) {
395 int maxTime = 0;
396 for (final Ref r : wants) {
397 try {
398 final RevObject obj = walk.parseAny(r.getObjectId());
399 if (obj instanceof RevCommit) {
400 final int cTime = ((RevCommit) obj).getCommitTime();
401 if (maxTime < cTime)
402 maxTime = cTime;
403 }
404 } catch (IOException error) {
405
406 }
407 }
408 return maxTime;
409 }
410
411 private void markReachable(final Set<ObjectId> have, final int maxTime)
412 throws IOException {
413 Map<String, Ref> refs = local.getRefDatabase().getRefs(ALL);
414 for (final Ref r : refs.values()) {
415 ObjectId id = r.getPeeledObjectId();
416 if (id == null)
417 id = r.getObjectId();
418 if (id == null)
419 continue;
420 parseReachable(id);
421 }
422
423 for (ObjectId id : local.getAdditionalHaves())
424 parseReachable(id);
425
426 for (ObjectId id : have)
427 parseReachable(id);
428
429 if (maxTime > 0) {
430
431
432
433
434 final Date maxWhen = new Date(maxTime * 1000L);
435 walk.sort(RevSort.COMMIT_TIME_DESC);
436 walk.markStart(reachableCommits);
437 walk.setRevFilter(CommitTimeRevFilter.after(maxWhen));
438 for (;;) {
439 final RevCommit c = walk.next();
440 if (c == null)
441 break;
442 if (c.has(ADVERTISED) && !c.has(COMMON)) {
443
444
445
446 c.add(COMMON);
447 c.carry(COMMON);
448 reachableCommits.add(c);
449 }
450 }
451 }
452 }
453
454 private void parseReachable(ObjectId id) {
455 try {
456 RevCommit o = walk.parseCommit(id);
457 if (!o.has(REACHABLE)) {
458 o.add(REACHABLE);
459 reachableCommits.add(o);
460 }
461 } catch (IOException readError) {
462
463 }
464 }
465
466 private boolean sendWants(final Collection<Ref> want) throws IOException {
467 final PacketLineOut p = statelessRPC ? pckState : pckOut;
468 boolean first = true;
469 for (final Ref r : want) {
470 ObjectId objectId = r.getObjectId();
471 if (objectId == null) {
472 continue;
473 }
474 try {
475 if (walk.parseAny(objectId).has(REACHABLE)) {
476
477
478
479 continue;
480 }
481 } catch (IOException err) {
482
483
484 }
485
486 final StringBuilder line = new StringBuilder(46);
487 line.append("want ");
488 line.append(objectId.name());
489 if (first) {
490 line.append(enableCapabilities());
491 first = false;
492 }
493 line.append('\n');
494 p.writeString(line.toString());
495 }
496 if (first)
497 return false;
498 p.end();
499 outNeedsEnd = false;
500 return true;
501 }
502
503 private String enableCapabilities() throws TransportException {
504 final StringBuilder line = new StringBuilder();
505 if (noProgress)
506 wantCapability(line, OPTION_NO_PROGRESS);
507 if (includeTags)
508 includeTags = wantCapability(line, OPTION_INCLUDE_TAG);
509 if (allowOfsDelta)
510 wantCapability(line, OPTION_OFS_DELTA);
511
512 if (wantCapability(line, OPTION_MULTI_ACK_DETAILED)) {
513 multiAck = MultiAck.DETAILED;
514 if (statelessRPC)
515 noDone = wantCapability(line, OPTION_NO_DONE);
516 } else if (wantCapability(line, OPTION_MULTI_ACK))
517 multiAck = MultiAck.CONTINUE;
518 else
519 multiAck = MultiAck.OFF;
520
521 if (thinPack)
522 thinPack = wantCapability(line, OPTION_THIN_PACK);
523 if (wantCapability(line, OPTION_SIDE_BAND_64K))
524 sideband = true;
525 else if (wantCapability(line, OPTION_SIDE_BAND))
526 sideband = true;
527
528 if (statelessRPC && multiAck != MultiAck.DETAILED) {
529
530
531
532
533 throw new PackProtocolException(uri, MessageFormat.format(
534 JGitText.get().statelessRPCRequiresOptionToBeEnabled,
535 OPTION_MULTI_ACK_DETAILED));
536 }
537
538 addUserAgentCapability(line);
539 return line.toString();
540 }
541
542 private void negotiate(final ProgressMonitor monitor) throws IOException,
543 CancelledException {
544 final MutableObjectId ackId = new MutableObjectId();
545 int resultsPending = 0;
546 int havesSent = 0;
547 int havesSinceLastContinue = 0;
548 boolean receivedContinue = false;
549 boolean receivedAck = false;
550 boolean receivedReady = false;
551
552 if (statelessRPC)
553 state.writeTo(out, null);
554
555 negotiateBegin();
556 SEND_HAVES: for (;;) {
557 final RevCommit c = walk.next();
558 if (c == null)
559 break SEND_HAVES;
560
561 pckOut.writeString("have " + c.getId().name() + "\n");
562 havesSent++;
563 havesSinceLastContinue++;
564
565 if ((31 & havesSent) != 0) {
566
567
568
569
570 continue;
571 }
572
573 if (monitor.isCancelled())
574 throw new CancelledException();
575
576 pckOut.end();
577 resultsPending++;
578
579 if (havesSent == 32 && !statelessRPC) {
580
581
582
583
584
585 continue;
586 }
587
588 READ_RESULT: for (;;) {
589 final AckNackResult anr = pckIn.readACK(ackId);
590 switch (anr) {
591 case NAK:
592
593
594
595 resultsPending--;
596 break READ_RESULT;
597
598 case ACK:
599
600
601
602
603 multiAck = MultiAck.OFF;
604 resultsPending = 0;
605 receivedAck = true;
606 if (statelessRPC)
607 state.writeTo(out, null);
608 break SEND_HAVES;
609
610 case ACK_CONTINUE:
611 case ACK_COMMON:
612 case ACK_READY:
613
614
615
616
617
618 markCommon(walk.parseAny(ackId), anr);
619 receivedAck = true;
620 receivedContinue = true;
621 havesSinceLastContinue = 0;
622 if (anr == AckNackResult.ACK_READY)
623 receivedReady = true;
624 break;
625 }
626
627 if (monitor.isCancelled())
628 throw new CancelledException();
629 }
630
631 if (noDone & receivedReady)
632 break SEND_HAVES;
633 if (statelessRPC)
634 state.writeTo(out, null);
635
636 if (receivedContinue && havesSinceLastContinue > MAX_HAVES) {
637
638
639
640
641
642 break SEND_HAVES;
643 }
644 }
645
646
647
648 if (monitor.isCancelled())
649 throw new CancelledException();
650
651 if (!receivedReady || !noDone) {
652
653
654
655
656 pckOut.writeString("done\n");
657 pckOut.flush();
658 }
659
660 if (!receivedAck) {
661
662
663
664
665 multiAck = MultiAck.OFF;
666 resultsPending++;
667 }
668
669 READ_RESULT: while (resultsPending > 0 || multiAck != MultiAck.OFF) {
670 final AckNackResult anr = pckIn.readACK(ackId);
671 resultsPending--;
672 switch (anr) {
673 case NAK:
674
675
676
677 break;
678
679 case ACK:
680
681
682
683 break READ_RESULT;
684
685 case ACK_CONTINUE:
686 case ACK_COMMON:
687 case ACK_READY:
688
689
690 multiAck = MultiAck.CONTINUE;
691 break;
692 }
693
694 if (monitor.isCancelled())
695 throw new CancelledException();
696 }
697 }
698
699 private void negotiateBegin() throws IOException {
700 walk.resetRetain(REACHABLE, ADVERTISED);
701 walk.markStart(reachableCommits);
702 walk.sort(RevSort.COMMIT_TIME_DESC);
703 walk.setRevFilter(new RevFilter() {
704 @Override
705 public RevFilter clone() {
706 return this;
707 }
708
709 @Override
710 public boolean include(final RevWalk walker, final RevCommit c) {
711 final boolean remoteKnowsIsCommon = c.has(COMMON);
712 if (c.has(ADVERTISED)) {
713
714
715
716
717
718 c.add(COMMON);
719 }
720 return !remoteKnowsIsCommon;
721 }
722
723 @Override
724 public boolean requiresCommitBody() {
725 return false;
726 }
727 });
728 }
729
730 private void markRefsAdvertised() {
731 for (final Ref r : getRefs()) {
732 markAdvertised(r.getObjectId());
733 if (r.getPeeledObjectId() != null)
734 markAdvertised(r.getPeeledObjectId());
735 }
736 }
737
738 private void markAdvertised(final AnyObjectId id) {
739 try {
740 walk.parseAny(id).add(ADVERTISED);
741 } catch (IOException readError) {
742
743 }
744 }
745
746 private void markCommon(final RevObject obj, final AckNackResult anr)
747 throws IOException {
748 if (statelessRPC && anr == AckNackResult.ACK_COMMON && !obj.has(STATE)) {
749 StringBuilder s;
750
751 s = new StringBuilder(6 + Constants.OBJECT_ID_STRING_LENGTH);
752 s.append("have ");
753 s.append(obj.name());
754 s.append('\n');
755 pckState.writeString(s.toString());
756 obj.add(STATE);
757 }
758 obj.add(COMMON);
759 if (obj instanceof RevCommit)
760 ((RevCommit) obj).carry(COMMON);
761 }
762
763 private void receivePack(final ProgressMonitor monitor,
764 OutputStream outputStream) throws IOException {
765 onReceivePack();
766 InputStream input = in;
767 if (sideband)
768 input = new SideBandInputStream(input, monitor, getMessageWriter(),
769 outputStream);
770
771 try (ObjectInserter ins = local.newObjectInserter()) {
772 PackParser parser = ins.newPackParser(input);
773 parser.setAllowThin(thinPack);
774 parser.setObjectChecker(transport.getObjectChecker());
775 parser.setLockMessage(lockMessage);
776 packLock = parser.parse(monitor);
777 ins.flush();
778 }
779 }
780
781
782
783
784
785
786
787
788 protected void onReceivePack() {
789
790 }
791
792 private static class CancelledException extends Exception {
793 private static final long serialVersionUID = 1L;
794 }
795 }