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