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