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