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