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.transport.GitProtocolConstants.MultiAck;
75 import org.eclipse.jgit.revwalk.RevCommit;
76 import org.eclipse.jgit.revwalk.RevCommitList;
77 import org.eclipse.jgit.revwalk.RevFlag;
78 import org.eclipse.jgit.revwalk.RevObject;
79 import org.eclipse.jgit.revwalk.RevSort;
80 import org.eclipse.jgit.revwalk.RevWalk;
81 import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter;
82 import org.eclipse.jgit.revwalk.filter.RevFilter;
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<RevCommit>();
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 public FetchConfig parse(final Config cfg) {
284 return new FetchConfig(cfg);
285 }
286 };
287
288 final boolean allowOfsDelta;
289
290 FetchConfig(final Config c) {
291 allowOfsDelta = c.getBoolean("repack", "usedeltabaseoffset", true);
292 }
293 }
294
295 public final void fetch(final ProgressMonitor monitor,
296 final Collection<Ref> want, final Set<ObjectId> have)
297 throws TransportException {
298 fetch(monitor, want, have, null);
299 }
300
301
302
303
304 public final void fetch(final ProgressMonitor monitor,
305 final Collection<Ref> want, final Set<ObjectId> have,
306 OutputStream outputStream) throws TransportException {
307 markStartedOperation();
308 doFetch(monitor, want, have, outputStream);
309 }
310
311 public boolean didFetchIncludeTags() {
312 return false;
313 }
314
315 public boolean didFetchTestConnectivity() {
316 return false;
317 }
318
319 public void setPackLockMessage(final String message) {
320 lockMessage = message;
321 }
322
323 public Collection<PackLock> getPackLocks() {
324 if (packLock != null)
325 return Collections.singleton(packLock);
326 return Collections.<PackLock> emptyList();
327 }
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348 protected void doFetch(final ProgressMonitor monitor,
349 final Collection<Ref> want, final Set<ObjectId> have,
350 OutputStream outputStream) throws TransportException {
351 try {
352 noProgress = monitor == NullProgressMonitor.INSTANCE;
353
354 markRefsAdvertised();
355 markReachable(have, maxTimeWanted(want));
356
357 if (statelessRPC) {
358 state = new TemporaryBuffer.Heap(Integer.MAX_VALUE);
359 pckState = new PacketLineOut(state);
360 }
361
362 if (sendWants(want)) {
363 negotiate(monitor);
364
365 walk.dispose();
366 reachableCommits = null;
367 state = null;
368 pckState = null;
369
370 receivePack(monitor, outputStream);
371 }
372 } catch (CancelledException ce) {
373 close();
374 return;
375 } catch (IOException err) {
376 close();
377 throw new TransportException(err.getMessage(), err);
378 } catch (RuntimeException err) {
379 close();
380 throw new TransportException(err.getMessage(), err);
381 }
382 }
383
384 @Override
385 public void close() {
386 if (walk != null)
387 walk.close();
388 super.close();
389 }
390
391 private int maxTimeWanted(final Collection<Ref> wants) {
392 int maxTime = 0;
393 for (final Ref r : wants) {
394 try {
395 final RevObject obj = walk.parseAny(r.getObjectId());
396 if (obj instanceof RevCommit) {
397 final int cTime = ((RevCommit) obj).getCommitTime();
398 if (maxTime < cTime)
399 maxTime = cTime;
400 }
401 } catch (IOException error) {
402
403 }
404 }
405 return maxTime;
406 }
407
408 private void markReachable(final Set<ObjectId> have, final int maxTime)
409 throws IOException {
410 Map<String, Ref> refs = local.getRefDatabase().getRefs(ALL);
411 for (final Ref r : refs.values()) {
412 ObjectId id = r.getPeeledObjectId();
413 if (id == null)
414 id = r.getObjectId();
415 if (id == null)
416 continue;
417 parseReachable(id);
418 }
419
420 for (ObjectId id : local.getAdditionalHaves())
421 parseReachable(id);
422
423 for (ObjectId id : have)
424 parseReachable(id);
425
426 if (maxTime > 0) {
427
428
429
430
431 final Date maxWhen = new Date(maxTime * 1000L);
432 walk.sort(RevSort.COMMIT_TIME_DESC);
433 walk.markStart(reachableCommits);
434 walk.setRevFilter(CommitTimeRevFilter.after(maxWhen));
435 for (;;) {
436 final RevCommit c = walk.next();
437 if (c == null)
438 break;
439 if (c.has(ADVERTISED) && !c.has(COMMON)) {
440
441
442
443 c.add(COMMON);
444 c.carry(COMMON);
445 reachableCommits.add(c);
446 }
447 }
448 }
449 }
450
451 private void parseReachable(ObjectId id) {
452 try {
453 RevCommit o = walk.parseCommit(id);
454 if (!o.has(REACHABLE)) {
455 o.add(REACHABLE);
456 reachableCommits.add(o);
457 }
458 } catch (IOException readError) {
459
460 }
461 }
462
463 private boolean sendWants(final Collection<Ref> want) throws IOException {
464 final PacketLineOut p = statelessRPC ? pckState : pckOut;
465 boolean first = true;
466 for (final Ref r : want) {
467 try {
468 if (walk.parseAny(r.getObjectId()).has(REACHABLE)) {
469
470
471
472 continue;
473 }
474 } catch (IOException err) {
475
476
477 }
478
479 final StringBuilder line = new StringBuilder(46);
480 line.append("want ");
481 line.append(r.getObjectId().name());
482 if (first) {
483 line.append(enableCapabilities());
484 first = false;
485 }
486 line.append('\n');
487 p.writeString(line.toString());
488 }
489 if (first)
490 return false;
491 p.end();
492 outNeedsEnd = false;
493 return true;
494 }
495
496 private String enableCapabilities() throws TransportException {
497 final StringBuilder line = new StringBuilder();
498 if (noProgress)
499 wantCapability(line, OPTION_NO_PROGRESS);
500 if (includeTags)
501 includeTags = wantCapability(line, OPTION_INCLUDE_TAG);
502 if (allowOfsDelta)
503 wantCapability(line, OPTION_OFS_DELTA);
504
505 if (wantCapability(line, OPTION_MULTI_ACK_DETAILED)) {
506 multiAck = MultiAck.DETAILED;
507 if (statelessRPC)
508 noDone = wantCapability(line, OPTION_NO_DONE);
509 } else if (wantCapability(line, OPTION_MULTI_ACK))
510 multiAck = MultiAck.CONTINUE;
511 else
512 multiAck = MultiAck.OFF;
513
514 if (thinPack)
515 thinPack = wantCapability(line, OPTION_THIN_PACK);
516 if (wantCapability(line, OPTION_SIDE_BAND_64K))
517 sideband = true;
518 else if (wantCapability(line, OPTION_SIDE_BAND))
519 sideband = true;
520
521 if (statelessRPC && multiAck != MultiAck.DETAILED) {
522
523
524
525
526 throw new PackProtocolException(uri, MessageFormat.format(
527 JGitText.get().statelessRPCRequiresOptionToBeEnabled,
528 OPTION_MULTI_ACK_DETAILED));
529 }
530
531 addUserAgentCapability(line);
532 return line.toString();
533 }
534
535 private void negotiate(final ProgressMonitor monitor) throws IOException,
536 CancelledException {
537 final MutableObjectId ackId = new MutableObjectId();
538 int resultsPending = 0;
539 int havesSent = 0;
540 int havesSinceLastContinue = 0;
541 boolean receivedContinue = false;
542 boolean receivedAck = false;
543 boolean receivedReady = false;
544
545 if (statelessRPC)
546 state.writeTo(out, null);
547
548 negotiateBegin();
549 SEND_HAVES: for (;;) {
550 final RevCommit c = walk.next();
551 if (c == null)
552 break SEND_HAVES;
553
554 pckOut.writeString("have " + c.getId().name() + "\n");
555 havesSent++;
556 havesSinceLastContinue++;
557
558 if ((31 & havesSent) != 0) {
559
560
561
562
563 continue;
564 }
565
566 if (monitor.isCancelled())
567 throw new CancelledException();
568
569 pckOut.end();
570 resultsPending++;
571
572 if (havesSent == 32 && !statelessRPC) {
573
574
575
576
577
578 continue;
579 }
580
581 READ_RESULT: for (;;) {
582 final AckNackResult anr = pckIn.readACK(ackId);
583 switch (anr) {
584 case NAK:
585
586
587
588 resultsPending--;
589 break READ_RESULT;
590
591 case ACK:
592
593
594
595
596 multiAck = MultiAck.OFF;
597 resultsPending = 0;
598 receivedAck = true;
599 if (statelessRPC)
600 state.writeTo(out, null);
601 break SEND_HAVES;
602
603 case ACK_CONTINUE:
604 case ACK_COMMON:
605 case ACK_READY:
606
607
608
609
610
611 markCommon(walk.parseAny(ackId), anr);
612 receivedAck = true;
613 receivedContinue = true;
614 havesSinceLastContinue = 0;
615 if (anr == AckNackResult.ACK_READY)
616 receivedReady = true;
617 break;
618 }
619
620 if (monitor.isCancelled())
621 throw new CancelledException();
622 }
623
624 if (noDone & receivedReady)
625 break SEND_HAVES;
626 if (statelessRPC)
627 state.writeTo(out, null);
628
629 if (receivedContinue && havesSinceLastContinue > MAX_HAVES) {
630
631
632
633
634
635 break SEND_HAVES;
636 }
637 }
638
639
640
641 if (monitor.isCancelled())
642 throw new CancelledException();
643
644 if (!receivedReady || !noDone) {
645
646
647
648
649 pckOut.writeString("done\n");
650 pckOut.flush();
651 }
652
653 if (!receivedAck) {
654
655
656
657
658 multiAck = MultiAck.OFF;
659 resultsPending++;
660 }
661
662 READ_RESULT: while (resultsPending > 0 || multiAck != MultiAck.OFF) {
663 final AckNackResult anr = pckIn.readACK(ackId);
664 resultsPending--;
665 switch (anr) {
666 case NAK:
667
668
669
670 break;
671
672 case ACK:
673
674
675
676 break READ_RESULT;
677
678 case ACK_CONTINUE:
679 case ACK_COMMON:
680 case ACK_READY:
681
682
683 multiAck = MultiAck.CONTINUE;
684 break;
685 }
686
687 if (monitor.isCancelled())
688 throw new CancelledException();
689 }
690 }
691
692 private void negotiateBegin() throws IOException {
693 walk.resetRetain(REACHABLE, ADVERTISED);
694 walk.markStart(reachableCommits);
695 walk.sort(RevSort.COMMIT_TIME_DESC);
696 walk.setRevFilter(new RevFilter() {
697 @Override
698 public RevFilter clone() {
699 return this;
700 }
701
702 @Override
703 public boolean include(final RevWalk walker, final RevCommit c) {
704 final boolean remoteKnowsIsCommon = c.has(COMMON);
705 if (c.has(ADVERTISED)) {
706
707
708
709
710
711 c.add(COMMON);
712 }
713 return !remoteKnowsIsCommon;
714 }
715
716 @Override
717 public boolean requiresCommitBody() {
718 return false;
719 }
720 });
721 }
722
723 private void markRefsAdvertised() {
724 for (final Ref r : getRefs()) {
725 markAdvertised(r.getObjectId());
726 if (r.getPeeledObjectId() != null)
727 markAdvertised(r.getPeeledObjectId());
728 }
729 }
730
731 private void markAdvertised(final AnyObjectId id) {
732 try {
733 walk.parseAny(id).add(ADVERTISED);
734 } catch (IOException readError) {
735
736 }
737 }
738
739 private void markCommon(final RevObject obj, final AckNackResult anr)
740 throws IOException {
741 if (statelessRPC && anr == AckNackResult.ACK_COMMON && !obj.has(STATE)) {
742 StringBuilder s;
743
744 s = new StringBuilder(6 + Constants.OBJECT_ID_STRING_LENGTH);
745 s.append("have ");
746 s.append(obj.name());
747 s.append('\n');
748 pckState.writeString(s.toString());
749 obj.add(STATE);
750 }
751 obj.add(COMMON);
752 if (obj instanceof RevCommit)
753 ((RevCommit) obj).carry(COMMON);
754 }
755
756 private void receivePack(final ProgressMonitor monitor,
757 OutputStream outputStream) throws IOException {
758 onReceivePack();
759 InputStream input = in;
760 if (sideband)
761 input = new SideBandInputStream(input, monitor, getMessageWriter(),
762 outputStream);
763
764 try (ObjectInserter ins = local.newObjectInserter()) {
765 PackParser parser = ins.newPackParser(input);
766 parser.setAllowThin(thinPack);
767 parser.setObjectChecker(transport.getObjectChecker());
768 parser.setLockMessage(lockMessage);
769 packLock = parser.parse(monitor);
770 ins.flush();
771 }
772 }
773
774
775
776
777
778
779
780
781 protected void onReceivePack() {
782
783 }
784
785 private static class CancelledException extends Exception {
786 private static final long serialVersionUID = 1L;
787 }
788 }