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