2 * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
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
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
;
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
;
57 * Push implementation using the native Git pack transfer service.
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.
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.
68 * This implementation honors {@link Transport#isPushThin()} option.
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
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
) {
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
);
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.
113 transport
.openFetch().close();
114 } catch (NotSupportedException e
) {
116 } catch (NoRemoteRepositoryException e
) {
117 // Fetch concluded the repository doesn't exist.
120 } catch (TransportException e
) {
123 return new TransportException(uri
, "push not permitted");
126 protected void doPush(final ProgressMonitor monitor
,
127 final Map
<String
, RemoteRefUpdate
> refUpdates
)
128 throws TransportException
{
130 writeCommands(refUpdates
.values(), monitor
);
132 writePack(refUpdates
, monitor
);
133 if (sentCommand
&& capableReport
)
134 readStatusReport(refUpdates
);
135 } catch (TransportException e
) {
137 } catch (Exception e
) {
138 throw new TransportException(uri
, e
.getMessage(), e
);
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
);
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());
159 sb
.append(rru
.getNewObjectId().name());
161 sb
.append(rru
.getRemoteName());
164 sb
.append(capabilities
);
167 pckOut
.writeString(sb
.toString());
168 rru
.setStatus(sentCommand ? Status
.AWAITING_REPORT
: Status
.OK
);
173 if (monitor
.isCancelled())
174 throw new TransportException(uri
, "push cancelled");
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
.setCharAt(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
>(
193 final ArrayList
<ObjectId
> newObjects
= new ArrayList
<ObjectId
>(
196 for (final Ref r
: getRefs())
197 remoteObjects
.add(r
.getObjectId());
198 remoteObjects
.addAll(additionalHaves
);
199 for (final RemoteRefUpdate r
: refUpdates
.values()) {
200 if (!ObjectId
.zeroId().equals(r
.getNewObjectId()))
201 newObjects
.add(r
.getNewObjectId());
204 writer
.preparePack(newObjects
, remoteObjects
, thinPack
, true);
205 writer
.writePack(out
);
208 private void readStatusReport(final Map
<String
, RemoteRefUpdate
> refUpdates
)
210 final String unpackLine
= pckIn
.readString();
211 if (!unpackLine
.startsWith("unpack "))
212 throw new PackProtocolException(uri
, "unexpected report line: "
214 final String unpackStatus
= unpackLine
.substring("unpack ".length());
215 if (!unpackStatus
.equals("ok"))
216 throw new TransportException(uri
,
217 "error occurred during unpacking on the remote end: "
221 while ((refLine
= pckIn
.readString()).length() > 0) {
224 if (refLine
.startsWith("ok ")) {
226 refNameEnd
= refLine
.length();
227 } else if (refLine
.startsWith("ng ")) {
229 refNameEnd
= refLine
.indexOf(" ", 3);
231 if (refNameEnd
== -1)
232 throw new PackProtocolException(uri
233 + ": unexpected report line: " + refLine
);
234 final String refName
= refLine
.substring(3, refNameEnd
);
235 final String message
= (ok ?
null : refLine
236 .substring(refNameEnd
+ 1));
238 final RemoteRefUpdate rru
= refUpdates
.get(refName
);
240 throw new PackProtocolException(uri
241 + ": unexpected ref report: " + refName
);
243 rru
.setStatus(Status
.OK
);
245 rru
.setStatus(Status
.REJECTED_OTHER_REASON
);
246 rru
.setMessage(message
);
249 for (final RemoteRefUpdate rru
: refUpdates
.values()) {
250 if (rru
.getStatus() == Status
.AWAITING_REPORT
)
251 throw new PackProtocolException(uri
252 + ": expected report for ref " + rru
.getRemoteName()