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