Delete reflog when deleting ref during dumb transport push
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / transport / WalkRemoteObjectDatabase.java
blob915faac9eb85e59c0ed2c08b9631d03cbc4c6bf8
1 /*
2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
8 * conditions are met:
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
21 * written permission.
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;
50 import java.util.Map;
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;
58 /**
59 * Transfers object data through a dumb transport.
60 * <p>
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();
83 /**
84 * Obtain the list of available packs (if any).
85 * <p>
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.
91 * @throws IOException
92 * The connection is unable to read the remote repository's list
93 * of available pack files.
95 abstract Collection<String> getPackNames() throws IOException;
97 /**
98 * Obtain alternate connections to alternate object databases (if any).
99 * <p>
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.
104 * <p>
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()
115 throws IOException;
118 * Open a single file for reading.
119 * <p>
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.
129 * @param path
130 * location of the file to read, relative to this objects
131 * directory (e.g.
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,
144 IOException;
147 * Create a new connection for a discovered alternate object database
148 * <p>
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()}.
153 * @param location
154 * the location of the new alternate, relative to the current
155 * object database.
156 * @return a new database connection that can read from the specified
157 * alternate.
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
162 * verify that.
164 abstract WalkRemoteObjectDatabase openAlternate(String location)
165 throws IOException;
168 * Close any resources used by this connection.
169 * <p>
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.
179 * <p>
180 * Path may start with <code>../</code> to request deletion of a file that
181 * resides in the repository itself.
182 * <p>
183 * When possible empty directories must be removed, up to but not including
184 * the current object database directory itself.
185 * <p>
186 * This method does not support deletion of directories.
188 * @param path
189 * name of the item to be removed, relative to the current object
190 * database.
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.
200 * <p>
201 * Path may start with <code>../</code> to request writing of a file that
202 * resides in the repository itself.
203 * <p>
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.
206 * <p>
207 * This method creates any missing parent directories, if necessary.
209 * @param path
210 * name of the file to write, relative to the current object
211 * database.
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.
226 * <p>
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.
231 * <p>
232 * Path may start with <code>../</code> to request writing of a file that
233 * resides in the repository itself.
234 * <p>
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.
237 * <p>
238 * This method creates any missing parent directories, if necessary.
240 * @param path
241 * name of the file to write, relative to the current object
242 * database.
243 * @param data
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);
251 try {
252 os.write(data);
253 } finally {
254 os.close();
259 * Delete a loose ref from the remote repository.
261 * @param name
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 * Delete a reflog from the remote repository.
274 * @param name
275 * name of the ref within the ref space, for example
276 * <code>refs/heads/pu</code>.
277 * @throws IOException
278 * deletion is not supported, or deletion failed.
280 void deleteRefLog(final String name) throws IOException {
281 deleteFile("../logs/" + name);
285 * Overwrite (or create) a loose ref in the remote repository.
286 * <p>
287 * This method creates any missing parent directories, if necessary.
289 * @param name
290 * name of the ref within the ref space, for example
291 * <code>refs/heads/pu</code>.
292 * @param value
293 * new value to store in this ref. Must not be null.
294 * @throws IOException
295 * writing is not supported, or attempting to write the file
296 * failed, possibly due to permissions or remote disk full, etc.
298 void writeRef(final String name, final ObjectId value) throws IOException {
299 final ByteArrayOutputStream b;
301 b = new ByteArrayOutputStream(Constants.OBJECT_ID_LENGTH * 2 + 1);
302 value.copyTo(b);
303 b.write('\n');
305 writeFile("../" + name, b.toByteArray());
309 * Rebuild the {@link #INFO_PACKS} for dumb transport clients.
310 * <p>
311 * This method rebuilds the contents of the {@link #INFO_PACKS} file to
312 * match the passed list of pack names.
314 * @param packNames
315 * names of available pack files, in the order they should appear
316 * in the file. Valid pack name strings are of the form
317 * <code>pack-035760ab452d6eebd123add421f253ce7682355a.pack</code>.
318 * @throws IOException
319 * writing is not supported, or attempting to write the file
320 * failed, possibly due to permissions or remote disk full, etc.
322 void writeInfoPacks(final Collection<String> packNames) throws IOException {
323 final StringBuilder w = new StringBuilder();
324 for (final String n : packNames) {
325 w.append("P ");
326 w.append(n);
327 w.append('\n');
329 writeFile(INFO_PACKS, Constants.encodeASCII(w.toString()));
333 * Rebuild the {@link #INFO_REFS} for dumb transport clients.
334 * <p>
335 * This method rebuilds the contents of the {@link #INFO_REFS} file to match
336 * the passed list of references.
338 * @param refs
339 * the complete set of references the remote side now has. This
340 * should have been computed by applying updates to the
341 * advertised refs already discovered.
342 * @throws IOException
343 * writing is not supported, or attempting to write the file
344 * failed, possibly due to permissions or remote disk full, etc.
346 void writeInfoRefs(final Collection<Ref> refs) throws IOException {
347 final StringWriter w = new StringWriter();
348 final char[] tmp = new char[Constants.OBJECT_ID_LENGTH * 2];
349 for (final Ref r : refs) {
350 if (Constants.HEAD.equals(r.getName())) {
351 // Historically HEAD has never been published through
352 // the INFO_REFS file. This is a mistake, but its the
353 // way things are.
355 continue;
358 r.getObjectId().copyTo(tmp, w);
359 w.write('\t');
360 w.write(r.getName());
361 w.write('\n');
363 if (r.getPeeledObjectId() != null) {
364 r.getPeeledObjectId().copyTo(tmp, w);
365 w.write('\t');
366 w.write(r.getName());
367 w.write("^{}\n");
370 writeFile(INFO_REFS, Constants.encodeASCII(w.toString()));
374 * Rebuild the {@link #PACKED_REFS} file.
375 * <p>
376 * This method rebuilds the contents of the {@link #PACKED_REFS} file to
377 * match the passed list of references, including only those refs that have
378 * a storage type of {@link Ref.Storage#PACKED}.
380 * @param refs
381 * the complete set of references the remote side now has. This
382 * should have been computed by applying updates to the
383 * advertised refs already discovered.
384 * @throws IOException
385 * writing is not supported, or attempting to write the file
386 * failed, possibly due to permissions or remote disk full, etc.
388 void writePackedRefs(final Collection<Ref> refs) throws IOException {
389 boolean peeled = false;
391 for (final Ref r : refs) {
392 if (r.getStorage() != Ref.Storage.PACKED)
393 continue;
394 if (r.getPeeledObjectId() != null)
395 peeled = true;
398 final StringWriter w = new StringWriter();
399 if (peeled) {
400 w.write("# pack-refs with:");
401 if (peeled)
402 w.write(" peeled");
403 w.write('\n');
406 final char[] tmp = new char[Constants.OBJECT_ID_LENGTH * 2];
407 for (final Ref r : refs) {
408 if (r.getStorage() != Ref.Storage.PACKED)
409 continue;
411 r.getObjectId().copyTo(tmp, w);
412 w.write(' ');
413 w.write(r.getName());
414 w.write('\n');
416 if (r.getPeeledObjectId() != null) {
417 w.write('^');
418 r.getPeeledObjectId().copyTo(tmp, w);
419 w.write('\n');
422 writeFile(PACKED_REFS, Constants.encodeASCII(w.toString()));
426 * Open a buffered reader around a file.
427 * <p>
428 * This is shorthand for calling {@link #open(String)} and then wrapping it
429 * in a reader suitable for line oriented files like the alternates list.
431 * @return a stream to read from the file. Never null.
432 * @param path
433 * location of the file to read, relative to this objects
434 * directory (e.g. <code>info/packs</code>).
435 * @throws FileNotFoundException
436 * the requested file does not exist at the given location.
437 * @throws IOException
438 * The connection is unable to read the remote's file, and the
439 * failure occurred prior to being able to determine if the file
440 * exists, or after it was determined to exist but before the
441 * stream could be created.
443 BufferedReader openReader(final String path) throws IOException {
444 return new BufferedReader(new InputStreamReader(open(path).in, CHARENC));
448 * Read a standard Git alternates file to discover other object databases.
449 * <p>
450 * This method is suitable for reading the standard formats of the
451 * alternates file, such as found in <code>objects/info/alternates</code>
452 * or <code>objects/info/http-alternates</code> within a Git repository.
453 * <p>
454 * Alternates appear one per line, with paths expressed relative to this
455 * object database.
457 * @param listPath
458 * location of the alternate file to read, relative to this
459 * object database (e.g. <code>info/alternates</code>).
460 * @return the list of discovered alternates. Empty list if the file exists,
461 * but no entries were discovered.
462 * @throws FileNotFoundException
463 * the requested file does not exist at the given location.
464 * @throws IOException
465 * The connection is unable to read the remote's file, and the
466 * failure occurred prior to being able to determine if the file
467 * exists, or after it was determined to exist but before the
468 * stream could be created.
470 Collection<WalkRemoteObjectDatabase> readAlternates(final String listPath)
471 throws IOException {
472 final BufferedReader br = openReader(listPath);
473 try {
474 final Collection<WalkRemoteObjectDatabase> alts = new ArrayList<WalkRemoteObjectDatabase>();
475 for (;;) {
476 String line = br.readLine();
477 if (line == null)
478 break;
479 if (!line.endsWith("/"))
480 line += "/";
481 alts.add(openAlternate(line));
483 return alts;
484 } finally {
485 br.close();
490 * Read a standard Git packed-refs file to discover known references.
492 * @param avail
493 * return collection of references. Any existing entries will be
494 * replaced if they are found in the packed-refs file.
495 * @throws TransportException
496 * an error occurred reading from the packed refs file.
498 protected void readPackedRefs(final Map<String, Ref> avail)
499 throws TransportException {
500 try {
501 final BufferedReader br = openReader(PACKED_REFS);
502 try {
503 readPackedRefsImpl(avail, br);
504 } finally {
505 br.close();
507 } catch (FileNotFoundException notPacked) {
508 // Perhaps it wasn't worthwhile, or is just an older repository.
509 } catch (IOException e) {
510 throw new TransportException(getURI(), "error in packed-refs", e);
514 private void readPackedRefsImpl(final Map<String, Ref> avail,
515 final BufferedReader br) throws IOException {
516 Ref last = null;
517 for (;;) {
518 String line = br.readLine();
519 if (line == null)
520 break;
521 if (line.charAt(0) == '#')
522 continue;
523 if (line.charAt(0) == '^') {
524 if (last == null)
525 throw new TransportException("Peeled line before ref.");
526 final ObjectId id = ObjectId.fromString(line + 1);
527 last = new Ref(Ref.Storage.PACKED, last.getName(), last
528 .getObjectId(), id);
529 avail.put(last.getName(), last);
530 continue;
533 final int sp = line.indexOf(' ');
534 if (sp < 0)
535 throw new TransportException("Unrecognized ref: " + line);
536 final ObjectId id = ObjectId.fromString(line.substring(0, sp));
537 final String name = line.substring(sp + 1);
538 last = new Ref(Ref.Storage.PACKED, name, id);
539 avail.put(last.getName(), last);
543 static final class FileStream {
544 final InputStream in;
546 final long length;
549 * Create a new stream of unknown length.
551 * @param i
552 * stream containing the file data. This stream will be
553 * closed by the caller when reading is complete.
555 FileStream(final InputStream i) {
556 in = i;
557 length = -1;
561 * Create a new stream of known length.
563 * @param i
564 * stream containing the file data. This stream will be
565 * closed by the caller when reading is complete.
566 * @param n
567 * total number of bytes available for reading through
568 * <code>i</code>.
570 FileStream(final InputStream i, final long n) {
571 in = i;
572 length = n;
575 byte[] toArray() throws IOException {
576 try {
577 if (length >= 0) {
578 final byte[] r = new byte[(int) length];
579 NB.readFully(in, r, 0, r.length);
580 return r;
583 final ByteArrayOutputStream r = new ByteArrayOutputStream();
584 final byte[] buf = new byte[2048];
585 int n;
586 while ((n = in.read(buf)) >= 0)
587 r.write(buf, 0, n);
588 return r.toByteArray();
589 } finally {
590 in.close();