2 * Copyright (C) 2008-2010, Google Inc.
3 * and other copyright owners as documented in the project's IP log.
5 * This program and the accompanying materials are made available
6 * under the terms of the Eclipse Distribution License v1.0 which
7 * accompanies this distribution, is reproduced below, and is
8 * available at http://www.eclipse.org/org/documents/edl-v10.php
10 * All rights reserved.
12 * Redistribution and use in source and binary forms, with or
13 * without modification, are permitted provided that the following
16 * - Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
19 * - Redistributions in binary form must reproduce the above
20 * copyright notice, this list of conditions and the following
21 * disclaimer in the documentation and/or other materials provided
22 * with the distribution.
24 * - Neither the name of the Eclipse Foundation, Inc. nor the
25 * names of its contributors may be used to endorse or promote
26 * products derived from this software without specific prior
29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44 package org
.eclipse
.jgit
.transport
;
46 import java
.io
.EOFException
;
47 import java
.io
.IOException
;
48 import java
.io
.InputStream
;
49 import java
.io
.OutputStream
;
50 import java
.util
.ArrayList
;
51 import java
.util
.HashSet
;
52 import java
.util
.Iterator
;
53 import java
.util
.List
;
57 import org
.eclipse
.jgit
.errors
.PackProtocolException
;
58 import org
.eclipse
.jgit
.lib
.NullProgressMonitor
;
59 import org
.eclipse
.jgit
.lib
.ObjectId
;
60 import org
.eclipse
.jgit
.lib
.PackWriter
;
61 import org
.eclipse
.jgit
.lib
.ProgressMonitor
;
62 import org
.eclipse
.jgit
.lib
.Ref
;
63 import org
.eclipse
.jgit
.lib
.Repository
;
64 import org
.eclipse
.jgit
.revwalk
.RevCommit
;
65 import org
.eclipse
.jgit
.revwalk
.RevFlag
;
66 import org
.eclipse
.jgit
.revwalk
.RevFlagSet
;
67 import org
.eclipse
.jgit
.revwalk
.RevObject
;
68 import org
.eclipse
.jgit
.revwalk
.RevTag
;
69 import org
.eclipse
.jgit
.revwalk
.RevWalk
;
70 import org
.eclipse
.jgit
.transport
.BasePackFetchConnection
.MultiAck
;
71 import org
.eclipse
.jgit
.transport
.RefAdvertiser
.PacketLineOutRefAdvertiser
;
72 import org
.eclipse
.jgit
.util
.io
.InterruptTimer
;
73 import org
.eclipse
.jgit
.util
.io
.TimeoutInputStream
;
74 import org
.eclipse
.jgit
.util
.io
.TimeoutOutputStream
;
77 * Implements the server side of a fetch connection, transmitting objects.
79 public class UploadPack
{
80 static final String OPTION_INCLUDE_TAG
= BasePackFetchConnection
.OPTION_INCLUDE_TAG
;
82 static final String OPTION_MULTI_ACK
= BasePackFetchConnection
.OPTION_MULTI_ACK
;
84 static final String OPTION_MULTI_ACK_DETAILED
= BasePackFetchConnection
.OPTION_MULTI_ACK_DETAILED
;
86 static final String OPTION_THIN_PACK
= BasePackFetchConnection
.OPTION_THIN_PACK
;
88 static final String OPTION_SIDE_BAND
= BasePackFetchConnection
.OPTION_SIDE_BAND
;
90 static final String OPTION_SIDE_BAND_64K
= BasePackFetchConnection
.OPTION_SIDE_BAND_64K
;
92 static final String OPTION_OFS_DELTA
= BasePackFetchConnection
.OPTION_OFS_DELTA
;
94 static final String OPTION_NO_PROGRESS
= BasePackFetchConnection
.OPTION_NO_PROGRESS
;
96 /** Database we read the objects from. */
97 private final Repository db
;
99 /** Revision traversal support over {@link #db}. */
100 private final RevWalk walk
;
102 /** Timeout in seconds to wait for client interaction. */
106 * Is the client connection a bi-directional socket or pipe?
108 * If true, this class assumes it can perform multiple read and write cycles
109 * with the client over the input and output streams. This matches the
110 * functionality available with a standard TCP/IP connection, or a local
111 * operating system or in-memory pipe.
113 * If false, this class runs in a read everything then output results mode,
114 * making it suitable for single round-trip systems RPCs such as HTTP.
116 private boolean biDirectionalPipe
= true;
118 /** Timer to manage {@link #timeout}. */
119 private InterruptTimer timer
;
121 private InputStream rawIn
;
123 private OutputStream rawOut
;
125 private PacketLineIn pckIn
;
127 private PacketLineOut pckOut
;
129 /** The refs we advertised as existing at the start of the connection. */
130 private Map
<String
, Ref
> refs
;
132 /** Filter used while advertising the refs to the client. */
133 private RefFilter refFilter
;
135 /** Capabilities requested by the client. */
136 private final Set
<String
> options
= new HashSet
<String
>();
138 /** Objects the client wants to obtain. */
139 private final List
<RevObject
> wantAll
= new ArrayList
<RevObject
>();
141 /** Objects the client wants to obtain. */
142 private final List
<RevCommit
> wantCommits
= new ArrayList
<RevCommit
>();
144 /** Objects on both sides, these don't have to be sent. */
145 private final List
<RevObject
> commonBase
= new ArrayList
<RevObject
>();
147 /** null if {@link #commonBase} should be examined again. */
148 private Boolean okToGiveUp
;
150 /** Marked on objects we sent in our advertisement list. */
151 private final RevFlag ADVERTISED
;
153 /** Marked on objects the client has asked us to give them. */
154 private final RevFlag WANT
;
156 /** Marked on objects both we and the client have. */
157 private final RevFlag PEER_HAS
;
159 /** Marked on objects in {@link #commonBase}. */
160 private final RevFlag COMMON
;
162 private final RevFlagSet SAVE
;
164 private MultiAck multiAck
= MultiAck
.OFF
;
167 * Create a new pack upload for an open repository.
170 * the source repository.
172 public UploadPack(final Repository copyFrom
) {
174 walk
= new RevWalk(db
);
175 walk
.setRetainBody(false);
177 ADVERTISED
= walk
.newFlag("ADVERTISED");
178 WANT
= walk
.newFlag("WANT");
179 PEER_HAS
= walk
.newFlag("PEER_HAS");
180 COMMON
= walk
.newFlag("COMMON");
181 walk
.carry(PEER_HAS
);
183 SAVE
= new RevFlagSet();
184 SAVE
.add(ADVERTISED
);
187 refFilter
= RefFilter
.DEFAULT
;
190 /** @return the repository this upload is reading from. */
191 public final Repository
getRepository() {
195 /** @return the RevWalk instance used by this connection. */
196 public final RevWalk
getRevWalk() {
200 /** @return timeout (in seconds) before aborting an IO operation. */
201 public int getTimeout() {
206 * Set the timeout before willing to abort an IO call.
209 * number of seconds to wait (with no data transfer occurring)
210 * before aborting an IO read or write operation with the
213 public void setTimeout(final int seconds
) {
218 * @return true if this class expects a bi-directional pipe opened between
219 * the client and itself. The default is true.
221 public boolean isBiDirectionalPipe() {
222 return biDirectionalPipe
;
227 * if true, this class will assume the socket is a fully
228 * bidirectional pipe between the two peers and takes advantage
229 * of that by first transmitting the known refs, then waiting to
230 * read commands. If false, this class assumes it must read the
231 * commands before writing output and does not perform the
232 * initial advertising.
234 public void setBiDirectionalPipe(final boolean twoWay
) {
235 biDirectionalPipe
= twoWay
;
238 /** @return the filter used while advertising the refs to the client */
239 public RefFilter
getRefFilter() {
244 * Set the filter used while advertising the refs to the client.
246 * Only refs allowed by this filter will be sent to the client. This can
247 * be used by a server to restrict the list of references the client can
248 * obtain through clone or fetch, effectively limiting the access to only
252 * the filter; may be null to show all refs.
254 public void setRefFilter(final RefFilter refFilter
) {
255 this.refFilter
= refFilter
!= null ? refFilter
: RefFilter
.DEFAULT
;
259 * Execute the upload task on the socket.
262 * raw input to read client commands from. Caller must ensure the
263 * input is buffered, otherwise read performance may suffer.
265 * response back to the Git network client, to write the pack
266 * data onto. Caller must ensure the output is buffered,
267 * otherwise write performance may suffer.
269 * secondary "notice" channel to send additional messages out
270 * through. When run over SSH this should be tied back to the
271 * standard error channel of the command execution. For most
272 * other network connections this should be null.
273 * @throws IOException
275 public void upload(final InputStream input
, final OutputStream output
,
276 final OutputStream messages
) throws IOException
{
282 final Thread caller
= Thread
.currentThread();
283 timer
= new InterruptTimer(caller
.getName() + "-Timer");
284 TimeoutInputStream i
= new TimeoutInputStream(rawIn
, timer
);
285 TimeoutOutputStream o
= new TimeoutOutputStream(rawOut
, timer
);
286 i
.setTimeout(timeout
* 1000);
287 o
.setTimeout(timeout
* 1000);
292 pckIn
= new PacketLineIn(rawIn
);
293 pckOut
= new PacketLineOut(rawOut
);
306 private void service() throws IOException
{
307 if (biDirectionalPipe
)
308 sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut
));
310 refs
= refFilter
.filter(db
.getAllRefs());
311 for (Ref r
: refs
.values()) {
313 walk
.parseAny(r
.getObjectId()).add(ADVERTISED
);
314 } catch (IOException e
) {
315 // Skip missing/corrupt objects
321 if (wantAll
.isEmpty())
324 if (options
.contains(OPTION_MULTI_ACK_DETAILED
))
325 multiAck
= MultiAck
.DETAILED
;
326 else if (options
.contains(OPTION_MULTI_ACK
))
327 multiAck
= MultiAck
.CONTINUE
;
329 multiAck
= MultiAck
.OFF
;
336 * Generate an advertisement of available refs and capabilities.
339 * the advertisement formatter.
340 * @throws IOException
341 * the formatter failed to write an advertisement.
343 public void sendAdvertisedRefs(final RefAdvertiser adv
) throws IOException
{
344 adv
.init(walk
, ADVERTISED
);
345 adv
.advertiseCapability(OPTION_INCLUDE_TAG
);
346 adv
.advertiseCapability(OPTION_MULTI_ACK_DETAILED
);
347 adv
.advertiseCapability(OPTION_MULTI_ACK
);
348 adv
.advertiseCapability(OPTION_OFS_DELTA
);
349 adv
.advertiseCapability(OPTION_SIDE_BAND
);
350 adv
.advertiseCapability(OPTION_SIDE_BAND_64K
);
351 adv
.advertiseCapability(OPTION_THIN_PACK
);
352 adv
.advertiseCapability(OPTION_NO_PROGRESS
);
353 adv
.setDerefTags(true);
354 refs
= refFilter
.filter(db
.getAllRefs());
359 private void recvWants() throws IOException
{
360 boolean isFirst
= true;
361 for (;; isFirst
= false) {
364 line
= pckIn
.readString();
365 } catch (EOFException eof
) {
371 if (line
== PacketLineIn
.END
)
373 if (!line
.startsWith("want ") || line
.length() < 45)
374 throw new PackProtocolException("expected want; got " + line
);
376 if (isFirst
&& line
.length() > 45) {
377 String opt
= line
.substring(45);
378 if (opt
.startsWith(" "))
379 opt
= opt
.substring(1);
380 for (String c
: opt
.split(" "))
382 line
= line
.substring(0, 45);
385 final ObjectId id
= ObjectId
.fromString(line
.substring(5));
388 o
= walk
.parseAny(id
);
389 } catch (IOException e
) {
390 throw new PackProtocolException(id
.name() + " not valid", e
);
392 if (!o
.has(ADVERTISED
))
393 throw new PackProtocolException(id
.name() + " not valid");
398 private void want(RevObject o
) {
403 if (o
instanceof RevCommit
)
404 wantCommits
.add((RevCommit
) o
);
406 else if (o
instanceof RevTag
) {
408 o
= ((RevTag
) o
).getObject();
409 } while (o
instanceof RevTag
);
410 if (o
instanceof RevCommit
)
416 private boolean negotiate() throws IOException
{
417 ObjectId last
= ObjectId
.zeroId();
421 line
= pckIn
.readString();
422 } catch (EOFException eof
) {
426 if (line
== PacketLineIn
.END
) {
427 if (commonBase
.isEmpty() || multiAck
!= MultiAck
.OFF
)
428 pckOut
.writeString("NAK\n");
430 if (!biDirectionalPipe
)
433 } else if (line
.startsWith("have ") && line
.length() == 45) {
434 final ObjectId id
= ObjectId
.fromString(line
.substring(5));
436 // Both sides have the same object; let the client know.
441 if (commonBase
.size() == 1)
442 pckOut
.writeString("ACK " + id
.name() + "\n");
445 pckOut
.writeString("ACK " + id
.name() + " continue\n");
448 pckOut
.writeString("ACK " + id
.name() + " common\n");
451 } else if (okToGiveUp()) {
452 // They have this object; we don't.
458 pckOut
.writeString("ACK " + id
.name() + " continue\n");
461 pckOut
.writeString("ACK " + id
.name() + " ready\n");
466 } else if (line
.equals("done")) {
467 if (commonBase
.isEmpty())
468 pckOut
.writeString("NAK\n");
470 else if (multiAck
!= MultiAck
.OFF
)
471 pckOut
.writeString("ACK " + last
.name() + "\n");
476 throw new PackProtocolException("expected have; got " + line
);
481 private boolean matchHave(final ObjectId id
) {
484 o
= walk
.parseAny(id
);
485 } catch (IOException err
) {
489 if (!o
.has(PEER_HAS
)) {
491 if (o
instanceof RevCommit
)
492 ((RevCommit
) o
).carry(PEER_HAS
);
498 private void addCommonBase(final RevObject o
) {
499 if (!o
.has(COMMON
)) {
506 private boolean okToGiveUp() throws PackProtocolException
{
507 if (okToGiveUp
== null)
508 okToGiveUp
= Boolean
.valueOf(okToGiveUpImp());
509 return okToGiveUp
.booleanValue();
512 private boolean okToGiveUpImp() throws PackProtocolException
{
513 if (commonBase
.isEmpty())
517 for (final Iterator
<RevCommit
> i
= wantCommits
.iterator(); i
519 final RevCommit want
= i
.next();
520 if (wantSatisfied(want
))
523 } catch (IOException e
) {
524 throw new PackProtocolException("internal revision error", e
);
526 return wantCommits
.isEmpty();
529 private boolean wantSatisfied(final RevCommit want
) throws IOException
{
530 walk
.resetRetain(SAVE
);
531 walk
.markStart(want
);
533 final RevCommit c
= walk
.next();
536 if (c
.has(PEER_HAS
)) {
544 private void sendPack() throws IOException
{
545 final boolean thin
= options
.contains(OPTION_THIN_PACK
);
546 final boolean progress
= !options
.contains(OPTION_NO_PROGRESS
);
547 final boolean sideband
= options
.contains(OPTION_SIDE_BAND
)
548 || options
.contains(OPTION_SIDE_BAND_64K
);
550 ProgressMonitor pm
= NullProgressMonitor
.INSTANCE
;
551 OutputStream packOut
= rawOut
;
554 int bufsz
= SideBandOutputStream
.SMALL_BUF
;
555 if (options
.contains(OPTION_SIDE_BAND_64K
))
556 bufsz
= SideBandOutputStream
.MAX_BUF
;
558 packOut
= new SideBandOutputStream(SideBandOutputStream
.CH_DATA
,
561 pm
= new SideBandProgressMonitor(new SideBandOutputStream(
562 SideBandOutputStream
.CH_PROGRESS
, bufsz
, rawOut
));
566 pw
= new PackWriter(db
, pm
, NullProgressMonitor
.INSTANCE
);
567 pw
.setDeltaBaseAsOffset(options
.contains(OPTION_OFS_DELTA
));
569 pw
.preparePack(wantAll
, commonBase
);
570 if (options
.contains(OPTION_INCLUDE_TAG
)) {
571 for (final Ref r
: refs
.values()) {
574 o
= walk
.parseAny(r
.getObjectId());
575 } catch (IOException e
) {
578 if (o
.has(WANT
) || !(o
instanceof RevTag
))
580 final RevTag t
= (RevTag
) o
;
581 if (!pw
.willInclude(t
) && pw
.willInclude(t
.getObject()))
585 pw
.writePack(packOut
);