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