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
45 package org.eclipse.jgit.transport;
46
47 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC;
48
49 import java.io.IOException;
50 import java.io.InputStream;
51 import java.io.OutputStream;
52 import java.text.MessageFormat;
53 import java.util.Collection;
54 import java.util.HashSet;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.Set;
58
59 import org.eclipse.jgit.errors.NoRemoteRepositoryException;
60 import org.eclipse.jgit.errors.NotSupportedException;
61 import org.eclipse.jgit.errors.PackProtocolException;
62 import org.eclipse.jgit.errors.TooLargeObjectInPackException;
63 import org.eclipse.jgit.errors.TooLargePackException;
64 import org.eclipse.jgit.errors.TransportException;
65 import org.eclipse.jgit.internal.JGitText;
66 import org.eclipse.jgit.internal.storage.pack.PackWriter;
67 import org.eclipse.jgit.lib.ObjectId;
68 import org.eclipse.jgit.lib.ProgressMonitor;
69 import org.eclipse.jgit.lib.Ref;
70 import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 public abstract class BasePackPushConnection extends BasePackConnection implements
92 PushConnection {
93
94
95
96
97 public static final String CAPABILITY_REPORT_STATUS = GitProtocolConstants.CAPABILITY_REPORT_STATUS;
98
99
100
101
102
103 public static final String CAPABILITY_DELETE_REFS = GitProtocolConstants.CAPABILITY_DELETE_REFS;
104
105
106
107
108
109 public static final String CAPABILITY_OFS_DELTA = GitProtocolConstants.CAPABILITY_OFS_DELTA;
110
111
112
113
114
115 public static final String CAPABILITY_SIDE_BAND_64K = GitProtocolConstants.CAPABILITY_SIDE_BAND_64K;
116
117
118
119
120
121 public static final String CAPABILITY_PUSH_OPTIONS = GitProtocolConstants.CAPABILITY_PUSH_OPTIONS;
122
123 private final boolean thinPack;
124 private final boolean atomic;
125
126
127 private List<String> pushOptions;
128
129 private boolean capableAtomic;
130 private boolean capableDeleteRefs;
131 private boolean capableReport;
132 private boolean capableSideBand;
133 private boolean capableOfsDelta;
134 private boolean capablePushOptions;
135
136 private boolean sentCommand;
137 private boolean writePack;
138
139
140 private long packTransferTime;
141
142
143
144
145
146
147
148 public BasePackPushConnection(final PackTransport packTransport) {
149 super(packTransport);
150 thinPack = transport.isPushThin();
151 atomic = transport.isPushAtomic();
152 pushOptions = transport.getPushOptions();
153 }
154
155 @Override
156 public void push(final ProgressMonitor monitor,
157 final Map<String, RemoteRefUpdate> refUpdates)
158 throws TransportException {
159 push(monitor, refUpdates, null);
160 }
161
162
163
164
165 @Override
166 public void push(final ProgressMonitor monitor,
167 final Map<String, RemoteRefUpdate> refUpdates, OutputStream outputStream)
168 throws TransportException {
169 markStartedOperation();
170 doPush(monitor, refUpdates, outputStream);
171 }
172
173 @Override
174 protected TransportException noRepository() {
175
176
177
178
179
180
181
182 try {
183 transport.openFetch().close();
184 } catch (NotSupportedException e) {
185
186 } catch (NoRemoteRepositoryException e) {
187
188
189 return e;
190 } catch (TransportException e) {
191
192 }
193 return new TransportException(uri, JGitText.get().pushNotPermitted);
194 }
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209 protected void doPush(final ProgressMonitor monitor,
210 final Map<String, RemoteRefUpdate> refUpdates,
211 OutputStream outputStream) throws TransportException {
212 try {
213 writeCommands(refUpdates.values(), monitor, outputStream);
214
215 if (pushOptions != null && capablePushOptions)
216 transmitOptions();
217 if (writePack)
218 writePack(refUpdates, monitor);
219 if (sentCommand) {
220 if (capableReport)
221 readStatusReport(refUpdates);
222 if (capableSideBand) {
223
224
225
226
227
228 int b = in.read();
229 if (0 <= b)
230 throw new TransportException(uri, MessageFormat.format(
231 JGitText.get().expectedEOFReceived,
232 Character.valueOf((char) b)));
233 }
234 }
235 } catch (TransportException e) {
236 throw e;
237 } catch (Exception e) {
238 throw new TransportException(uri, e.getMessage(), e);
239 } finally {
240 close();
241 }
242 }
243
244 private void writeCommands(final Collection<RemoteRefUpdate> refUpdates,
245 final ProgressMonitor monitor, OutputStream outputStream) throws IOException {
246 final String capabilities = enableCapabilities(monitor, outputStream);
247 if (atomic && !capableAtomic) {
248 throw new TransportException(uri,
249 JGitText.get().atomicPushNotSupported);
250 }
251
252 if (pushOptions != null && !capablePushOptions) {
253 throw new TransportException(uri,
254 MessageFormat.format(JGitText.get().pushOptionsNotSupported,
255 pushOptions.toString()));
256 }
257
258 for (final RemoteRefUpdate rru : refUpdates) {
259 if (!capableDeleteRefs && rru.isDelete()) {
260 rru.setStatus(Status.REJECTED_NODELETE);
261 continue;
262 }
263
264 final StringBuilder sb = new StringBuilder();
265 ObjectId oldId = rru.getExpectedOldObjectId();
266 if (oldId == null) {
267 final Ref advertised = getRef(rru.getRemoteName());
268 oldId = advertised != null ? advertised.getObjectId() : null;
269 if (oldId == null) {
270 oldId = ObjectId.zeroId();
271 }
272 }
273 sb.append(oldId.name());
274 sb.append(' ');
275 sb.append(rru.getNewObjectId().name());
276 sb.append(' ');
277 sb.append(rru.getRemoteName());
278 if (!sentCommand) {
279 sentCommand = true;
280 sb.append(capabilities);
281 }
282
283 pckOut.writeString(sb.toString());
284 rru.setStatus(Status.AWAITING_REPORT);
285 if (!rru.isDelete())
286 writePack = true;
287 }
288
289 if (monitor.isCancelled())
290 throw new TransportException(uri, JGitText.get().pushCancelled);
291 pckOut.end();
292 outNeedsEnd = false;
293 }
294
295 private void transmitOptions() throws IOException {
296 for (final String pushOption : pushOptions) {
297 pckOut.writeString(pushOption);
298 }
299
300 pckOut.end();
301 }
302
303 private String enableCapabilities(final ProgressMonitor monitor,
304 OutputStream outputStream) {
305 final StringBuilder line = new StringBuilder();
306 if (atomic)
307 capableAtomic = wantCapability(line, CAPABILITY_ATOMIC);
308 capableReport = wantCapability(line, CAPABILITY_REPORT_STATUS);
309 capableDeleteRefs = wantCapability(line, CAPABILITY_DELETE_REFS);
310 capableOfsDelta = wantCapability(line, CAPABILITY_OFS_DELTA);
311
312 if (pushOptions != null) {
313 capablePushOptions = wantCapability(line, CAPABILITY_PUSH_OPTIONS);
314 }
315
316 capableSideBand = wantCapability(line, CAPABILITY_SIDE_BAND_64K);
317 if (capableSideBand) {
318 in = new SideBandInputStream(in, monitor, getMessageWriter(),
319 outputStream);
320 pckIn = new PacketLineIn(in);
321 }
322 addUserAgentCapability(line);
323
324 if (line.length() > 0)
325 line.setCharAt(0, '\0');
326 return line.toString();
327 }
328
329 private void writePack(final Map<String, RemoteRefUpdate> refUpdates,
330 final ProgressMonitor monitor) throws IOException {
331 Set<ObjectId> remoteObjects = new HashSet<>();
332 Set<ObjectId> newObjects = new HashSet<>();
333
334 try (final PackWriter writer = new PackWriter(transport.getPackConfig(),
335 local.newObjectReader())) {
336
337 for (final Ref r : getRefs()) {
338
339 ObjectId oid = r.getObjectId();
340 if (local.hasObject(oid))
341 remoteObjects.add(oid);
342 }
343 remoteObjects.addAll(additionalHaves);
344 for (final RemoteRefUpdate r : refUpdates.values()) {
345 if (!ObjectId.zeroId().equals(r.getNewObjectId()))
346 newObjects.add(r.getNewObjectId());
347 }
348
349 writer.setIndexDisabled(true);
350 writer.setUseCachedPacks(true);
351 writer.setUseBitmaps(true);
352 writer.setThin(thinPack);
353 writer.setReuseValidatingObjects(false);
354 writer.setDeltaBaseAsOffset(capableOfsDelta);
355 writer.preparePack(monitor, newObjects, remoteObjects);
356
357 OutputStream packOut = out;
358 if (capableSideBand) {
359 packOut = new CheckingSideBandOutputStream(in, out);
360 }
361 writer.writePack(monitor, monitor, packOut);
362
363 packTransferTime = writer.getStatistics().getTimeWriting();
364 }
365 }
366
367 private void readStatusReport(final Map<String, RemoteRefUpdate> refUpdates)
368 throws IOException {
369 final String unpackLine = readStringLongTimeout();
370 if (!unpackLine.startsWith("unpack "))
371 throw new PackProtocolException(uri, MessageFormat
372 .format(JGitText.get().unexpectedReportLine, unpackLine));
373 final String unpackStatus = unpackLine.substring("unpack ".length());
374 if (unpackStatus.startsWith("error Pack exceeds the limit of")) {
375 throw new TooLargePackException(uri,
376 unpackStatus.substring("error ".length()));
377 } else if (unpackStatus.startsWith("error Object too large")) {
378 throw new TooLargeObjectInPackException(uri,
379 unpackStatus.substring("error ".length()));
380 } else if (!unpackStatus.equals("ok")) {
381 throw new TransportException(uri, MessageFormat.format(
382 JGitText.get().errorOccurredDuringUnpackingOnTheRemoteEnd, unpackStatus));
383 }
384
385 String refLine;
386 while ((refLine = pckIn.readString()) != PacketLineIn.END) {
387 boolean ok = false;
388 int refNameEnd = -1;
389 if (refLine.startsWith("ok ")) {
390 ok = true;
391 refNameEnd = refLine.length();
392 } else if (refLine.startsWith("ng ")) {
393 ok = false;
394 refNameEnd = refLine.indexOf(" ", 3);
395 }
396 if (refNameEnd == -1)
397 throw new PackProtocolException(MessageFormat.format(JGitText.get().unexpectedReportLine2
398 , uri, refLine));
399 final String refName = refLine.substring(3, refNameEnd);
400 final String message = (ok ? null : refLine
401 .substring(refNameEnd + 1));
402
403 final RemoteRefUpdate rru = refUpdates.get(refName);
404 if (rru == null)
405 throw new PackProtocolException(MessageFormat.format(JGitText.get().unexpectedRefReport, uri, refName));
406 if (ok) {
407 rru.setStatus(Status.OK);
408 } else {
409 rru.setStatus(Status.REJECTED_OTHER_REASON);
410 rru.setMessage(message);
411 }
412 }
413 for (final RemoteRefUpdate rru : refUpdates.values()) {
414 if (rru.getStatus() == Status.AWAITING_REPORT)
415 throw new PackProtocolException(MessageFormat.format(
416 JGitText.get().expectedReportForRefNotReceived , uri, rru.getRemoteName()));
417 }
418 }
419
420 private String readStringLongTimeout() throws IOException {
421 if (timeoutIn == null)
422 return pckIn.readString();
423
424
425
426
427
428
429
430
431
432 final int oldTimeout = timeoutIn.getTimeout();
433 final int sendTime = (int) Math.min(packTransferTime, 28800000L);
434 try {
435 int timeout = 10 * Math.max(sendTime, oldTimeout);
436 timeoutIn.setTimeout((timeout < 0) ? Integer.MAX_VALUE : timeout);
437 return pckIn.readString();
438 } finally {
439 timeoutIn.setTimeout(oldTimeout);
440 }
441 }
442
443
444
445
446
447
448
449 public List<String> getPushOptions() {
450 return pushOptions;
451 }
452
453 private static class CheckingSideBandOutputStream extends OutputStream {
454 private final InputStream in;
455 private final OutputStream out;
456
457 CheckingSideBandOutputStream(InputStream in, OutputStream out) {
458 this.in = in;
459 this.out = out;
460 }
461
462 @Override
463 public void write(int b) throws IOException {
464 write(new byte[] { (byte) b });
465 }
466
467 @Override
468 public void write(byte[] buf, int ptr, int cnt) throws IOException {
469 try {
470 out.write(buf, ptr, cnt);
471 } catch (IOException e) {
472 throw checkError(e);
473 }
474 }
475
476 @Override
477 public void flush() throws IOException {
478 try {
479 out.flush();
480 } catch (IOException e) {
481 throw checkError(e);
482 }
483 }
484
485 private IOException checkError(IOException e1) {
486 try {
487 in.read();
488 } catch (TransportException e2) {
489 return e2;
490 } catch (IOException e2) {
491 return e1;
492 }
493 return e1;
494 }
495 }
496 }