jgit: Switch usage of AnyObjectId.toString() to new AnyObjectId.name()
[egit/charleso.git] / org.spearce.jgit / src / org / spearce / jgit / transport / BasePackPushConnection.java
blob259462396cafe1847ffd4693c25a03c0b9ed42f1
1 /*
2 * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
5 * All rights reserved.
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
9 * conditions are met:
11 * - Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * - Redistributions in binary form must reproduce the above
15 * copyright notice, this list of conditions and the following
16 * disclaimer in the documentation and/or other materials provided
17 * with the distribution.
19 * - Neither the name of the Git Development Community nor the
20 * names of its contributors may be used to endorse or promote
21 * products derived from this software without specific prior
22 * written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
25 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
26 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
29 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
36 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 package org.spearce.jgit.transport;
41 import java.io.IOException;
42 import java.util.ArrayList;
43 import java.util.Collection;
44 import java.util.Map;
46 import org.spearce.jgit.errors.NoRemoteRepositoryException;
47 import org.spearce.jgit.errors.NotSupportedException;
48 import org.spearce.jgit.errors.PackProtocolException;
49 import org.spearce.jgit.errors.TransportException;
50 import org.spearce.jgit.lib.ObjectId;
51 import org.spearce.jgit.lib.PackWriter;
52 import org.spearce.jgit.lib.ProgressMonitor;
53 import org.spearce.jgit.lib.Ref;
54 import org.spearce.jgit.transport.RemoteRefUpdate.Status;
56 /**
57 * Push implementation using the native Git pack transfer service.
58 * <p>
59 * This is the canonical implementation for transferring objects to the remote
60 * repository from the local repository by talking to the 'git-receive-pack'
61 * service. Objects are packed on the local side into a pack file and then sent
62 * to the remote repository.
63 * <p>
64 * This connection requires only a bi-directional pipe or socket, and thus is
65 * easily wrapped up into a local process pipe, anonymous TCP socket, or a
66 * command executed through an SSH tunnel.
67 * <p>
68 * This implementation honors {@link Transport#isPushThin()} option.
69 * <p>
70 * Concrete implementations should just call
71 * {@link #init(java.io.InputStream, java.io.OutputStream)} and
72 * {@link #readAdvertisedRefs()} methods in constructor or before any use. They
73 * should also handle resources releasing in {@link #close()} method if needed.
75 class BasePackPushConnection extends BasePackConnection implements
76 PushConnection {
77 static final String CAPABILITY_REPORT_STATUS = "report-status";
79 static final String CAPABILITY_DELETE_REFS = "delete-refs";
81 private final boolean thinPack;
83 private boolean capableDeleteRefs;
85 private boolean capableReport;
87 private boolean sentCommand;
89 private boolean writePack;
91 BasePackPushConnection(final PackTransport transport) {
92 super(transport);
93 thinPack = transport.isPushThin();
96 public void push(final ProgressMonitor monitor,
97 final Map<String, RemoteRefUpdate> refUpdates)
98 throws TransportException {
99 markStartedOperation();
100 doPush(monitor, refUpdates);
103 @Override
104 protected TransportException noRepository() {
105 // Sadly we cannot tell the "invalid URI" case from "push not allowed".
106 // Opening a fetch connection can help us tell the difference, as any
107 // useful repository is going to support fetch if it also would allow
108 // push. So if fetch throws NoRemoteRepositoryException we know the
109 // URI is wrong. Otherwise we can correctly state push isn't allowed
110 // as the fetch connection opened successfully.
112 try {
113 transport.openFetch().close();
114 } catch (NotSupportedException e) {
115 // Fall through.
116 } catch (NoRemoteRepositoryException e) {
117 // Fetch concluded the repository doesn't exist.
119 return e;
120 } catch (TransportException e) {
121 // Fall through.
123 return new TransportException(uri, "push not permitted");
126 protected void doPush(final ProgressMonitor monitor,
127 final Map<String, RemoteRefUpdate> refUpdates)
128 throws TransportException {
129 try {
130 writeCommands(refUpdates.values(), monitor);
131 if (writePack)
132 writePack(refUpdates, monitor);
133 if (sentCommand && capableReport)
134 readStatusReport(refUpdates);
135 } catch (TransportException e) {
136 throw e;
137 } catch (Exception e) {
138 throw new TransportException(uri, e.getMessage(), e);
139 } finally {
140 close();
144 private void writeCommands(final Collection<RemoteRefUpdate> refUpdates,
145 final ProgressMonitor monitor) throws IOException {
146 final String capabilities = enableCapabilities();
147 for (final RemoteRefUpdate rru : refUpdates) {
148 if (!capableDeleteRefs && rru.isDelete()) {
149 rru.setStatus(Status.REJECTED_NODELETE);
150 continue;
153 final StringBuilder sb = new StringBuilder();
154 final Ref advertisedRef = getRef(rru.getRemoteName());
155 final ObjectId oldId = (advertisedRef == null ? ObjectId.zeroId()
156 : advertisedRef.getObjectId());
157 sb.append(oldId.name());
158 sb.append(' ');
159 sb.append(rru.getNewObjectId().name());
160 sb.append(' ');
161 sb.append(rru.getRemoteName());
162 if (!sentCommand) {
163 sentCommand = true;
164 sb.append(capabilities);
167 pckOut.writeString(sb.toString());
168 rru.setStatus(sentCommand ? Status.AWAITING_REPORT : Status.OK);
169 if (!rru.isDelete())
170 writePack = true;
173 if (monitor.isCancelled())
174 throw new TransportException(uri, "push cancelled");
175 pckOut.end();
176 outNeedsEnd = false;
179 private String enableCapabilities() {
180 final StringBuilder line = new StringBuilder();
181 capableReport = wantCapability(line, CAPABILITY_REPORT_STATUS);
182 capableDeleteRefs = wantCapability(line, CAPABILITY_DELETE_REFS);
183 if (line.length() > 0)
184 line.insert(0, '\0');
185 return line.toString();
188 private void writePack(final Map<String, RemoteRefUpdate> refUpdates,
189 final ProgressMonitor monitor) throws IOException {
190 final PackWriter writer = new PackWriter(local, monitor);
191 final ArrayList<ObjectId> remoteObjects = new ArrayList<ObjectId>(
192 getRefs().size());
193 final ArrayList<ObjectId> newObjects = new ArrayList<ObjectId>(
194 refUpdates.size());
196 for (final Ref r : getRefs())
197 remoteObjects.add(r.getObjectId());
198 for (final RemoteRefUpdate r : refUpdates.values()) {
199 if (!ObjectId.zeroId().equals(r.getNewObjectId()))
200 newObjects.add(r.getNewObjectId());
203 writer.preparePack(newObjects, remoteObjects, thinPack, true);
204 writer.writePack(out);
207 private void readStatusReport(final Map<String, RemoteRefUpdate> refUpdates)
208 throws IOException {
209 final String unpackLine = pckIn.readString();
210 if (!unpackLine.startsWith("unpack "))
211 throw new PackProtocolException(uri, "unexpected report line: "
212 + unpackLine);
213 final String unpackStatus = unpackLine.substring("unpack ".length());
214 if (!unpackStatus.equals("ok"))
215 throw new TransportException(uri,
216 "error occurred during unpacking on the remote end: "
217 + unpackStatus);
219 String refLine;
220 while ((refLine = pckIn.readString()).length() > 0) {
221 boolean ok = false;
222 int refNameEnd = -1;
223 if (refLine.startsWith("ok ")) {
224 ok = true;
225 refNameEnd = refLine.length();
226 } else if (refLine.startsWith("ng ")) {
227 ok = false;
228 refNameEnd = refLine.indexOf(" ", 3);
230 if (refNameEnd == -1)
231 throw new PackProtocolException(uri
232 + ": unexpected report line: " + refLine);
233 final String refName = refLine.substring(3, refNameEnd);
234 final String message = (ok ? null : refLine
235 .substring(refNameEnd + 1));
237 final RemoteRefUpdate rru = refUpdates.get(refName);
238 if (rru == null)
239 throw new PackProtocolException(uri
240 + ": unexpected ref report: " + refName);
241 if (ok) {
242 rru.setStatus(Status.OK);
243 } else {
244 rru.setStatus(Status.REJECTED_OTHER_REASON);
245 rru.setMessage(message);
248 for (final RemoteRefUpdate rru : refUpdates.values()) {
249 if (rru.getStatus() == Status.AWAITING_REPORT)
250 throw new PackProtocolException(uri
251 + ": expected report for ref " + rru.getRemoteName()
252 + " not received");