Fix RemoteRefUpdate to delete local tracking ref upon successful deletion
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / transport / WalkRemoteObjectDatabase.java
blob54dd5815c165ca5f922b89d0bb462af682dcce53
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.util.ArrayList;
48 import java.util.Collection;
49 import java.util.Map;
51 import org.spearce.jgit.errors.TransportException;
52 import org.spearce.jgit.lib.Constants;
53 import org.spearce.jgit.lib.ObjectId;
54 import org.spearce.jgit.lib.ProgressMonitor;
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 * naked 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 ROOT_DIR = "../";
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 = ROOT_DIR + Constants.INFO_REFS;
79 abstract URIish getURI();
81 /**
82 * Obtain the list of available packs (if any).
83 * <p>
84 * Pack names should be the file name in the packs directory, that is
85 * <code>pack-035760ab452d6eebd123add421f253ce7682355a.pack</code>. Index
86 * names should not be included in the returned collection.
88 * @return list of pack names; null or empty list if none are available.
89 * @throws IOException
90 * The connection is unable to read the remote repository's list
91 * of available pack files.
93 abstract Collection<String> getPackNames() throws IOException;
95 /**
96 * Obtain alternate connections to alternate object databases (if any).
97 * <p>
98 * Alternates are typically read from the file {@link #INFO_ALTERNATES} or
99 * {@link #INFO_HTTP_ALTERNATES}. The content of each line must be resolved
100 * by the implementation and a new database reference should be returned to
101 * represent the additional location.
102 * <p>
103 * Alternates may reuse the same network connection handle, however the
104 * fetch connection will {@link #close()} each created alternate.
106 * @return list of additional object databases the caller could fetch from;
107 * null or empty list if none are configured.
108 * @throws IOException
109 * The connection is unable to read the remote repository's list
110 * of configured alternates.
112 abstract Collection<WalkRemoteObjectDatabase> getAlternates()
113 throws IOException;
116 * Open a single file for reading.
117 * <p>
118 * Implementors should make every attempt possible to ensure
119 * {@link FileNotFoundException} is used when the remote object does not
120 * exist. However when fetching over HTTP some misconfigured servers may
121 * generate a 200 OK status message (rather than a 404 Not Found) with an
122 * HTML formatted message explaining the requested resource does not exist.
123 * Callers such as {@link WalkFetchConnection} are prepared to handle this
124 * by validating the content received, and assuming content that fails to
125 * match its hash is an incorrectly phrased FileNotFoundException.
127 * @param path
128 * location of the file to read, relative to this objects
129 * directory (e.g.
130 * <code>cb/95df6ab7ae9e57571511ef451cf33767c26dd2</code> or
131 * <code>pack/pack-035760ab452d6eebd123add421f253ce7682355a.pack</code>).
132 * @return a stream to read from the file. Never null.
133 * @throws FileNotFoundException
134 * the requested file does not exist at the given location.
135 * @throws IOException
136 * The connection is unable to read the remote's file, and the
137 * failure occurred prior to being able to determine if the file
138 * exists, or after it was determined to exist but before the
139 * stream could be created.
141 abstract FileStream open(String path) throws FileNotFoundException,
142 IOException;
145 * Create a new connection for a discovered alternate object database
146 * <p>
147 * This method is typically called by {@link #readAlternates(String)} when
148 * subclasses us the generic alternate parsing logic for their
149 * implementation of {@link #getAlternates()}.
151 * @param location
152 * the location of the new alternate, relative to the current
153 * object database.
154 * @return a new database connection that can read from the specified
155 * alternate.
156 * @throws IOException
157 * The database connection cannot be established with the
158 * alternate, such as if the alternate location does not
159 * actually exist and the connection's constructor attempts to
160 * verify that.
162 abstract WalkRemoteObjectDatabase openAlternate(String location)
163 throws IOException;
166 * Close any resources used by this connection.
167 * <p>
168 * If the remote repository is contacted by a network socket this method
169 * must close that network socket, disconnecting the two peers. If the
170 * remote repository is actually local (same system) this method must close
171 * any open file handles used to read the "remote" repository.
173 abstract void close();
176 * Delete a file from the object database.
177 * <p>
178 * Path may start with <code>../</code> to request deletion of a file that
179 * resides in the repository itself.
180 * <p>
181 * When possible empty directories must be removed, up to but not including
182 * the current object database directory itself.
183 * <p>
184 * This method does not support deletion of directories.
186 * @param path
187 * name of the item to be removed, relative to the current object
188 * database.
189 * @throws IOException
190 * deletion is not supported, or deletion failed.
192 void deleteFile(final String path) throws IOException {
193 throw new IOException("Deleting '" + path + "' not supported.");
197 * Open a remote file for writing.
198 * <p>
199 * Path may start with <code>../</code> to request writing of a file that
200 * resides in the repository itself.
201 * <p>
202 * The requested path may or may not exist. If the path already exists as a
203 * file the file should be truncated and completely replaced.
204 * <p>
205 * This method creates any missing parent directories, if necessary.
207 * @param path
208 * name of the file to write, relative to the current object
209 * database.
210 * @return stream to write into this file. Caller must close the stream to
211 * complete the write request. The stream is not buffered and each
212 * write may cause a network request/response so callers should
213 * buffer to smooth out small writes.
214 * @param monitor
215 * (optional) progress monitor to post write completion to during
216 * the stream's close method.
217 * @param monitorTask
218 * (optional) task name to display during the close method.
219 * @throws IOException
220 * writing is not supported, or attempting to write the file
221 * failed, possibly due to permissions or remote disk full, etc.
223 OutputStream writeFile(final String path, final ProgressMonitor monitor,
224 final String monitorTask) throws IOException {
225 throw new IOException("Writing of '" + path + "' not supported.");
229 * Atomically write a remote file.
230 * <p>
231 * This method attempts to perform as atomic of an update as it can,
232 * reducing (or eliminating) the time that clients might be able to see
233 * partial file content. This method is not suitable for very large
234 * transfers as the complete content must be passed as an argument.
235 * <p>
236 * Path may start with <code>../</code> to request writing of a file that
237 * resides in the repository itself.
238 * <p>
239 * The requested path may or may not exist. If the path already exists as a
240 * file the file should be truncated and completely replaced.
241 * <p>
242 * This method creates any missing parent directories, if necessary.
244 * @param path
245 * name of the file to write, relative to the current object
246 * database.
247 * @param data
248 * complete new content of the file.
249 * @throws IOException
250 * writing is not supported, or attempting to write the file
251 * failed, possibly due to permissions or remote disk full, etc.
253 void writeFile(final String path, final byte[] data) throws IOException {
254 final OutputStream os = writeFile(path, null, null);
255 try {
256 os.write(data);
257 } finally {
258 os.close();
263 * Delete a loose ref from the remote repository.
265 * @param name
266 * name of the ref within the ref space, for example
267 * <code>refs/heads/pu</code>.
268 * @throws IOException
269 * deletion is not supported, or deletion failed.
271 void deleteRef(final String name) throws IOException {
272 deleteFile(ROOT_DIR + name);
276 * Delete a reflog from the remote repository.
278 * @param name
279 * name of the ref within the ref space, for example
280 * <code>refs/heads/pu</code>.
281 * @throws IOException
282 * deletion is not supported, or deletion failed.
284 void deleteRefLog(final String name) throws IOException {
285 deleteFile(ROOT_DIR + Constants.LOGS + "/" + name);
289 * Overwrite (or create) a loose ref in the remote repository.
290 * <p>
291 * This method creates any missing parent directories, if necessary.
293 * @param name
294 * name of the ref within the ref space, for example
295 * <code>refs/heads/pu</code>.
296 * @param value
297 * new value to store in this ref. Must not be null.
298 * @throws IOException
299 * writing is not supported, or attempting to write the file
300 * failed, possibly due to permissions or remote disk full, etc.
302 void writeRef(final String name, final ObjectId value) throws IOException {
303 final ByteArrayOutputStream b;
305 b = new ByteArrayOutputStream(Constants.OBJECT_ID_LENGTH * 2 + 1);
306 value.copyTo(b);
307 b.write('\n');
309 writeFile(ROOT_DIR + name, b.toByteArray());
313 * Rebuild the {@link #INFO_PACKS} for dumb transport clients.
314 * <p>
315 * This method rebuilds the contents of the {@link #INFO_PACKS} file to
316 * match the passed list of pack names.
318 * @param packNames
319 * names of available pack files, in the order they should appear
320 * in the file. Valid pack name strings are of the form
321 * <code>pack-035760ab452d6eebd123add421f253ce7682355a.pack</code>.
322 * @throws IOException
323 * writing is not supported, or attempting to write the file
324 * failed, possibly due to permissions or remote disk full, etc.
326 void writeInfoPacks(final Collection<String> packNames) throws IOException {
327 final StringBuilder w = new StringBuilder();
328 for (final String n : packNames) {
329 w.append("P ");
330 w.append(n);
331 w.append('\n');
333 writeFile(INFO_PACKS, Constants.encodeASCII(w.toString()));
337 * Open a buffered reader around a file.
338 * <p>
339 * This is shorthand for calling {@link #open(String)} and then wrapping it
340 * in a reader suitable for line oriented files like the alternates list.
342 * @return a stream to read from the file. Never null.
343 * @param path
344 * location of the file to read, relative to this objects
345 * directory (e.g. <code>info/packs</code>).
346 * @throws FileNotFoundException
347 * the requested file does not exist at the given location.
348 * @throws IOException
349 * The connection is unable to read the remote's file, and the
350 * failure occurred prior to being able to determine if the file
351 * exists, or after it was determined to exist but before the
352 * stream could be created.
354 BufferedReader openReader(final String path) throws IOException {
355 final InputStream is = open(path).in;
356 return new BufferedReader(new InputStreamReader(is, Constants.CHARSET));
360 * Read a standard Git alternates file to discover other object databases.
361 * <p>
362 * This method is suitable for reading the standard formats of the
363 * alternates file, such as found in <code>objects/info/alternates</code>
364 * or <code>objects/info/http-alternates</code> within a Git repository.
365 * <p>
366 * Alternates appear one per line, with paths expressed relative to this
367 * object database.
369 * @param listPath
370 * location of the alternate file to read, relative to this
371 * object database (e.g. <code>info/alternates</code>).
372 * @return the list of discovered alternates. Empty list if the file exists,
373 * but no entries were discovered.
374 * @throws FileNotFoundException
375 * the requested file does not exist at the given location.
376 * @throws IOException
377 * The connection is unable to read the remote's file, and the
378 * failure occurred prior to being able to determine if the file
379 * exists, or after it was determined to exist but before the
380 * stream could be created.
382 Collection<WalkRemoteObjectDatabase> readAlternates(final String listPath)
383 throws IOException {
384 final BufferedReader br = openReader(listPath);
385 try {
386 final Collection<WalkRemoteObjectDatabase> alts = new ArrayList<WalkRemoteObjectDatabase>();
387 for (;;) {
388 String line = br.readLine();
389 if (line == null)
390 break;
391 if (!line.endsWith("/"))
392 line += "/";
393 alts.add(openAlternate(line));
395 return alts;
396 } finally {
397 br.close();
402 * Read a standard Git packed-refs file to discover known references.
404 * @param avail
405 * return collection of references. Any existing entries will be
406 * replaced if they are found in the packed-refs file.
407 * @throws TransportException
408 * an error occurred reading from the packed refs file.
410 protected void readPackedRefs(final Map<String, Ref> avail)
411 throws TransportException {
412 try {
413 final BufferedReader br = openReader(ROOT_DIR
414 + Constants.PACKED_REFS);
415 try {
416 readPackedRefsImpl(avail, br);
417 } finally {
418 br.close();
420 } catch (FileNotFoundException notPacked) {
421 // Perhaps it wasn't worthwhile, or is just an older repository.
422 } catch (IOException e) {
423 throw new TransportException(getURI(), "error in packed-refs", e);
427 private void readPackedRefsImpl(final Map<String, Ref> avail,
428 final BufferedReader br) throws IOException {
429 Ref last = null;
430 for (;;) {
431 String line = br.readLine();
432 if (line == null)
433 break;
434 if (line.charAt(0) == '#')
435 continue;
436 if (line.charAt(0) == '^') {
437 if (last == null)
438 throw new TransportException("Peeled line before ref.");
439 final ObjectId id = ObjectId.fromString(line + 1);
440 last = new Ref(Ref.Storage.PACKED, last.getName(), last
441 .getObjectId(), id);
442 avail.put(last.getName(), last);
443 continue;
446 final int sp = line.indexOf(' ');
447 if (sp < 0)
448 throw new TransportException("Unrecognized ref: " + line);
449 final ObjectId id = ObjectId.fromString(line.substring(0, sp));
450 final String name = line.substring(sp + 1);
451 last = new Ref(Ref.Storage.PACKED, name, id);
452 avail.put(last.getName(), last);
456 static final class FileStream {
457 final InputStream in;
459 final long length;
462 * Create a new stream of unknown length.
464 * @param i
465 * stream containing the file data. This stream will be
466 * closed by the caller when reading is complete.
468 FileStream(final InputStream i) {
469 in = i;
470 length = -1;
474 * Create a new stream of known length.
476 * @param i
477 * stream containing the file data. This stream will be
478 * closed by the caller when reading is complete.
479 * @param n
480 * total number of bytes available for reading through
481 * <code>i</code>.
483 FileStream(final InputStream i, final long n) {
484 in = i;
485 length = n;
488 byte[] toArray() throws IOException {
489 try {
490 if (length >= 0) {
491 final byte[] r = new byte[(int) length];
492 NB.readFully(in, r, 0, r.length);
493 return r;
496 final ByteArrayOutputStream r = new ByteArrayOutputStream();
497 final byte[] buf = new byte[2048];
498 int n;
499 while ((n = in.read(buf)) >= 0)
500 r.write(buf, 0, n);
501 return r.toByteArray();
502 } finally {
503 in.close();