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 /** null if {@link #commonBase} should be examined again. */
116 private Boolean okToGiveUp
;
118 /** Marked on objects we sent in our advertisement list. */
119 private final RevFlag ADVERTISED
;
121 /** Marked on objects the client has asked us to give them. */
122 private final RevFlag WANT
;
124 /** Marked on objects both we and the client have. */
125 private final RevFlag PEER_HAS
;
127 /** Marked on objects in {@link #commonBase}. */
128 private final RevFlag COMMON
;
130 private final RevFlagSet SAVE
;
132 private boolean multiAck
;
135 * Create a new pack upload for an open repository.
138 * the source repository.
140 public UploadPack(final Repository copyFrom
) {
142 walk
= new RevWalk(db
);
143 walk
.setRetainBody(false);
145 ADVERTISED
= walk
.newFlag("ADVERTISED");
146 WANT
= walk
.newFlag("WANT");
147 PEER_HAS
= walk
.newFlag("PEER_HAS");
148 COMMON
= walk
.newFlag("COMMON");
149 walk
.carry(PEER_HAS
);
151 SAVE
= new RevFlagSet();
152 SAVE
.add(ADVERTISED
);
157 /** @return the repository this receive completes into. */
158 public final Repository
getRepository() {
162 /** @return the RevWalk instance used by this connection. */
163 public final RevWalk
getRevWalk() {
168 * Execute the upload task on the socket.
171 * raw input to read client commands from. Caller must ensure the
172 * input is buffered, otherwise read performance may suffer.
174 * response back to the Git network client, to write the pack
175 * data onto. Caller must ensure the output is buffered,
176 * otherwise write performance may suffer.
178 * secondary "notice" channel to send additional messages out
179 * through. When run over SSH this should be tied back to the
180 * standard error channel of the command execution. For most
181 * other network connections this should be null.
182 * @throws IOException
184 public void upload(final InputStream input
, final OutputStream output
,
185 final OutputStream messages
) throws IOException
{
189 pckIn
= new PacketLineIn(rawIn
);
190 pckOut
= new PacketLineOut(rawOut
);
194 private void service() throws IOException
{
195 sendAdvertisedRefs();
197 if (wantAll
.isEmpty())
199 multiAck
= options
.contains(OPTION_MULTI_ACK
);
204 private void sendAdvertisedRefs() throws IOException
{
205 refs
= db
.getAllRefs();
207 final StringBuilder m
= new StringBuilder(100);
208 final char[] idtmp
= new char[2 * Constants
.OBJECT_ID_LENGTH
];
209 final Iterator
<Ref
> i
= RefComparator
.sort(refs
.values()).iterator();
211 final Ref r
= i
.next();
212 final RevObject o
= safeParseAny(r
.getObjectId());
214 advertise(m
, idtmp
, o
, r
.getOrigName());
217 m
.append(OPTION_INCLUDE_TAG
);
219 m
.append(OPTION_MULTI_ACK
);
221 m
.append(OPTION_OFS_DELTA
);
223 m
.append(OPTION_SIDE_BAND
);
225 m
.append(OPTION_SIDE_BAND_64K
);
227 m
.append(OPTION_THIN_PACK
);
229 m
.append(OPTION_NO_PROGRESS
);
231 writeAdvertisedRef(m
);
232 if (o
instanceof RevTag
)
233 writeAdvertisedTag(m
, idtmp
, o
, r
.getName());
236 while (i
.hasNext()) {
237 final Ref r
= i
.next();
238 final RevObject o
= safeParseAny(r
.getObjectId());
240 advertise(m
, idtmp
, o
, r
.getOrigName());
241 writeAdvertisedRef(m
);
242 if (o
instanceof RevTag
)
243 writeAdvertisedTag(m
, idtmp
, o
, r
.getName());
249 private RevObject
safeParseAny(final ObjectId id
) {
251 return walk
.parseAny(id
);
252 } catch (IOException e
) {
257 private void advertise(final StringBuilder m
, final char[] idtmp
,
258 final RevObject o
, final String name
) {
261 o
.getId().copyTo(idtmp
, m
);
266 private void writeAdvertisedRef(final StringBuilder m
) throws IOException
{
268 pckOut
.writeString(m
.toString());
271 private void writeAdvertisedTag(final StringBuilder m
, final char[] idtmp
,
272 final RevObject tag
, final String name
) throws IOException
{
274 while (o
instanceof RevTag
) {
275 // Fully unwrap here so later on we have these already parsed.
277 walk
.parseHeaders(((RevTag
) o
).getObject());
278 } catch (IOException err
) {
281 o
= ((RevTag
) o
).getObject();
284 advertise(m
, idtmp
, ((RevTag
) tag
).getObject(), name
+ "^{}");
285 writeAdvertisedRef(m
);
288 private void recvWants() throws IOException
{
289 boolean isFirst
= true;
290 for (;; isFirst
= false) {
293 line
= pckIn
.readString();
294 } catch (EOFException eof
) {
300 if (line
== PacketLineIn
.END
)
302 if (!line
.startsWith("want ") || line
.length() < 45)
303 throw new PackProtocolException("expected want; got " + line
);
306 final int sp
= line
.indexOf(' ', 45);
308 for (String c
: line
.substring(sp
+ 1).split(" "))
310 line
= line
.substring(0, sp
);
314 final ObjectId id
= ObjectId
.fromString(line
.substring(5));
317 o
= walk
.parseAny(id
);
318 } catch (IOException e
) {
319 throw new PackProtocolException(id
.name() + " not valid", e
);
321 if (!o
.has(ADVERTISED
))
322 throw new PackProtocolException(id
.name() + " not valid");
327 private void want(RevObject o
) {
332 if (o
instanceof RevCommit
)
333 wantCommits
.add((RevCommit
) o
);
335 else if (o
instanceof RevTag
) {
337 o
= ((RevTag
) o
).getObject();
338 } while (o
instanceof RevTag
);
339 if (o
instanceof RevCommit
)
345 private void negotiate() throws IOException
{
346 ObjectId last
= ObjectId
.zeroId();
350 line
= pckIn
.readString();
351 } catch (EOFException eof
) {
355 if (line
== PacketLineIn
.END
) {
356 if (commonBase
.isEmpty() || multiAck
)
357 pckOut
.writeString("NAK\n");
359 } else if (line
.startsWith("have ") && line
.length() == 45) {
360 final ObjectId id
= ObjectId
.fromString(line
.substring(5));
362 // Both sides have the same object; let the client know.
366 pckOut
.writeString("ACK " + id
.name() + " continue\n");
367 } else if (commonBase
.size() == 1)
368 pckOut
.writeString("ACK " + id
.name() + "\n");
370 // They have this object; we don't.
372 if (multiAck
&& okToGiveUp())
373 pckOut
.writeString("ACK " + id
.name() + " continue\n");
376 } else if (line
.equals("done")) {
377 if (commonBase
.isEmpty())
378 pckOut
.writeString("NAK\n");
381 pckOut
.writeString("ACK " + last
.name() + "\n");
385 throw new PackProtocolException("expected have; got " + line
);
390 private boolean matchHave(final ObjectId id
) {
393 o
= walk
.parseAny(id
);
394 } catch (IOException err
) {
398 if (!o
.has(PEER_HAS
)) {
400 if (o
instanceof RevCommit
)
401 ((RevCommit
) o
).carry(PEER_HAS
);
407 private void addCommonBase(final RevObject o
) {
408 if (!o
.has(COMMON
)) {
415 private boolean okToGiveUp() throws PackProtocolException
{
416 if (okToGiveUp
== null)
417 okToGiveUp
= Boolean
.valueOf(okToGiveUpImp());
418 return okToGiveUp
.booleanValue();
421 private boolean okToGiveUpImp() throws PackProtocolException
{
422 if (commonBase
.isEmpty())
426 for (final Iterator
<RevCommit
> i
= wantCommits
.iterator(); i
428 final RevCommit want
= i
.next();
429 if (wantSatisfied(want
))
432 } catch (IOException e
) {
433 throw new PackProtocolException("internal revision error", e
);
435 return wantCommits
.isEmpty();
438 private boolean wantSatisfied(final RevCommit want
) throws IOException
{
439 walk
.resetRetain(SAVE
);
440 walk
.markStart(want
);
442 final RevCommit c
= walk
.next();
445 if (c
.has(PEER_HAS
)) {
453 private void sendPack() throws IOException
{
454 final boolean thin
= options
.contains(OPTION_THIN_PACK
);
455 final boolean progress
= !options
.contains(OPTION_NO_PROGRESS
);
456 final boolean sideband
= options
.contains(OPTION_SIDE_BAND
)
457 || options
.contains(OPTION_SIDE_BAND_64K
);
459 ProgressMonitor pm
= NullProgressMonitor
.INSTANCE
;
460 OutputStream packOut
= rawOut
;
463 int bufsz
= SideBandOutputStream
.SMALL_BUF
;
464 if (options
.contains(OPTION_SIDE_BAND_64K
))
465 bufsz
= SideBandOutputStream
.MAX_BUF
;
466 bufsz
-= SideBandOutputStream
.HDR_SIZE
;
468 packOut
= new BufferedOutputStream(new SideBandOutputStream(
469 SideBandOutputStream
.CH_DATA
, pckOut
), bufsz
);
472 pm
= new SideBandProgressMonitor(pckOut
);
476 pw
= new PackWriter(db
, pm
, NullProgressMonitor
.INSTANCE
);
477 pw
.setDeltaBaseAsOffset(options
.contains(OPTION_OFS_DELTA
));
479 pw
.preparePack(wantAll
, commonBase
);
480 if (options
.contains(OPTION_INCLUDE_TAG
)) {
481 for (final Ref r
: refs
.values()) {
484 o
= walk
.parseAny(r
.getObjectId());
485 } catch (IOException e
) {
488 if (o
.has(WANT
) || !(o
instanceof RevTag
))
490 final RevTag t
= (RevTag
) o
;
491 if (!pw
.willInclude(t
) && pw
.willInclude(t
.getObject()))
495 pw
.writePack(packOut
);