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 ObjectId objectId = r.getObjectId();
468 if (objectId == null) {
469 continue;
470 }
471 try {
472 if (walk.parseAny(objectId).has(REACHABLE)) {
473
474
475
476 continue;
477 }
478 } catch (IOException err) {
479
480
481 }
482
483 final StringBuilder line = new StringBuilder(46);
484 line.append("want ");
485 line.append(objectId.name());
486 if (first) {
487 line.append(enableCapabilities());
488 first = false;
489 }
490 line.append('\n');
491 p.writeString(line.toString());
492 }
493 if (first)
494 return false;
495 p.end();
496 outNeedsEnd = false;
497 return true;
498 }
499
500 private String enableCapabilities() throws TransportException {
501 final StringBuilder line = new StringBuilder();
502 if (noProgress)
503 wantCapability(line, OPTION_NO_PROGRESS);
504 if (includeTags)
505 includeTags = wantCapability(line, OPTION_INCLUDE_TAG);
506 if (allowOfsDelta)
507 wantCapability(line, OPTION_OFS_DELTA);
508
509 if (wantCapability(line, OPTION_MULTI_ACK_DETAILED)) {
510 multiAck = MultiAck.DETAILED;
511 if (statelessRPC)
512 noDone = wantCapability(line, OPTION_NO_DONE);
513 } else if (wantCapability(line, OPTION_MULTI_ACK))
514 multiAck = MultiAck.CONTINUE;
515 else
516 multiAck = MultiAck.OFF;
517
518 if (thinPack)
519 thinPack = wantCapability(line, OPTION_THIN_PACK);
520 if (wantCapability(line, OPTION_SIDE_BAND_64K))
521 sideband = true;
522 else if (wantCapability(line, OPTION_SIDE_BAND))
523 sideband = true;
524
525 if (statelessRPC && multiAck != MultiAck.DETAILED) {
526
527
528
529
530 throw new PackProtocolException(uri, MessageFormat.format(
531 JGitText.get().statelessRPCRequiresOptionToBeEnabled,
532 OPTION_MULTI_ACK_DETAILED));
533 }
534
535 addUserAgentCapability(line);
536 return line.toString();
537 }
538
539 private void negotiate(final ProgressMonitor monitor) throws IOException,
540 CancelledException {
541 final MutableObjectId ackId = new MutableObjectId();
542 int resultsPending = 0;
543 int havesSent = 0;
544 int havesSinceLastContinue = 0;
545 boolean receivedContinue = false;
546 boolean receivedAck = false;
547 boolean receivedReady = false;
548
549 if (statelessRPC)
550 state.writeTo(out, null);
551
552 negotiateBegin();
553 SEND_HAVES: for (;;) {
554 final RevCommit c = walk.next();
555 if (c == null)
556 break SEND_HAVES;
557
558 pckOut.writeString("have " + c.getId().name() + "\n");
559 havesSent++;
560 havesSinceLastContinue++;
561
562 if ((31 & havesSent) != 0) {
563
564
565
566
567 continue;
568 }
569
570 if (monitor.isCancelled())
571 throw new CancelledException();
572
573 pckOut.end();
574 resultsPending++;
575
576 if (havesSent == 32 && !statelessRPC) {
577
578
579
580
581
582 continue;
583 }
584
585 READ_RESULT: for (;;) {
586 final AckNackResult anr = pckIn.readACK(ackId);
587 switch (anr) {
588 case NAK:
589
590
591
592 resultsPending--;
593 break READ_RESULT;
594
595 case ACK:
596
597
598
599
600 multiAck = MultiAck.OFF;
601 resultsPending = 0;
602 receivedAck = true;
603 if (statelessRPC)
604 state.writeTo(out, null);
605 break SEND_HAVES;
606
607 case ACK_CONTINUE:
608 case ACK_COMMON:
609 case ACK_READY:
610
611
612
613
614
615 markCommon(walk.parseAny(ackId), anr);
616 receivedAck = true;
617 receivedContinue = true;
618 havesSinceLastContinue = 0;
619 if (anr == AckNackResult.ACK_READY)
620 receivedReady = true;
621 break;
622 }
623
624 if (monitor.isCancelled())
625 throw new CancelledException();
626 }
627
628 if (noDone & receivedReady)
629 break SEND_HAVES;
630 if (statelessRPC)
631 state.writeTo(out, null);
632
633 if (receivedContinue && havesSinceLastContinue > MAX_HAVES) {
634
635
636
637
638
639 break SEND_HAVES;
640 }
641 }
642
643
644
645 if (monitor.isCancelled())
646 throw new CancelledException();
647
648 if (!receivedReady || !noDone) {
649
650
651
652
653 pckOut.writeString("done\n");
654 pckOut.flush();
655 }
656
657 if (!receivedAck) {
658
659
660
661
662 multiAck = MultiAck.OFF;
663 resultsPending++;
664 }
665
666 READ_RESULT: while (resultsPending > 0 || multiAck != MultiAck.OFF) {
667 final AckNackResult anr = pckIn.readACK(ackId);
668 resultsPending--;
669 switch (anr) {
670 case NAK:
671
672
673
674 break;
675
676 case ACK:
677
678
679
680 break READ_RESULT;
681
682 case ACK_CONTINUE:
683 case ACK_COMMON:
684 case ACK_READY:
685
686
687 multiAck = MultiAck.CONTINUE;
688 break;
689 }
690
691 if (monitor.isCancelled())
692 throw new CancelledException();
693 }
694 }
695
696 private void negotiateBegin() throws IOException {
697 walk.resetRetain(REACHABLE, ADVERTISED);
698 walk.markStart(reachableCommits);
699 walk.sort(RevSort.COMMIT_TIME_DESC);
700 walk.setRevFilter(new RevFilter() {
701 @Override
702 public RevFilter clone() {
703 return this;
704 }
705
706 @Override
707 public boolean include(final RevWalk walker, final RevCommit c) {
708 final boolean remoteKnowsIsCommon = c.has(COMMON);
709 if (c.has(ADVERTISED)) {
710
711
712
713
714
715 c.add(COMMON);
716 }
717 return !remoteKnowsIsCommon;
718 }
719
720 @Override
721 public boolean requiresCommitBody() {
722 return false;
723 }
724 });
725 }
726
727 private void markRefsAdvertised() {
728 for (final Ref r : getRefs()) {
729 markAdvertised(r.getObjectId());
730 if (r.getPeeledObjectId() != null)
731 markAdvertised(r.getPeeledObjectId());
732 }
733 }
734
735 private void markAdvertised(final AnyObjectId id) {
736 try {
737 walk.parseAny(id).add(ADVERTISED);
738 } catch (IOException readError) {
739
740 }
741 }
742
743 private void markCommon(final RevObject obj, final AckNackResult anr)
744 throws IOException {
745 if (statelessRPC && anr == AckNackResult.ACK_COMMON && !obj.has(STATE)) {
746 StringBuilder s;
747
748 s = new StringBuilder(6 + Constants.OBJECT_ID_STRING_LENGTH);
749 s.append("have ");
750 s.append(obj.name());
751 s.append('\n');
752 pckState.writeString(s.toString());
753 obj.add(STATE);
754 }
755 obj.add(COMMON);
756 if (obj instanceof RevCommit)
757 ((RevCommit) obj).carry(COMMON);
758 }
759
760 private void receivePack(final ProgressMonitor monitor,
761 OutputStream outputStream) throws IOException {
762 onReceivePack();
763 InputStream input = in;
764 if (sideband)
765 input = new SideBandInputStream(input, monitor, getMessageWriter(),
766 outputStream);
767
768 try (ObjectInserter ins = local.newObjectInserter()) {
769 PackParser parser = ins.newPackParser(input);
770 parser.setAllowThin(thinPack);
771 parser.setObjectChecker(transport.getObjectChecker());
772 parser.setLockMessage(lockMessage);
773 packLock = parser.parse(monitor);
774 ins.flush();
775 }
776 }
777
778
779
780
781
782
783
784
785 protected void onReceivePack() {
786
787 }
788
789 private static class CancelledException extends Exception {
790 private static final long serialVersionUID = 1L;
791 }
792 }