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