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