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