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
92 public abstract class BasePackPushConnection extends BasePackConnection implements
93 PushConnection {
94
95
96
97
98 public static final String CAPABILITY_REPORT_STATUS = GitProtocolConstants.CAPABILITY_REPORT_STATUS;
99
100
101
102
103
104 public static final String CAPABILITY_DELETE_REFS = GitProtocolConstants.CAPABILITY_DELETE_REFS;
105
106
107
108
109
110 public static final String CAPABILITY_OFS_DELTA = GitProtocolConstants.CAPABILITY_OFS_DELTA;
111
112
113
114
115
116 public static final String CAPABILITY_SIDE_BAND_64K = GitProtocolConstants.CAPABILITY_SIDE_BAND_64K;
117
118
119
120
121
122 public static final String CAPABILITY_PUSH_OPTIONS = GitProtocolConstants.CAPABILITY_PUSH_OPTIONS;
123
124 private final boolean thinPack;
125 private final boolean atomic;
126
127
128 private List<String> pushOptions;
129
130 private boolean capableAtomic;
131 private boolean capableDeleteRefs;
132 private boolean capableReport;
133 private boolean capableSideBand;
134 private boolean capableOfsDelta;
135 private boolean capablePushOptions;
136
137 private boolean sentCommand;
138 private boolean writePack;
139
140
141 private long packTransferTime;
142
143
144
145
146
147
148
149 public BasePackPushConnection(PackTransport packTransport) {
150 super(packTransport);
151 thinPack = transport.isPushThin();
152 atomic = transport.isPushAtomic();
153 pushOptions = transport.getPushOptions();
154 }
155
156
157 @Override
158 public void push(final ProgressMonitor monitor,
159 final Map<String, RemoteRefUpdate> refUpdates)
160 throws TransportException {
161 push(monitor, refUpdates, null);
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
174 @Override
175 protected TransportException noRepository() {
176
177
178
179
180
181
182
183 try {
184 transport.openFetch().close();
185 } catch (NotSupportedException e) {
186
187 } catch (NoRemoteRepositoryException e) {
188
189
190 return e;
191 } catch (TransportException e) {
192
193 }
194 return new TransportException(uri, JGitText.get().pushNotPermitted);
195 }
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210 protected void doPush(final ProgressMonitor monitor,
211 final Map<String, RemoteRefUpdate> refUpdates,
212 OutputStream outputStream) throws TransportException {
213 try {
214 writeCommands(refUpdates.values(), monitor, outputStream);
215
216 if (pushOptions != null && capablePushOptions)
217 transmitOptions();
218 if (writePack)
219 writePack(refUpdates, monitor);
220 if (sentCommand) {
221 if (capableReport)
222 readStatusReport(refUpdates);
223 if (capableSideBand) {
224
225
226
227
228
229 int b = in.read();
230 if (0 <= b)
231 throw new TransportException(uri, MessageFormat.format(
232 JGitText.get().expectedEOFReceived,
233 Character.valueOf((char) b)));
234 }
235 }
236 } catch (TransportException e) {
237 throw e;
238 } catch (Exception e) {
239 throw new TransportException(uri, e.getMessage(), e);
240 } finally {
241 close();
242 }
243 }
244
245 private void writeCommands(final Collection<RemoteRefUpdate> refUpdates,
246 final ProgressMonitor monitor, OutputStream outputStream) throws IOException {
247 final String capabilities = enableCapabilities(monitor, outputStream);
248 if (atomic && !capableAtomic) {
249 throw new TransportException(uri,
250 JGitText.get().atomicPushNotSupported);
251 }
252
253 if (pushOptions != null && !capablePushOptions) {
254 throw new TransportException(uri,
255 MessageFormat.format(JGitText.get().pushOptionsNotSupported,
256 pushOptions.toString()));
257 }
258
259 for (RemoteRefUpdate rru : refUpdates) {
260 if (!capableDeleteRefs && rru.isDelete()) {
261 rru.setStatus(Status.REJECTED_NODELETE);
262 continue;
263 }
264
265 final StringBuilder sb = new StringBuilder();
266 ObjectId oldId = rru.getExpectedOldObjectId();
267 if (oldId == null) {
268 final Ref advertised = getRef(rru.getRemoteName());
269 oldId = advertised != null ? advertised.getObjectId() : null;
270 if (oldId == null) {
271 oldId = ObjectId.zeroId();
272 }
273 }
274 sb.append(oldId.name());
275 sb.append(' ');
276 sb.append(rru.getNewObjectId().name());
277 sb.append(' ');
278 sb.append(rru.getRemoteName());
279 if (!sentCommand) {
280 sentCommand = true;
281 sb.append(capabilities);
282 }
283
284 pckOut.writeString(sb.toString());
285 rru.setStatus(Status.AWAITING_REPORT);
286 if (!rru.isDelete())
287 writePack = true;
288 }
289
290 if (monitor.isCancelled())
291 throw new TransportException(uri, JGitText.get().pushCancelled);
292 pckOut.end();
293 outNeedsEnd = false;
294 }
295
296 private void transmitOptions() throws IOException {
297 for (String pushOption : pushOptions) {
298 pckOut.writeString(pushOption);
299 }
300
301 pckOut.end();
302 }
303
304 private String enableCapabilities(final ProgressMonitor monitor,
305 OutputStream outputStream) {
306 final StringBuilder line = new StringBuilder();
307 if (atomic)
308 capableAtomic = wantCapability(line, CAPABILITY_ATOMIC);
309 capableReport = wantCapability(line, CAPABILITY_REPORT_STATUS);
310 capableDeleteRefs = wantCapability(line, CAPABILITY_DELETE_REFS);
311 capableOfsDelta = wantCapability(line, CAPABILITY_OFS_DELTA);
312
313 if (pushOptions != null) {
314 capablePushOptions = wantCapability(line, CAPABILITY_PUSH_OPTIONS);
315 }
316
317 capableSideBand = wantCapability(line, CAPABILITY_SIDE_BAND_64K);
318 if (capableSideBand) {
319 in = new SideBandInputStream(in, monitor, getMessageWriter(),
320 outputStream);
321 pckIn = new PacketLineIn(in);
322 }
323 addUserAgentCapability(line);
324
325 if (line.length() > 0)
326 line.setCharAt(0, '\0');
327 return line.toString();
328 }
329
330 private void writePack(final Map<String, RemoteRefUpdate> refUpdates,
331 final ProgressMonitor monitor) throws IOException {
332 Set<ObjectId> remoteObjects = new HashSet<>();
333 Set<ObjectId> newObjects = new HashSet<>();
334
335 try (PackWritere/pack/PackWriter.html#PackWriter">PackWriter writer = new PackWriter(transport.getPackConfig(),
336 local.newObjectReader())) {
337
338 for (Ref r : getRefs()) {
339
340 ObjectId oid = r.getObjectId();
341 if (local.getObjectDatabase().has(oid))
342 remoteObjects.add(oid);
343 }
344 remoteObjects.addAll(additionalHaves);
345 for (RemoteRefUpdate r : refUpdates.values()) {
346 if (!ObjectId.zeroId().equals(r.getNewObjectId()))
347 newObjects.add(r.getNewObjectId());
348 }
349
350 writer.setIndexDisabled(true);
351 writer.setUseCachedPacks(true);
352 writer.setUseBitmaps(true);
353 writer.setThin(thinPack);
354 writer.setReuseValidatingObjects(false);
355 writer.setDeltaBaseAsOffset(capableOfsDelta);
356 writer.preparePack(monitor, newObjects, remoteObjects);
357
358 OutputStream packOut = out;
359 if (capableSideBand) {
360 packOut = new CheckingSideBandOutputStream(in, out);
361 }
362 writer.writePack(monitor, monitor, packOut);
363
364 packTransferTime = writer.getStatistics().getTimeWriting();
365 }
366 }
367
368 private void readStatusReport(Map<String, RemoteRefUpdate> refUpdates)
369 throws IOException {
370 final String unpackLine = readStringLongTimeout();
371 if (!unpackLine.startsWith("unpack "))
372 throw new PackProtocolException(uri, MessageFormat
373 .format(JGitText.get().unexpectedReportLine, unpackLine));
374 final String unpackStatus = unpackLine.substring("unpack ".length());
375 if (unpackStatus.startsWith("error Pack exceeds the limit of")) {
376 throw new TooLargePackException(uri,
377 unpackStatus.substring("error ".length()));
378 } else if (unpackStatus.startsWith("error Object too large")) {
379 throw new TooLargeObjectInPackException(uri,
380 unpackStatus.substring("error ".length()));
381 } else if (!unpackStatus.equals("ok")) {
382 throw new TransportException(uri, MessageFormat.format(
383 JGitText.get().errorOccurredDuringUnpackingOnTheRemoteEnd, unpackStatus));
384 }
385
386 for (String refLine : pckIn.readStrings()) {
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 (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 }