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 package org.eclipse.jgit.transport;
45
46 import static org.eclipse.jgit.lib.RefDatabase.ALL;
47 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
48 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_TIP_SHA1_IN_WANT;
49 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG;
50 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_MULTI_ACK;
51 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_MULTI_ACK_DETAILED;
52 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_DONE;
53 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_PROGRESS;
54 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_OFS_DELTA;
55 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SHALLOW;
56 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND;
57 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K;
58 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK;
59
60 import java.io.EOFException;
61 import java.io.IOException;
62 import java.io.InputStream;
63 import java.io.OutputStream;
64 import java.text.MessageFormat;
65 import java.util.ArrayList;
66 import java.util.Collection;
67 import java.util.Collections;
68 import java.util.HashSet;
69 import java.util.List;
70 import java.util.Map;
71 import java.util.Set;
72
73 import org.eclipse.jgit.errors.CorruptObjectException;
74 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
75 import org.eclipse.jgit.errors.MissingObjectException;
76 import org.eclipse.jgit.errors.PackProtocolException;
77 import org.eclipse.jgit.internal.JGitText;
78 import org.eclipse.jgit.internal.storage.pack.PackWriter;
79 import org.eclipse.jgit.lib.Constants;
80 import org.eclipse.jgit.lib.NullProgressMonitor;
81 import org.eclipse.jgit.lib.ObjectId;
82 import org.eclipse.jgit.lib.ProgressMonitor;
83 import org.eclipse.jgit.lib.Ref;
84 import org.eclipse.jgit.lib.Repository;
85 import org.eclipse.jgit.revwalk.AsyncRevObjectQueue;
86 import org.eclipse.jgit.revwalk.DepthWalk;
87 import org.eclipse.jgit.revwalk.ObjectWalk;
88 import org.eclipse.jgit.revwalk.RevCommit;
89 import org.eclipse.jgit.revwalk.RevFlag;
90 import org.eclipse.jgit.revwalk.RevFlagSet;
91 import org.eclipse.jgit.revwalk.RevObject;
92 import org.eclipse.jgit.revwalk.RevTag;
93 import org.eclipse.jgit.revwalk.RevWalk;
94 import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter;
95 import org.eclipse.jgit.storage.pack.PackConfig;
96 import org.eclipse.jgit.transport.GitProtocolConstants.MultiAck;
97 import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
98 import org.eclipse.jgit.util.io.InterruptTimer;
99 import org.eclipse.jgit.util.io.NullOutputStream;
100 import org.eclipse.jgit.util.io.TimeoutInputStream;
101 import org.eclipse.jgit.util.io.TimeoutOutputStream;
102
103
104
105
106 public class UploadPack {
107
108 public static enum RequestPolicy {
109
110 ADVERTISED,
111
112
113
114
115
116 REACHABLE_COMMIT,
117
118
119
120
121
122
123
124
125
126 TIP,
127
128
129
130
131
132
133
134 REACHABLE_COMMIT_TIP,
135
136
137 ANY;
138 }
139
140
141
142
143
144
145 public interface RequestValidator {
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160 void checkWants(UploadPack up, List<ObjectId> wants)
161 throws PackProtocolException, IOException;
162 }
163
164
165 public static class FirstLine {
166 private final String line;
167 private final Set<String> options;
168
169
170
171
172
173
174
175 public FirstLine(String line) {
176 if (line.length() > 45) {
177 final HashSet<String> opts = new HashSet<String>();
178 String opt = line.substring(45);
179 if (opt.startsWith(" "))
180 opt = opt.substring(1);
181 for (String c : opt.split(" "))
182 opts.add(c);
183 this.line = line.substring(0, 45);
184 this.options = Collections.unmodifiableSet(opts);
185 } else {
186 this.line = line;
187 this.options = Collections.emptySet();
188 }
189 }
190
191
192 public String getLine() {
193 return line;
194 }
195
196
197 public Set<String> getOptions() {
198 return options;
199 }
200 }
201
202
203 private final Repository db;
204
205
206 private final RevWalk walk;
207
208
209 private PackConfig packConfig;
210
211
212 private TransferConfig transferConfig;
213
214
215 private int timeout;
216
217
218
219
220
221
222
223
224
225
226
227
228 private boolean biDirectionalPipe = true;
229
230
231 private InterruptTimer timer;
232
233 private InputStream rawIn;
234
235 private OutputStream rawOut;
236
237 private PacketLineIn pckIn;
238
239 private PacketLineOut pckOut;
240
241 private OutputStream msgOut = NullOutputStream.INSTANCE;
242
243
244 private Map<String, Ref> refs;
245
246
247 private AdvertiseRefsHook advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
248
249
250 private RefFilter refFilter = RefFilter.DEFAULT;
251
252
253 private PreUploadHook preUploadHook = PreUploadHook.NULL;
254
255
256 private Set<String> options;
257 String userAgent;
258
259
260 private final Set<ObjectId> wantIds = new HashSet<ObjectId>();
261
262
263 private final Set<RevObject> wantAll = new HashSet<RevObject>();
264
265
266 private final Set<RevObject> commonBase = new HashSet<RevObject>();
267
268
269 private final Set<ObjectId> clientShallowCommits = new HashSet<ObjectId>();
270
271
272 private final List<ObjectId> unshallowCommits = new ArrayList<ObjectId>();
273
274
275 private int depth;
276
277
278 private int oldestTime;
279
280
281 private Boolean okToGiveUp;
282
283 private boolean sentReady;
284
285
286 private Set<ObjectId> advertised;
287
288
289 private final RevFlag WANT;
290
291
292 private final RevFlag PEER_HAS;
293
294
295 private final RevFlag COMMON;
296
297
298 private final RevFlag SATISFIED;
299
300 private final RevFlagSet SAVE;
301
302 private RequestValidator requestValidator = new AdvertisedRequestValidator();
303
304 private MultiAck multiAck = MultiAck.OFF;
305
306 private boolean noDone;
307
308 private PackWriter.Statistics statistics;
309
310 private UploadPackLogger logger = UploadPackLogger.NULL;
311
312
313
314
315
316
317
318 public UploadPack(final Repository copyFrom) {
319 db = copyFrom;
320 walk = new RevWalk(db);
321 walk.setRetainBody(false);
322
323 WANT = walk.newFlag("WANT");
324 PEER_HAS = walk.newFlag("PEER_HAS");
325 COMMON = walk.newFlag("COMMON");
326 SATISFIED = walk.newFlag("SATISFIED");
327 walk.carry(PEER_HAS);
328
329 SAVE = new RevFlagSet();
330 SAVE.add(WANT);
331 SAVE.add(PEER_HAS);
332 SAVE.add(COMMON);
333 SAVE.add(SATISFIED);
334
335 setTransferConfig(null);
336 }
337
338
339 public final Repository getRepository() {
340 return db;
341 }
342
343
344 public final RevWalk getRevWalk() {
345 return walk;
346 }
347
348
349
350
351
352
353
354 public final Map<String, Ref> getAdvertisedRefs() {
355 return refs;
356 }
357
358
359
360
361
362
363
364
365
366
367
368
369
370 public void setAdvertisedRefs(Map<String, Ref> allRefs) {
371 if (allRefs != null)
372 refs = allRefs;
373 else
374 refs = db.getAllRefs();
375 if (refFilter == RefFilter.DEFAULT)
376 refs = transferConfig.getRefFilter().filter(refs);
377 else
378 refs = refFilter.filter(refs);
379 }
380
381
382 public int getTimeout() {
383 return timeout;
384 }
385
386
387
388
389
390
391
392
393
394 public void setTimeout(final int seconds) {
395 timeout = seconds;
396 }
397
398
399
400
401
402 public boolean isBiDirectionalPipe() {
403 return biDirectionalPipe;
404 }
405
406
407
408
409
410
411
412
413
414
415 public void setBiDirectionalPipe(final boolean twoWay) {
416 biDirectionalPipe = twoWay;
417 }
418
419
420
421
422
423 public RequestPolicy getRequestPolicy() {
424 if (requestValidator instanceof AdvertisedRequestValidator)
425 return RequestPolicy.ADVERTISED;
426 if (requestValidator instanceof ReachableCommitRequestValidator)
427 return RequestPolicy.REACHABLE_COMMIT;
428 if (requestValidator instanceof TipRequestValidator)
429 return RequestPolicy.TIP;
430 if (requestValidator instanceof ReachableCommitTipRequestValidator)
431 return RequestPolicy.REACHABLE_COMMIT_TIP;
432 if (requestValidator instanceof AnyRequestValidator)
433 return RequestPolicy.ANY;
434 return null;
435 }
436
437
438
439
440
441
442
443
444
445
446
447
448 public void setRequestPolicy(RequestPolicy policy) {
449 switch (policy) {
450 case ADVERTISED:
451 default:
452 requestValidator = new AdvertisedRequestValidator();
453 break;
454 case REACHABLE_COMMIT:
455 requestValidator = new ReachableCommitRequestValidator();
456 break;
457 case TIP:
458 requestValidator = new TipRequestValidator();
459 break;
460 case REACHABLE_COMMIT_TIP:
461 requestValidator = new ReachableCommitTipRequestValidator();
462 break;
463 case ANY:
464 requestValidator = new AnyRequestValidator();
465 break;
466 }
467 }
468
469
470
471
472
473
474 public void setRequestValidator(RequestValidator validator) {
475 requestValidator = validator != null ? validator
476 : new AdvertisedRequestValidator();
477 }
478
479
480 public AdvertiseRefsHook getAdvertiseRefsHook() {
481 return advertiseRefsHook;
482 }
483
484
485 public RefFilter getRefFilter() {
486 return refFilter;
487 }
488
489
490
491
492
493
494
495
496
497
498
499 public void setAdvertiseRefsHook(final AdvertiseRefsHook advertiseRefsHook) {
500 if (advertiseRefsHook != null)
501 this.advertiseRefsHook = advertiseRefsHook;
502 else
503 this.advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
504 }
505
506
507
508
509
510
511
512
513
514
515
516
517 public void setRefFilter(final RefFilter refFilter) {
518 this.refFilter = refFilter != null ? refFilter : RefFilter.DEFAULT;
519 }
520
521
522 public PreUploadHook getPreUploadHook() {
523 return preUploadHook;
524 }
525
526
527
528
529
530
531
532 public void setPreUploadHook(PreUploadHook hook) {
533 preUploadHook = hook != null ? hook : PreUploadHook.NULL;
534 }
535
536
537
538
539
540
541
542
543 public void setPackConfig(PackConfig pc) {
544 this.packConfig = pc;
545 }
546
547
548
549
550
551
552
553 public void setTransferConfig(TransferConfig tc) {
554 this.transferConfig = tc != null ? tc : new TransferConfig(db);
555 setRequestPolicy(transferConfig.isAllowTipSha1InWant()
556 ? RequestPolicy.TIP : RequestPolicy.ADVERTISED);
557 }
558
559
560 public UploadPackLogger getLogger() {
561 return logger;
562 }
563
564
565
566
567
568
569
570 public void setLogger(UploadPackLogger logger) {
571 this.logger = logger;
572 }
573
574
575
576
577
578
579
580
581
582
583
584
585 public boolean isSideBand() throws RequestNotYetReadException {
586 if (options == null)
587 throw new RequestNotYetReadException();
588 return (options.contains(OPTION_SIDE_BAND)
589 || options.contains(OPTION_SIDE_BAND_64K));
590 }
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609 public void upload(final InputStream input, final OutputStream output,
610 final OutputStream messages) throws IOException {
611 try {
612 rawIn = input;
613 rawOut = output;
614 if (messages != null)
615 msgOut = messages;
616
617 if (timeout > 0) {
618 final Thread caller = Thread.currentThread();
619 timer = new InterruptTimer(caller.getName() + "-Timer");
620 TimeoutInputStream i = new TimeoutInputStream(rawIn, timer);
621 TimeoutOutputStream o = new TimeoutOutputStream(rawOut, timer);
622 i.setTimeout(timeout * 1000);
623 o.setTimeout(timeout * 1000);
624 rawIn = i;
625 rawOut = o;
626 }
627
628 pckIn = new PacketLineIn(rawIn);
629 pckOut = new PacketLineOut(rawOut);
630 service();
631 } finally {
632 msgOut = NullOutputStream.INSTANCE;
633 walk.close();
634 if (timer != null) {
635 try {
636 timer.terminate();
637 } finally {
638 timer = null;
639 }
640 }
641 }
642 }
643
644
645
646
647
648
649
650
651
652 public PackWriter.Statistics getPackStatistics() {
653 return statistics;
654 }
655
656 private Map<String, Ref> getAdvertisedOrDefaultRefs() {
657 if (refs == null)
658 setAdvertisedRefs(null);
659 return refs;
660 }
661
662 private void service() throws IOException {
663 if (biDirectionalPipe)
664 sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
665 else if (requestValidator instanceof AnyRequestValidator)
666 advertised = Collections.emptySet();
667 else
668 advertised = refIdSet(getAdvertisedOrDefaultRefs().values());
669
670 boolean sendPack;
671 try {
672 recvWants();
673 if (wantIds.isEmpty()) {
674 preUploadHook.onBeginNegotiateRound(this, wantIds, 0);
675 preUploadHook.onEndNegotiateRound(this, wantIds, 0, 0, false);
676 return;
677 }
678
679 if (options.contains(OPTION_MULTI_ACK_DETAILED)) {
680 multiAck = MultiAck.DETAILED;
681 noDone = options.contains(OPTION_NO_DONE);
682 } else if (options.contains(OPTION_MULTI_ACK))
683 multiAck = MultiAck.CONTINUE;
684 else
685 multiAck = MultiAck.OFF;
686
687 if (depth != 0)
688 processShallow();
689 if (!clientShallowCommits.isEmpty())
690 walk.assumeShallow(clientShallowCommits);
691 sendPack = negotiate();
692 } catch (PackProtocolException err) {
693 reportErrorDuringNegotiate(err.getMessage());
694 throw err;
695
696 } catch (ServiceMayNotContinueException err) {
697 if (!err.isOutput() && err.getMessage() != null) {
698 try {
699 pckOut.writeString("ERR " + err.getMessage() + "\n");
700 err.setOutput();
701 } catch (Throwable err2) {
702
703 }
704 }
705 throw err;
706
707 } catch (IOException err) {
708 reportErrorDuringNegotiate(JGitText.get().internalServerError);
709 throw err;
710 } catch (RuntimeException err) {
711 reportErrorDuringNegotiate(JGitText.get().internalServerError);
712 throw err;
713 } catch (Error err) {
714 reportErrorDuringNegotiate(JGitText.get().internalServerError);
715 throw err;
716 }
717
718 if (sendPack)
719 sendPack();
720 }
721
722 private static Set<ObjectId> refIdSet(Collection<Ref> refs) {
723 Set<ObjectId> ids = new HashSet<ObjectId>(refs.size());
724 for (Ref ref : refs) {
725 if (ref.getObjectId() != null)
726 ids.add(ref.getObjectId());
727 }
728 return ids;
729 }
730
731 private void reportErrorDuringNegotiate(String msg) {
732 try {
733 pckOut.writeString("ERR " + msg + "\n");
734 } catch (Throwable err) {
735
736 }
737 }
738
739 private void processShallow() throws IOException {
740 try (DepthWalk.RevWalk depthWalk = new DepthWalk.RevWalk(
741 walk.getObjectReader(), depth)) {
742
743
744 for (ObjectId o : wantIds) {
745 try {
746 depthWalk.markRoot(depthWalk.parseCommit(o));
747 } catch (IncorrectObjectTypeException notCommit) {
748
749 }
750 }
751
752 RevCommit o;
753 while ((o = depthWalk.next()) != null) {
754 DepthWalk.Commit c = (DepthWalk.Commit) o;
755
756
757
758 if (c.getDepth() == depth && !clientShallowCommits.contains(c))
759 pckOut.writeString("shallow " + o.name());
760
761
762
763 if (c.getDepth() < depth && clientShallowCommits.remove(c)) {
764 unshallowCommits.add(c.copy());
765 pckOut.writeString("unshallow " + c.name());
766 }
767 }
768 }
769 pckOut.end();
770 }
771
772
773
774
775
776
777
778
779
780
781
782 public void sendAdvertisedRefs(final RefAdvertiser adv) throws IOException,
783 ServiceMayNotContinueException {
784 try {
785 advertiseRefsHook.advertiseRefs(this);
786 } catch (ServiceMayNotContinueException fail) {
787 if (fail.getMessage() != null) {
788 adv.writeOne("ERR " + fail.getMessage());
789 fail.setOutput();
790 }
791 throw fail;
792 }
793
794 adv.init(db);
795 adv.advertiseCapability(OPTION_INCLUDE_TAG);
796 adv.advertiseCapability(OPTION_MULTI_ACK_DETAILED);
797 adv.advertiseCapability(OPTION_MULTI_ACK);
798 adv.advertiseCapability(OPTION_OFS_DELTA);
799 adv.advertiseCapability(OPTION_SIDE_BAND);
800 adv.advertiseCapability(OPTION_SIDE_BAND_64K);
801 adv.advertiseCapability(OPTION_THIN_PACK);
802 adv.advertiseCapability(OPTION_NO_PROGRESS);
803 adv.advertiseCapability(OPTION_SHALLOW);
804 if (!biDirectionalPipe)
805 adv.advertiseCapability(OPTION_NO_DONE);
806 RequestPolicy policy = getRequestPolicy();
807 if (policy == RequestPolicy.TIP
808 || policy == RequestPolicy.REACHABLE_COMMIT_TIP
809 || policy == null)
810 adv.advertiseCapability(OPTION_ALLOW_TIP_SHA1_IN_WANT);
811 adv.advertiseCapability(OPTION_AGENT, UserAgent.get());
812 adv.setDerefTags(true);
813 Map<String, Ref> advertisedOrDefaultRefs = getAdvertisedOrDefaultRefs();
814 findSymrefs(adv, advertisedOrDefaultRefs);
815 advertised = adv.send(advertisedOrDefaultRefs);
816 if (adv.isEmpty())
817 adv.advertiseId(ObjectId.zeroId(), "capabilities^{}");
818 adv.end();
819 }
820
821
822
823
824
825
826
827
828
829
830
831
832 public void sendMessage(String what) {
833 try {
834 msgOut.write(Constants.encode(what + "\n"));
835 } catch (IOException e) {
836
837 }
838 }
839
840
841
842
843
844 public OutputStream getMessageOutputStream() {
845 return msgOut;
846 }
847
848 private void recvWants() throws IOException {
849 boolean isFirst = true;
850 for (;;) {
851 String line;
852 try {
853 line = pckIn.readString();
854 } catch (EOFException eof) {
855 if (isFirst)
856 break;
857 throw eof;
858 }
859
860 if (line == PacketLineIn.END)
861 break;
862
863 if (line.startsWith("deepen ")) {
864 depth = Integer.parseInt(line.substring(7));
865 continue;
866 }
867
868 if (line.startsWith("shallow ")) {
869 clientShallowCommits.add(ObjectId.fromString(line.substring(8)));
870 continue;
871 }
872
873 if (!line.startsWith("want ") || line.length() < 45)
874 throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedGot, "want", line));
875
876 if (isFirst) {
877 if (line.length() > 45) {
878 FirstLine firstLine = new FirstLine(line);
879 options = firstLine.getOptions();
880 line = firstLine.getLine();
881 } else
882 options = Collections.emptySet();
883 }
884
885 wantIds.add(ObjectId.fromString(line.substring(5)));
886 isFirst = false;
887 }
888 }
889
890
891
892
893
894
895
896 public int getDepth() {
897 if (options == null)
898 throw new RequestNotYetReadException();
899 return depth;
900 }
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917 public String getPeerUserAgent() {
918 return UserAgent.getAgent(options, userAgent);
919 }
920
921 private boolean negotiate() throws IOException {
922 okToGiveUp = Boolean.FALSE;
923
924 ObjectId last = ObjectId.zeroId();
925 List<ObjectId> peerHas = new ArrayList<ObjectId>(64);
926 for (;;) {
927 String line;
928 try {
929 line = pckIn.readString();
930 } catch (EOFException eof) {
931
932
933
934
935
936 if (!biDirectionalPipe && depth > 0)
937 return false;
938 throw eof;
939 }
940
941 if (line == PacketLineIn.END) {
942 last = processHaveLines(peerHas, last);
943 if (commonBase.isEmpty() || multiAck != MultiAck.OFF)
944 pckOut.writeString("NAK\n");
945 if (noDone && sentReady) {
946 pckOut.writeString("ACK " + last.name() + "\n");
947 return true;
948 }
949 if (!biDirectionalPipe)
950 return false;
951 pckOut.flush();
952
953 } else if (line.startsWith("have ") && line.length() == 45) {
954 peerHas.add(ObjectId.fromString(line.substring(5)));
955
956 } else if (line.equals("done")) {
957 last = processHaveLines(peerHas, last);
958
959 if (commonBase.isEmpty())
960 pckOut.writeString("NAK\n");
961
962 else if (multiAck != MultiAck.OFF)
963 pckOut.writeString("ACK " + last.name() + "\n");
964
965 return true;
966
967 } else {
968 throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedGot, "have", line));
969 }
970 }
971 }
972
973 private ObjectId processHaveLines(List<ObjectId> peerHas, ObjectId last)
974 throws IOException {
975 preUploadHook.onBeginNegotiateRound(this, wantIds, peerHas.size());
976 if (wantAll.isEmpty() && !wantIds.isEmpty())
977 parseWants();
978 if (peerHas.isEmpty())
979 return last;
980
981 sentReady = false;
982 int haveCnt = 0;
983 walk.getObjectReader().setAvoidUnreachableObjects(true);
984 AsyncRevObjectQueue q = walk.parseAny(peerHas, false);
985 try {
986 for (;;) {
987 RevObject obj;
988 try {
989 obj = q.next();
990 } catch (MissingObjectException notFound) {
991 continue;
992 }
993 if (obj == null)
994 break;
995
996 last = obj;
997 haveCnt++;
998
999 if (obj instanceof RevCommit) {
1000 RevCommit c = (RevCommit) obj;
1001 if (oldestTime == 0 || c.getCommitTime() < oldestTime)
1002 oldestTime = c.getCommitTime();
1003 }
1004
1005 if (obj.has(PEER_HAS))
1006 continue;
1007
1008 obj.add(PEER_HAS);
1009 if (obj instanceof RevCommit)
1010 ((RevCommit) obj).carry(PEER_HAS);
1011 addCommonBase(obj);
1012
1013
1014
1015 switch (multiAck) {
1016 case OFF:
1017 if (commonBase.size() == 1)
1018 pckOut.writeString("ACK " + obj.name() + "\n");
1019 break;
1020 case CONTINUE:
1021 pckOut.writeString("ACK " + obj.name() + " continue\n");
1022 break;
1023 case DETAILED:
1024 pckOut.writeString("ACK " + obj.name() + " common\n");
1025 break;
1026 }
1027 }
1028 } finally {
1029 q.release();
1030 walk.getObjectReader().setAvoidUnreachableObjects(false);
1031 }
1032
1033 int missCnt = peerHas.size() - haveCnt;
1034
1035
1036
1037
1038
1039 boolean didOkToGiveUp = false;
1040 if (0 < missCnt) {
1041 for (int i = peerHas.size() - 1; i >= 0; i--) {
1042 ObjectId id = peerHas.get(i);
1043 if (walk.lookupOrNull(id) == null) {
1044 didOkToGiveUp = true;
1045 if (okToGiveUp()) {
1046 switch (multiAck) {
1047 case OFF:
1048 break;
1049 case CONTINUE:
1050 pckOut.writeString("ACK " + id.name() + " continue\n");
1051 break;
1052 case DETAILED:
1053 pckOut.writeString("ACK " + id.name() + " ready\n");
1054 sentReady = true;
1055 break;
1056 }
1057 }
1058 break;
1059 }
1060 }
1061 }
1062
1063 if (multiAck == MultiAck.DETAILED && !didOkToGiveUp && okToGiveUp()) {
1064 ObjectId id = peerHas.get(peerHas.size() - 1);
1065 sentReady = true;
1066 pckOut.writeString("ACK " + id.name() + " ready\n");
1067 sentReady = true;
1068 }
1069
1070 preUploadHook.onEndNegotiateRound(this, wantAll, haveCnt, missCnt, sentReady);
1071 peerHas.clear();
1072 return last;
1073 }
1074
1075 private void parseWants() throws IOException {
1076 List<ObjectId> notAdvertisedWants = null;
1077 for (ObjectId obj : wantIds) {
1078 if (!advertised.contains(obj)) {
1079 if (notAdvertisedWants == null)
1080 notAdvertisedWants = new ArrayList<ObjectId>();
1081 notAdvertisedWants.add(obj);
1082 }
1083 }
1084 if (notAdvertisedWants != null)
1085 requestValidator.checkWants(this, notAdvertisedWants);
1086
1087 AsyncRevObjectQueue q = walk.parseAny(wantIds, true);
1088 try {
1089 RevObject obj;
1090 while ((obj = q.next()) != null) {
1091 want(obj);
1092
1093 if (!(obj instanceof RevCommit))
1094 obj.add(SATISFIED);
1095 if (obj instanceof RevTag) {
1096 obj = walk.peel(obj);
1097 if (obj instanceof RevCommit)
1098 want(obj);
1099 }
1100 }
1101 wantIds.clear();
1102 } catch (MissingObjectException notFound) {
1103 ObjectId id = notFound.getObjectId();
1104 throw new PackProtocolException(MessageFormat.format(
1105 JGitText.get().wantNotValid, id.name()), notFound);
1106 } finally {
1107 q.release();
1108 }
1109 }
1110
1111 private void want(RevObject obj) {
1112 if (!obj.has(WANT)) {
1113 obj.add(WANT);
1114 wantAll.add(obj);
1115 }
1116 }
1117
1118
1119
1120
1121
1122
1123 public static final class AdvertisedRequestValidator
1124 implements RequestValidator {
1125 public void checkWants(UploadPack up, List<ObjectId> wants)
1126 throws PackProtocolException, IOException {
1127 if (!up.isBiDirectionalPipe())
1128 new ReachableCommitRequestValidator().checkWants(up, wants);
1129 else if (!wants.isEmpty())
1130 throw new PackProtocolException(MessageFormat.format(
1131 JGitText.get().wantNotValid, wants.iterator().next().name()));
1132 }
1133 }
1134
1135
1136
1137
1138
1139
1140 public static final class ReachableCommitRequestValidator
1141 implements RequestValidator {
1142 public void checkWants(UploadPack up, List<ObjectId> wants)
1143 throws PackProtocolException, IOException {
1144 checkNotAdvertisedWants(up.getRevWalk(), wants,
1145 refIdSet(up.getAdvertisedRefs().values()));
1146 }
1147 }
1148
1149
1150
1151
1152
1153
1154 public static final class TipRequestValidator implements RequestValidator {
1155 public void checkWants(UploadPack up, List<ObjectId> wants)
1156 throws PackProtocolException, IOException {
1157 if (!up.isBiDirectionalPipe())
1158 new ReachableCommitTipRequestValidator().checkWants(up, wants);
1159 else if (!wants.isEmpty()) {
1160 Set<ObjectId> refIds =
1161 refIdSet(up.getRepository().getRefDatabase().getRefs(ALL).values());
1162 for (ObjectId obj : wants) {
1163 if (!refIds.contains(obj))
1164 throw new PackProtocolException(MessageFormat.format(
1165 JGitText.get().wantNotValid, obj.name()));
1166 }
1167 }
1168 }
1169 }
1170
1171
1172
1173
1174
1175
1176 public static final class ReachableCommitTipRequestValidator
1177 implements RequestValidator {
1178 public void checkWants(UploadPack up, List<ObjectId> wants)
1179 throws PackProtocolException, IOException {
1180 checkNotAdvertisedWants(up.getRevWalk(), wants,
1181 refIdSet(up.getRepository().getRefDatabase().getRefs(ALL).values()));
1182 }
1183 }
1184
1185
1186
1187
1188
1189
1190 public static final class AnyRequestValidator implements RequestValidator {
1191 public void checkWants(UploadPack up, List<ObjectId> wants)
1192 throws PackProtocolException, IOException {
1193
1194 }
1195 }
1196
1197 private static void checkNotAdvertisedWants(RevWalk walk,
1198 List<ObjectId> notAdvertisedWants, Set<ObjectId> reachableFrom)
1199 throws MissingObjectException, IncorrectObjectTypeException, IOException {
1200
1201
1202
1203
1204
1205
1206 AsyncRevObjectQueue q = walk.parseAny(notAdvertisedWants, true);
1207 try {
1208 RevObject obj;
1209 while ((obj = q.next()) != null) {
1210 if (!(obj instanceof RevCommit))
1211 throw new PackProtocolException(MessageFormat.format(
1212 JGitText.get().wantNotValid, obj.name()));
1213 walk.markStart((RevCommit) obj);
1214 }
1215 } catch (MissingObjectException notFound) {
1216 ObjectId id = notFound.getObjectId();
1217 throw new PackProtocolException(MessageFormat.format(
1218 JGitText.get().wantNotValid, id.name()), notFound);
1219 } finally {
1220 q.release();
1221 }
1222 for (ObjectId id : reachableFrom) {
1223 try {
1224 walk.markUninteresting(walk.parseCommit(id));
1225 } catch (IncorrectObjectTypeException notCommit) {
1226 continue;
1227 }
1228 }
1229
1230 RevCommit bad = walk.next();
1231 if (bad != null) {
1232 throw new PackProtocolException(MessageFormat.format(
1233 JGitText.get().wantNotValid,
1234 bad.name()));
1235 }
1236 walk.reset();
1237 }
1238
1239 private void addCommonBase(final RevObject o) {
1240 if (!o.has(COMMON)) {
1241 o.add(COMMON);
1242 commonBase.add(o);
1243 okToGiveUp = null;
1244 }
1245 }
1246
1247 private boolean okToGiveUp() throws PackProtocolException {
1248 if (okToGiveUp == null)
1249 okToGiveUp = Boolean.valueOf(okToGiveUpImp());
1250 return okToGiveUp.booleanValue();
1251 }
1252
1253 private boolean okToGiveUpImp() throws PackProtocolException {
1254 if (commonBase.isEmpty())
1255 return false;
1256
1257 try {
1258 for (RevObject obj : wantAll) {
1259 if (!wantSatisfied(obj))
1260 return false;
1261 }
1262 return true;
1263 } catch (IOException e) {
1264 throw new PackProtocolException(JGitText.get().internalRevisionError, e);
1265 }
1266 }
1267
1268 private boolean wantSatisfied(final RevObject want) throws IOException {
1269 if (want.has(SATISFIED))
1270 return true;
1271
1272 walk.resetRetain(SAVE);
1273 walk.markStart((RevCommit) want);
1274 if (oldestTime != 0)
1275 walk.setRevFilter(CommitTimeRevFilter.after(oldestTime * 1000L));
1276 for (;;) {
1277 final RevCommit c = walk.next();
1278 if (c == null)
1279 break;
1280 if (c.has(PEER_HAS)) {
1281 addCommonBase(c);
1282 want.add(SATISFIED);
1283 return true;
1284 }
1285 }
1286 return false;
1287 }
1288
1289 private void sendPack() throws IOException {
1290 final boolean sideband = options.contains(OPTION_SIDE_BAND)
1291 || options.contains(OPTION_SIDE_BAND_64K);
1292
1293 if (!biDirectionalPipe) {
1294
1295
1296 int eof = rawIn.read();
1297 if (0 <= eof)
1298 throw new CorruptObjectException(MessageFormat.format(
1299 JGitText.get().expectedEOFReceived,
1300 "\\x" + Integer.toHexString(eof)));
1301 }
1302
1303 if (sideband) {
1304 try {
1305 sendPack(true);
1306 } catch (ServiceMayNotContinueException noPack) {
1307
1308 throw noPack;
1309 } catch (IOException err) {
1310 if (reportInternalServerErrorOverSideband())
1311 throw new UploadPackInternalServerErrorException(err);
1312 else
1313 throw err;
1314 } catch (RuntimeException err) {
1315 if (reportInternalServerErrorOverSideband())
1316 throw new UploadPackInternalServerErrorException(err);
1317 else
1318 throw err;
1319 } catch (Error err) {
1320 if (reportInternalServerErrorOverSideband())
1321 throw new UploadPackInternalServerErrorException(err);
1322 else
1323 throw err;
1324 }
1325 } else {
1326 sendPack(false);
1327 }
1328 }
1329
1330 private boolean reportInternalServerErrorOverSideband() {
1331 try {
1332 @SuppressWarnings("resource" )
1333 SideBandOutputStream err = new SideBandOutputStream(
1334 SideBandOutputStream.CH_ERROR,
1335 SideBandOutputStream.SMALL_BUF,
1336 rawOut);
1337 err.write(Constants.encode(JGitText.get().internalServerError));
1338 err.flush();
1339 return true;
1340 } catch (Throwable cannotReport) {
1341
1342 return false;
1343 }
1344 }
1345
1346 private void sendPack(final boolean sideband) throws IOException {
1347 ProgressMonitor pm = NullProgressMonitor.INSTANCE;
1348 OutputStream packOut = rawOut;
1349
1350 if (sideband) {
1351 int bufsz = SideBandOutputStream.SMALL_BUF;
1352 if (options.contains(OPTION_SIDE_BAND_64K))
1353 bufsz = SideBandOutputStream.MAX_BUF;
1354
1355 packOut = new SideBandOutputStream(SideBandOutputStream.CH_DATA,
1356 bufsz, rawOut);
1357 if (!options.contains(OPTION_NO_PROGRESS)) {
1358 msgOut = new SideBandOutputStream(
1359 SideBandOutputStream.CH_PROGRESS, bufsz, rawOut);
1360 pm = new SideBandProgressMonitor(msgOut);
1361 }
1362 }
1363
1364 try {
1365 if (wantAll.isEmpty()) {
1366 preUploadHook.onSendPack(this, wantIds, commonBase);
1367 } else {
1368 preUploadHook.onSendPack(this, wantAll, commonBase);
1369 }
1370 msgOut.flush();
1371 } catch (ServiceMayNotContinueException noPack) {
1372 if (sideband && noPack.getMessage() != null) {
1373 noPack.setOutput();
1374 @SuppressWarnings("resource" )
1375 SideBandOutputStream err = new SideBandOutputStream(
1376 SideBandOutputStream.CH_ERROR,
1377 SideBandOutputStream.SMALL_BUF, rawOut);
1378 err.write(Constants.encode(noPack.getMessage()));
1379 err.flush();
1380 }
1381 throw noPack;
1382 }
1383
1384 PackConfig cfg = packConfig;
1385 if (cfg == null)
1386 cfg = new PackConfig(db);
1387 final PackWriter pw = new PackWriter(cfg, walk.getObjectReader());
1388 try {
1389 pw.setIndexDisabled(true);
1390 pw.setUseCachedPacks(true);
1391 pw.setUseBitmaps(depth == 0 && clientShallowCommits.isEmpty());
1392 pw.setReuseDeltaCommits(true);
1393 pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA));
1394 pw.setThin(options.contains(OPTION_THIN_PACK));
1395 pw.setReuseValidatingObjects(false);
1396
1397 if (commonBase.isEmpty() && refs != null) {
1398 Set<ObjectId> tagTargets = new HashSet<ObjectId>();
1399 for (Ref ref : refs.values()) {
1400 if (ref.getPeeledObjectId() != null)
1401 tagTargets.add(ref.getPeeledObjectId());
1402 else if (ref.getObjectId() == null)
1403 continue;
1404 else if (ref.getName().startsWith(Constants.R_HEADS))
1405 tagTargets.add(ref.getObjectId());
1406 }
1407 pw.setTagTargets(tagTargets);
1408 }
1409
1410 if (depth > 0)
1411 pw.setShallowPack(depth, unshallowCommits);
1412
1413 RevWalk rw = walk;
1414 if (wantAll.isEmpty()) {
1415 pw.preparePack(pm, wantIds, commonBase);
1416 } else {
1417 walk.reset();
1418
1419 ObjectWalk ow = walk.toObjectWalkWithSameObjects();
1420 pw.preparePack(pm, ow, wantAll, commonBase);
1421 rw = ow;
1422 }
1423
1424 if (options.contains(OPTION_INCLUDE_TAG) && refs != null) {
1425 for (Ref ref : refs.values()) {
1426 ObjectId objectId = ref.getObjectId();
1427
1428
1429 if (wantAll.isEmpty()) {
1430 if (wantIds.contains(objectId))
1431 continue;
1432 } else {
1433 RevObject obj = rw.lookupOrNull(objectId);
1434 if (obj != null && obj.has(WANT))
1435 continue;
1436 }
1437
1438 if (!ref.isPeeled())
1439 ref = db.peel(ref);
1440
1441 ObjectId peeledId = ref.getPeeledObjectId();
1442 if (peeledId == null)
1443 continue;
1444
1445 objectId = ref.getObjectId();
1446 if (pw.willInclude(peeledId) && !pw.willInclude(objectId))
1447 pw.addObject(rw.parseAny(objectId));
1448 }
1449 }
1450
1451 pw.writePack(pm, NullProgressMonitor.INSTANCE, packOut);
1452
1453 if (msgOut != NullOutputStream.INSTANCE) {
1454 String msg = pw.getStatistics().getMessage() + '\n';
1455 msgOut.write(Constants.encode(msg));
1456 msgOut.flush();
1457 }
1458
1459 } finally {
1460 statistics = pw.getStatistics();
1461 if (statistics != null)
1462 logger.onPackStatistics(statistics);
1463 pw.close();
1464 }
1465
1466 if (sideband)
1467 pckOut.end();
1468 }
1469
1470 private static void findSymrefs(
1471 final RefAdvertiser adv, final Map<String, Ref> refs) {
1472 Ref head = refs.get(Constants.HEAD);
1473 if (head != null && head.isSymbolic()) {
1474 adv.addSymref(Constants.HEAD, head.getLeaf().getName());
1475 }
1476 }
1477 }