2 * Copyright (C) 2008, Google Inc.
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * - Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
18 * - Neither the name of the Git Development Community nor the
19 * names of its contributors may be used to endorse or promote
20 * products derived from this software without specific prior
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
24 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
25 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
30 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
33 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
35 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 package org
.spearce
.jgit
.transport
;
40 import java
.io
.BufferedOutputStream
;
41 import java
.io
.EOFException
;
42 import java
.io
.IOException
;
43 import java
.io
.InputStream
;
44 import java
.io
.OutputStream
;
45 import java
.util
.ArrayList
;
46 import java
.util
.HashSet
;
47 import java
.util
.Iterator
;
48 import java
.util
.List
;
52 import org
.spearce
.jgit
.errors
.PackProtocolException
;
53 import org
.spearce
.jgit
.lib
.Constants
;
54 import org
.spearce
.jgit
.lib
.NullProgressMonitor
;
55 import org
.spearce
.jgit
.lib
.ObjectId
;
56 import org
.spearce
.jgit
.lib
.PackWriter
;
57 import org
.spearce
.jgit
.lib
.ProgressMonitor
;
58 import org
.spearce
.jgit
.lib
.Ref
;
59 import org
.spearce
.jgit
.lib
.RefComparator
;
60 import org
.spearce
.jgit
.lib
.Repository
;
61 import org
.spearce
.jgit
.revwalk
.RevCommit
;
62 import org
.spearce
.jgit
.revwalk
.RevFlag
;
63 import org
.spearce
.jgit
.revwalk
.RevFlagSet
;
64 import org
.spearce
.jgit
.revwalk
.RevObject
;
65 import org
.spearce
.jgit
.revwalk
.RevTag
;
66 import org
.spearce
.jgit
.revwalk
.RevWalk
;
69 * Implements the server side of a fetch connection, transmitting objects.
71 public class UploadPack
{
72 static final String OPTION_INCLUDE_TAG
= BasePackFetchConnection
.OPTION_INCLUDE_TAG
;
74 static final String OPTION_MULTI_ACK
= BasePackFetchConnection
.OPTION_MULTI_ACK
;
76 static final String OPTION_THIN_PACK
= BasePackFetchConnection
.OPTION_THIN_PACK
;
78 static final String OPTION_SIDE_BAND
= BasePackFetchConnection
.OPTION_SIDE_BAND
;
80 static final String OPTION_SIDE_BAND_64K
= BasePackFetchConnection
.OPTION_SIDE_BAND_64K
;
82 static final String OPTION_OFS_DELTA
= BasePackFetchConnection
.OPTION_OFS_DELTA
;
84 static final String OPTION_NO_PROGRESS
= BasePackFetchConnection
.OPTION_NO_PROGRESS
;
86 /** Database we read the objects from. */
87 private final Repository db
;
89 /** Revision traversal support over {@link #db}. */
90 private final RevWalk walk
;
92 private InputStream rawIn
;
94 private OutputStream rawOut
;
96 private PacketLineIn pckIn
;
98 private PacketLineOut pckOut
;
100 /** The refs we advertised as existing at the start of the connection. */
101 private Map
<String
, Ref
> refs
;
103 /** Capabilities requested by the client. */
104 private final Set
<String
> options
= new HashSet
<String
>();
106 /** Objects the client wants to obtain. */
107 private final List
<RevObject
> wantAll
= new ArrayList
<RevObject
>();
109 /** Objects the client wants to obtain. */
110 private final List
<RevCommit
> wantCommits
= new ArrayList
<RevCommit
>();
112 /** Objects on both sides, these don't have to be sent. */
113 private final List
<RevObject
> commonBase
= new ArrayList
<RevObject
>();
115 /** Marked on objects we sent in our advertisement list. */
116 private final RevFlag ADVERTISED
;
118 /** Marked on objects the client has asked us to give them. */
119 private final RevFlag WANT
;
121 /** Marked on objects both we and the client have. */
122 private final RevFlag PEER_HAS
;
124 /** Marked on objects in {@link #commonBase}. */
125 private final RevFlag COMMON
;
127 private final RevFlagSet SAVE
;
129 private boolean multiAck
;
132 * Create a new pack upload for an open repository.
135 * the source repository.
137 public UploadPack(final Repository copyFrom
) {
139 walk
= new RevWalk(db
);
141 ADVERTISED
= walk
.newFlag("ADVERTISED");
142 WANT
= walk
.newFlag("WANT");
143 PEER_HAS
= walk
.newFlag("PEER_HAS");
144 COMMON
= walk
.newFlag("COMMON");
145 walk
.carry(PEER_HAS
);
147 SAVE
= new RevFlagSet();
148 SAVE
.add(ADVERTISED
);
153 /** @return the repository this receive completes into. */
154 public final Repository
getRepository() {
158 /** @return the RevWalk instance used by this connection. */
159 public final RevWalk
getRevWalk() {
164 * Execute the upload task on the socket.
167 * raw input to read client commands from. Caller must ensure the
168 * input is buffered, otherwise read performance may suffer.
170 * response back to the Git network client, to write the pack
171 * data onto. Caller must ensure the output is buffered,
172 * otherwise write performance may suffer.
174 * secondary "notice" channel to send additional messages out
175 * through. When run over SSH this should be tied back to the
176 * standard error channel of the command execution. For most
177 * other network connections this should be null.
178 * @throws IOException
180 public void upload(final InputStream input
, final OutputStream output
,
181 final OutputStream messages
) throws IOException
{
185 pckIn
= new PacketLineIn(rawIn
);
186 pckOut
= new PacketLineOut(rawOut
);
190 private void service() throws IOException
{
191 sendAdvertisedRefs();
193 if (wantAll
.isEmpty())
195 multiAck
= options
.contains(OPTION_MULTI_ACK
);
200 private void sendAdvertisedRefs() throws IOException
{
201 refs
= db
.getAllRefs();
203 final StringBuilder m
= new StringBuilder(100);
204 final char[] idtmp
= new char[2 * Constants
.OBJECT_ID_LENGTH
];
205 final Iterator
<Ref
> i
= RefComparator
.sort(refs
.values()).iterator();
207 final Ref r
= i
.next();
208 final RevObject o
= safeParseAny(r
.getObjectId());
210 advertise(m
, idtmp
, o
, r
.getOrigName());
213 m
.append(OPTION_INCLUDE_TAG
);
215 m
.append(OPTION_MULTI_ACK
);
217 m
.append(OPTION_OFS_DELTA
);
219 m
.append(OPTION_SIDE_BAND
);
221 m
.append(OPTION_SIDE_BAND_64K
);
223 m
.append(OPTION_THIN_PACK
);
225 m
.append(OPTION_NO_PROGRESS
);
227 writeAdvertisedRef(m
);
228 if (o
instanceof RevTag
)
229 writeAdvertisedTag(m
, idtmp
, o
, r
.getName());
232 while (i
.hasNext()) {
233 final Ref r
= i
.next();
234 final RevObject o
= safeParseAny(r
.getObjectId());
236 advertise(m
, idtmp
, o
, r
.getOrigName());
237 writeAdvertisedRef(m
);
238 if (o
instanceof RevTag
)
239 writeAdvertisedTag(m
, idtmp
, o
, r
.getName());
245 private RevObject
safeParseAny(final ObjectId id
) {
247 return walk
.parseAny(id
);
248 } catch (IOException e
) {
253 private void advertise(final StringBuilder m
, final char[] idtmp
,
254 final RevObject o
, final String name
) {
257 o
.getId().copyTo(idtmp
, m
);
262 private void writeAdvertisedRef(final StringBuilder m
) throws IOException
{
264 pckOut
.writeString(m
.toString());
267 private void writeAdvertisedTag(final StringBuilder m
, final char[] idtmp
,
268 final RevObject tag
, final String name
) throws IOException
{
270 while (o
instanceof RevTag
) {
271 // Fully unwrap here so later on we have these already parsed.
273 walk
.parse(((RevTag
) o
).getObject());
274 } catch (IOException err
) {
277 o
= ((RevTag
) o
).getObject();
280 advertise(m
, idtmp
, ((RevTag
) tag
).getObject(), name
+ "^{}");
281 writeAdvertisedRef(m
);
284 private void recvWants() throws IOException
{
285 boolean isFirst
= true;
286 for (;; isFirst
= false) {
289 line
= pckIn
.readString();
290 } catch (EOFException eof
) {
296 if (line
.length() == 0)
298 if (!line
.startsWith("want ") || line
.length() < 45)
299 throw new PackProtocolException("expected want; got " + line
);
302 final int sp
= line
.indexOf(' ', 45);
304 for (String c
: line
.substring(sp
+ 1).split(" "))
306 line
= line
.substring(0, sp
);
310 final ObjectId id
= ObjectId
.fromString(line
.substring(5));
313 o
= walk
.parseAny(id
);
314 } catch (IOException e
) {
315 throw new PackProtocolException(id
.name() + " not valid", e
);
317 if (!o
.has(ADVERTISED
))
318 throw new PackProtocolException(id
.name() + " not valid");
323 private void want(RevObject o
) {
328 if (o
instanceof RevCommit
)
329 wantCommits
.add((RevCommit
) o
);
331 else if (o
instanceof RevTag
) {
333 o
= ((RevTag
) o
).getObject();
334 } while (o
instanceof RevTag
);
335 if (o
instanceof RevCommit
)
341 private void negotiate() throws IOException
{
342 ObjectId last
= ObjectId
.zeroId();
346 line
= pckIn
.readString();
347 } catch (EOFException eof
) {
351 if (line
.length() == 0) {
352 if (commonBase
.isEmpty() || multiAck
)
353 pckOut
.writeString("NAK\n");
355 } else if (line
.startsWith("have ") && line
.length() == 45) {
356 final ObjectId id
= ObjectId
.fromString(line
.substring(5));
358 // Both sides have the same object; let the client know.
362 pckOut
.writeString("ACK " + id
.name() + " continue\n");
363 } else if (commonBase
.size() == 1)
364 pckOut
.writeString("ACK " + id
.name() + "\n");
366 // They have this object; we don't.
368 if (multiAck
&& okToGiveUp())
369 pckOut
.writeString("ACK " + id
.name() + " continue\n");
372 } else if (line
.equals("done")) {
373 if (commonBase
.isEmpty())
374 pckOut
.writeString("NAK\n");
377 pckOut
.writeString("ACK " + last
.name() + "\n");
381 throw new PackProtocolException("expected have; got " + line
);
386 private boolean matchHave(final ObjectId id
) {
389 o
= walk
.parseAny(id
);
390 } catch (IOException err
) {
394 if (!o
.has(PEER_HAS
)) {
396 if (o
instanceof RevCommit
)
397 ((RevCommit
) o
).carry(PEER_HAS
);
398 if (!o
.has(COMMON
)) {
406 private boolean okToGiveUp() throws PackProtocolException
{
407 if (commonBase
.isEmpty())
411 for (final Iterator
<RevCommit
> i
= wantCommits
.iterator(); i
413 final RevCommit want
= i
.next();
414 if (wantSatisfied(want
))
417 } catch (IOException e
) {
418 throw new PackProtocolException("internal revision error", e
);
420 return wantCommits
.isEmpty();
423 private boolean wantSatisfied(final RevCommit want
) throws IOException
{
424 walk
.resetRetain(SAVE
);
425 walk
.markStart(want
);
427 final RevCommit c
= walk
.next();
430 if (c
.has(PEER_HAS
)) {
431 if (!c
.has(COMMON
)) {
442 private void sendPack() throws IOException
{
443 final boolean thin
= options
.contains(OPTION_THIN_PACK
);
444 final boolean progress
= !options
.contains(OPTION_NO_PROGRESS
);
445 final boolean sideband
= options
.contains(OPTION_SIDE_BAND
)
446 || options
.contains(OPTION_SIDE_BAND_64K
);
448 ProgressMonitor pm
= NullProgressMonitor
.INSTANCE
;
449 OutputStream packOut
= rawOut
;
452 int bufsz
= SideBandOutputStream
.SMALL_BUF
;
453 if (options
.contains(OPTION_SIDE_BAND_64K
))
454 bufsz
= SideBandOutputStream
.MAX_BUF
;
455 bufsz
-= SideBandOutputStream
.HDR_SIZE
;
457 packOut
= new BufferedOutputStream(new SideBandOutputStream(
458 SideBandOutputStream
.CH_DATA
, pckOut
), bufsz
);
461 pm
= new SideBandProgressMonitor(pckOut
);
465 pw
= new PackWriter(db
, pm
, NullProgressMonitor
.INSTANCE
);
466 pw
.setDeltaBaseAsOffset(options
.contains(OPTION_OFS_DELTA
));
467 pw
.preparePack(wantAll
, commonBase
, thin
, true);
468 if (options
.contains(OPTION_INCLUDE_TAG
)) {
469 for (final Ref r
: refs
.values()) {
472 o
= walk
.parseAny(r
.getObjectId());
473 } catch (IOException e
) {
476 if (o
.has(WANT
) || !(o
instanceof RevTag
))
478 final RevTag t
= (RevTag
) o
;
479 if (!pw
.willInclude(t
) && pw
.willInclude(t
.getObject()))
483 pw
.writePack(packOut
);