2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
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
.BufferedReader
;
41 import java
.io
.ByteArrayOutputStream
;
42 import java
.io
.FileNotFoundException
;
43 import java
.io
.IOException
;
44 import java
.io
.InputStream
;
45 import java
.io
.InputStreamReader
;
46 import java
.io
.OutputStream
;
47 import java
.io
.StringWriter
;
48 import java
.util
.ArrayList
;
49 import java
.util
.Collection
;
52 import org
.spearce
.jgit
.errors
.TransportException
;
53 import org
.spearce
.jgit
.lib
.Constants
;
54 import org
.spearce
.jgit
.lib
.ObjectId
;
55 import org
.spearce
.jgit
.lib
.Ref
;
56 import org
.spearce
.jgit
.util
.NB
;
59 * Transfers object data through a dumb transport.
61 * Implementations are responsible for resolving path names relative to the
62 * <code>objects/</code> subdirectory of a single remote Git repository or
63 * nake object database and make the content available as a Java input stream
64 * for reading during fetch. The actual object traversal logic to determine the
65 * names of files to retrieve is handled through the generic, protocol
66 * independent {@link WalkFetchConnection}.
68 abstract class WalkRemoteObjectDatabase
{
69 static final String CHARENC
= Constants
.CHARACTER_ENCODING
;
71 static final String INFO_PACKS
= "info/packs";
73 static final String INFO_ALTERNATES
= "info/alternates";
75 static final String INFO_HTTP_ALTERNATES
= "info/http-alternates";
77 static final String INFO_REFS
= "../info/refs";
79 static final String PACKED_REFS
= "../packed-refs";
81 abstract URIish
getURI();
84 * Obtain the list of available packs (if any).
86 * Pack names should be the file name in the packs directory, that is
87 * <code>pack-035760ab452d6eebd123add421f253ce7682355a.pack</code>. Index
88 * names should not be included in the returned collection.
90 * @return list of pack names; null or empty list if none are available.
92 * The connection is unable to read the remote repository's list
93 * of available pack files.
95 abstract Collection
<String
> getPackNames() throws IOException
;
98 * Obtain alternate connections to alternate object databases (if any).
100 * Alternates are typically read from the file {@link #INFO_ALTERNATES} or
101 * {@link #INFO_HTTP_ALTERNATES}. The content of each line must be resolved
102 * by the implementation and a new database reference should be returned to
103 * represent the additional location.
105 * Alternates may reuse the same network connection handle, however the
106 * fetch connection will {@link #close()} each created alternate.
108 * @return list of additional object databases the caller could fetch from;
109 * null or empty list if none are configured.
110 * @throws IOException
111 * The connection is unable to read the remote repository's list
112 * of configured alternates.
114 abstract Collection
<WalkRemoteObjectDatabase
> getAlternates()
118 * Open a single file for reading.
120 * Implementors should make every attempt possible to ensure
121 * {@link FileNotFoundException} is used when the remote object does not
122 * exist. However when fetching over HTTP some misconfigured servers may
123 * generate a 200 OK status message (rather than a 404 Not Found) with an
124 * HTML formatted message explaining the requested resource does not exist.
125 * Callers such as {@link WalkFetchConnection} are prepared to handle this
126 * by validating the content received, and assuming content that fails to
127 * match its hash is an incorrectly phrased FileNotFoundException.
130 * location of the file to read, relative to this objects
132 * <code>cb/95df6ab7ae9e57571511ef451cf33767c26dd2</code> or
133 * <code>pack/pack-035760ab452d6eebd123add421f253ce7682355a.pack</code>).
134 * @return a stream to read from the file. Never null.
135 * @throws FileNotFoundException
136 * the requested file does not exist at the given location.
137 * @throws IOException
138 * The connection is unable to read the remote's file, and the
139 * failure occurred prior to being able to determine if the file
140 * exists, or after it was determined to exist but before the
141 * stream could be created.
143 abstract FileStream
open(String path
) throws FileNotFoundException
,
147 * Create a new connection for a discovered alternate object database
149 * This method is typically called by {@link #readAlternates(String)} when
150 * subclasses us the generic alternate parsing logic for their
151 * implementation of {@link #getAlternates()}.
154 * the location of the new alternate, relative to the current
156 * @return a new database connection that can read from the specified
158 * @throws IOException
159 * The database connection cannot be established with the
160 * alternate, such as if the alternate location does not
161 * actually exist and the connection's constructor attempts to
164 abstract WalkRemoteObjectDatabase
openAlternate(String location
)
168 * Close any resources used by this connection.
170 * If the remote repository is contacted by a network socket this method
171 * must close that network socket, disconnecting the two peers. If the
172 * remote repository is actually local (same system) this method must close
173 * any open file handles used to read the "remote" repository.
175 abstract void close();
178 * Delete a file from the object database.
180 * Path may start with <code>../</code> to request deletion of a file that
181 * resides in the repository itself.
183 * When possible empty directories must be removed, up to but not including
184 * the current object database directory itself.
186 * This method does not support deletion of directories.
189 * name of the item to be removed, relative to the current object
191 * @throws IOException
192 * deletion is not supported, or deletion failed.
194 void deleteFile(final String path
) throws IOException
{
195 throw new IOException("Deleting '" + path
+ "' not supported.");
199 * Open a remote file for writing.
201 * Path may start with <code>../</code> to request writing of a file that
202 * resides in the repository itself.
204 * The requested path may or may not exist. If the path already exists as a
205 * file the file should be truncated and completely replaced.
207 * This method creates any missing parent directories, if necessary.
210 * name of the file to write, relative to the current object
212 * @return stream to write into this file. Caller must close the stream to
213 * complete the write request. The stream is not buffered and each
214 * write may cause a network request/response so callers should
215 * buffer to smooth out small writes.
216 * @throws IOException
217 * writing is not supported, or attempting to write the file
218 * failed, possibly due to permissions or remote disk full, etc.
220 OutputStream
writeFile(final String path
) throws IOException
{
221 throw new IOException("Writing of '" + path
+ "' not supported.");
225 * Atomically write a remote file.
227 * This method attempts to perform as atomic of an update as it can,
228 * reducing (or eliminating) the time that clients might be able to see
229 * partial file content. This method is not suitable for very large
230 * transfers as the complete content must be passed as an argument.
232 * Path may start with <code>../</code> to request writing of a file that
233 * resides in the repository itself.
235 * The requested path may or may not exist. If the path already exists as a
236 * file the file should be truncated and completely replaced.
238 * This method creates any missing parent directories, if necessary.
241 * name of the file to write, relative to the current object
244 * complete new content of the file.
245 * @throws IOException
246 * writing is not supported, or attempting to write the file
247 * failed, possibly due to permissions or remote disk full, etc.
249 void writeFile(final String path
, final byte[] data
) throws IOException
{
250 final OutputStream os
= writeFile(path
);
259 * Delete a loose ref from the remote repository.
262 * name of the ref within the ref space, for example
263 * <code>refs/heads/pu</code>.
264 * @throws IOException
265 * deletion is not supported, or deletion failed.
267 void deleteRef(final String name
) throws IOException
{
268 deleteFile("../" + name
);
272 * Overwrite (or create) a loose ref in the remote repository.
274 * This method creates any missing parent directories, if necessary.
277 * name of the ref within the ref space, for example
278 * <code>refs/heads/pu</code>.
280 * new value to store in this ref. Must not be null.
281 * @throws IOException
282 * writing is not supported, or attempting to write the file
283 * failed, possibly due to permissions or remote disk full, etc.
285 void writeRef(final String name
, final ObjectId value
) throws IOException
{
286 final ByteArrayOutputStream b
;
288 b
= new ByteArrayOutputStream(Constants
.OBJECT_ID_LENGTH
* 2 + 1);
292 writeFile("../" + name
, b
.toByteArray());
296 * Rebuild the {@link #INFO_PACKS} for dumb transport clients.
298 * This method rebuilds the contents of the {@link #INFO_PACKS} file to
299 * match the passed list of pack names.
302 * names of available pack files, in the order they should appear
303 * in the file. Valid pack name strings are of the form
304 * <code>pack-035760ab452d6eebd123add421f253ce7682355a.pack</code>.
305 * @throws IOException
306 * writing is not supported, or attempting to write the file
307 * failed, possibly due to permissions or remote disk full, etc.
309 void writeInfoPacks(final Collection
<String
> packNames
) throws IOException
{
310 final StringBuilder w
= new StringBuilder();
311 for (final String n
: packNames
) {
316 writeFile(INFO_PACKS
, Constants
.encodeASCII(w
.toString()));
320 * Rebuild the {@link #INFO_REFS} for dumb transport clients.
322 * This method rebuilds the contents of the {@link #INFO_REFS} file to match
323 * the passed list of references.
326 * the complete set of references the remote side now has. This
327 * should have been computed by applying updates to the
328 * advertised refs already discovered.
329 * @throws IOException
330 * writing is not supported, or attempting to write the file
331 * failed, possibly due to permissions or remote disk full, etc.
333 void writeInfoRefs(final Collection
<Ref
> refs
) throws IOException
{
334 final StringWriter w
= new StringWriter();
335 final char[] tmp
= new char[Constants
.OBJECT_ID_LENGTH
* 2];
336 for (final Ref r
: refs
) {
337 if (Constants
.HEAD
.equals(r
.getName())) {
338 // Historically HEAD has never been published through
339 // the INFO_REFS file. This is a mistake, but its the
345 r
.getObjectId().copyTo(tmp
, w
);
347 w
.write(r
.getName());
350 if (r
.getPeeledObjectId() != null) {
351 r
.getPeeledObjectId().copyTo(tmp
, w
);
353 w
.write(r
.getName());
357 writeFile(INFO_REFS
, Constants
.encodeASCII(w
.toString()));
361 * Rebuild the {@link #PACKED_REFS} file.
363 * This method rebuilds the contents of the {@link #PACKED_REFS} file to
364 * match the passed list of references, including only those refs that have
365 * a storage type of {@link Ref.Storage#PACKED}.
368 * the complete set of references the remote side now has. This
369 * should have been computed by applying updates to the
370 * advertised refs already discovered.
371 * @throws IOException
372 * writing is not supported, or attempting to write the file
373 * failed, possibly due to permissions or remote disk full, etc.
375 void writePackedRefs(final Collection
<Ref
> refs
) throws IOException
{
376 boolean peeled
= false;
378 for (final Ref r
: refs
) {
379 if (r
.getStorage() != Ref
.Storage
.PACKED
)
381 if (r
.getPeeledObjectId() != null)
385 final StringWriter w
= new StringWriter();
387 w
.write("# pack-refs with:");
393 final char[] tmp
= new char[Constants
.OBJECT_ID_LENGTH
* 2];
394 for (final Ref r
: refs
) {
395 if (r
.getStorage() != Ref
.Storage
.PACKED
)
398 r
.getObjectId().copyTo(tmp
, w
);
400 w
.write(r
.getName());
403 if (r
.getPeeledObjectId() != null) {
405 r
.getPeeledObjectId().copyTo(tmp
, w
);
409 writeFile(PACKED_REFS
, Constants
.encodeASCII(w
.toString()));
413 * Open a buffered reader around a file.
415 * This is shorthand for calling {@link #open(String)} and then wrapping it
416 * in a reader suitable for line oriented files like the alternates list.
418 * @return a stream to read from the file. Never null.
420 * location of the file to read, relative to this objects
421 * directory (e.g. <code>info/packs</code>).
422 * @throws FileNotFoundException
423 * the requested file does not exist at the given location.
424 * @throws IOException
425 * The connection is unable to read the remote's file, and the
426 * failure occurred prior to being able to determine if the file
427 * exists, or after it was determined to exist but before the
428 * stream could be created.
430 BufferedReader
openReader(final String path
) throws IOException
{
431 return new BufferedReader(new InputStreamReader(open(path
).in
, CHARENC
));
435 * Read a standard Git alternates file to discover other object databases.
437 * This method is suitable for reading the standard formats of the
438 * alternates file, such as found in <code>objects/info/alternates</code>
439 * or <code>objects/info/http-alternates</code> within a Git repository.
441 * Alternates appear one per line, with paths expressed relative to this
445 * location of the alternate file to read, relative to this
446 * object database (e.g. <code>info/alternates</code>).
447 * @return the list of discovered alternates. Empty list if the file exists,
448 * but no entries were discovered.
449 * @throws FileNotFoundException
450 * the requested file does not exist at the given location.
451 * @throws IOException
452 * The connection is unable to read the remote's file, and the
453 * failure occurred prior to being able to determine if the file
454 * exists, or after it was determined to exist but before the
455 * stream could be created.
457 Collection
<WalkRemoteObjectDatabase
> readAlternates(final String listPath
)
459 final BufferedReader br
= openReader(listPath
);
461 final Collection
<WalkRemoteObjectDatabase
> alts
= new ArrayList
<WalkRemoteObjectDatabase
>();
463 String line
= br
.readLine();
466 if (!line
.endsWith("/"))
468 alts
.add(openAlternate(line
));
477 * Read a standard Git packed-refs file to discover known references.
480 * return collection of references. Any existing entries will be
481 * replaced if they are found in the packed-refs file.
482 * @throws TransportException
483 * an error occurred reading from the packed refs file.
485 protected void readPackedRefs(final Map
<String
, Ref
> avail
)
486 throws TransportException
{
488 final BufferedReader br
= openReader(PACKED_REFS
);
490 readPackedRefsImpl(avail
, br
);
494 } catch (FileNotFoundException notPacked
) {
495 // Perhaps it wasn't worthwhile, or is just an older repository.
496 } catch (IOException e
) {
497 throw new TransportException(getURI(), "error in packed-refs", e
);
501 private void readPackedRefsImpl(final Map
<String
, Ref
> avail
,
502 final BufferedReader br
) throws IOException
{
505 String line
= br
.readLine();
508 if (line
.charAt(0) == '#')
510 if (line
.charAt(0) == '^') {
512 throw new TransportException("Peeled line before ref.");
513 final ObjectId id
= ObjectId
.fromString(line
+ 1);
514 last
= new Ref(Ref
.Storage
.PACKED
, last
.getName(), last
516 avail
.put(last
.getName(), last
);
520 final int sp
= line
.indexOf(' ');
522 throw new TransportException("Unrecognized ref: " + line
);
523 final ObjectId id
= ObjectId
.fromString(line
.substring(0, sp
));
524 final String name
= line
.substring(sp
+ 1);
525 last
= new Ref(Ref
.Storage
.PACKED
, name
, id
);
526 avail
.put(last
.getName(), last
);
530 static final class FileStream
{
531 final InputStream in
;
536 * Create a new stream of unknown length.
539 * stream containing the file data. This stream will be
540 * closed by the caller when reading is complete.
542 FileStream(final InputStream i
) {
548 * Create a new stream of known length.
551 * stream containing the file data. This stream will be
552 * closed by the caller when reading is complete.
554 * total number of bytes available for reading through
557 FileStream(final InputStream i
, final long n
) {
562 byte[] toArray() throws IOException
{
565 final byte[] r
= new byte[(int) length
];
566 NB
.readFully(in
, r
, 0, r
.length
);
570 final ByteArrayOutputStream r
= new ByteArrayOutputStream();
571 final byte[] buf
= new byte[2048];
573 while ((n
= in
.read(buf
)) >= 0)
575 return r
.toByteArray();