Include URIish in bundle transport within any TransportExceptions
[egit.git] / org.spearce.jgit / src / org / spearce / jgit / transport / TransportBundle.java
blob6e1d51de8b4c17106d31633bfcbb575dc2306f5d
1 /*
2 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * Copyright (C) 2008, Google Inc.
6 * All rights reserved.
8 * Redistribution and use in source and binary forms, with or
9 * without modification, are permitted provided that the following
10 * conditions are met:
12 * - Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * - Redistributions in binary form must reproduce the above
16 * copyright notice, this list of conditions and the following
17 * disclaimer in the documentation and/or other materials provided
18 * with the distribution.
20 * - Neither the name of the Git Development Community nor the
21 * names of its contributors may be used to endorse or promote
22 * products derived from this software without specific prior
23 * written permission.
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
26 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
27 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
30 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
32 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
35 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
37 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40 package org.spearce.jgit.transport;
42 import java.io.BufferedInputStream;
43 import java.io.File;
44 import java.io.FileInputStream;
45 import java.io.FileNotFoundException;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.util.ArrayList;
49 import java.util.Collection;
50 import java.util.HashSet;
51 import java.util.LinkedHashMap;
52 import java.util.List;
53 import java.util.Set;
55 import org.spearce.jgit.errors.MissingObjectException;
56 import org.spearce.jgit.errors.MissingBundlePrerequisiteException;
57 import org.spearce.jgit.errors.NotSupportedException;
58 import org.spearce.jgit.errors.PackProtocolException;
59 import org.spearce.jgit.errors.TransportException;
60 import org.spearce.jgit.lib.Constants;
61 import org.spearce.jgit.lib.ObjectId;
62 import org.spearce.jgit.lib.ProgressMonitor;
63 import org.spearce.jgit.lib.Ref;
64 import org.spearce.jgit.lib.Repository;
65 import org.spearce.jgit.revwalk.RevCommit;
66 import org.spearce.jgit.revwalk.RevFlag;
67 import org.spearce.jgit.revwalk.RevObject;
68 import org.spearce.jgit.revwalk.RevWalk;
69 import org.spearce.jgit.util.FS;
70 import org.spearce.jgit.util.RawParseUtils;
72 /**
73 * Supports fetching from a git bundle (sneaker-net object transport).
74 * <p>
75 * Push support for a bundle is complex, as one does not have a peer to
76 * communicate with to decide what the peer already knows. So push is not
77 * supported by the bundle transport.
79 class TransportBundle extends PackTransport {
80 static final String V2_BUNDLE_SIGNATURE = "# v2 git bundle";
82 static boolean canHandle(final URIish uri) {
83 if (uri.getHost() != null || uri.getPort() > 0 || uri.getUser() != null
84 || uri.getPass() != null || uri.getPath() == null)
85 return false;
87 if ("file".equals(uri.getScheme()) || uri.getScheme() == null) {
88 final File f = FS.resolve(new File("."), uri.getPath());
89 return f.isFile() || f.getName().endsWith(".bundle");
92 return false;
95 private final File bundle;
97 TransportBundle(final Repository local, final URIish uri) {
98 super(local, uri);
99 bundle = FS.resolve(new File("."), uri.getPath()).getAbsoluteFile();
102 @Override
103 public FetchConnection openFetch() throws NotSupportedException,
104 TransportException {
105 return new BundleFetchConnection();
108 @Override
109 public PushConnection openPush() throws NotSupportedException {
110 throw new NotSupportedException(
111 "Push is not supported for bundle transport");
114 @Override
115 public void close() {
116 // Resources must be established per-connection.
119 class BundleFetchConnection extends BaseFetchConnection {
120 FileInputStream in;
122 RewindBufferedInputStream bin;
124 final Set<ObjectId> prereqs = new HashSet<ObjectId>();
126 BundleFetchConnection() throws TransportException {
127 try {
128 in = new FileInputStream(bundle);
129 bin = new RewindBufferedInputStream(in);
130 } catch (FileNotFoundException err) {
131 throw new TransportException(uri, "not found");
134 try {
135 switch (readSignature()) {
136 case 2:
137 readBundleV2();
138 break;
139 default:
140 throw new TransportException(uri, "not a bundle");
143 in.getChannel().position(
144 in.getChannel().position() - bin.buffered());
145 bin = null;
146 } catch (TransportException err) {
147 close();
148 throw err;
149 } catch (IOException err) {
150 close();
151 throw new TransportException(uri, err.getMessage(), err);
152 } catch (RuntimeException err) {
153 close();
154 throw new TransportException(uri, err.getMessage(), err);
158 private int readSignature() throws IOException {
159 final String rev = readLine(new byte[1024]);
160 if (V2_BUNDLE_SIGNATURE.equals(rev))
161 return 2;
162 throw new TransportException(uri, "not a bundle");
165 private void readBundleV2() throws IOException {
166 final byte[] hdrbuf = new byte[1024];
167 final LinkedHashMap<String, Ref> avail = new LinkedHashMap<String, Ref>();
168 for (;;) {
169 String line = readLine(hdrbuf);
170 if (line.length() == 0)
171 break;
173 if (line.charAt(0) == '-') {
174 prereqs.add(ObjectId.fromString(line.substring(1, 41)));
175 continue;
178 final String name = line.substring(41, line.length());
179 final ObjectId id = ObjectId.fromString(line.substring(0, 40));
180 final Ref prior = avail.put(name, new Ref(Ref.Storage.NETWORK,
181 name, id));
182 if (prior != null)
183 throw duplicateAdvertisement(name);
185 available(avail);
188 private PackProtocolException duplicateAdvertisement(final String name) {
189 return new PackProtocolException(uri,
190 "duplicate advertisements of " + name);
193 private String readLine(final byte[] hdrbuf) throws IOException {
194 bin.mark(hdrbuf.length);
195 final int cnt = bin.read(hdrbuf);
196 int lf = 0;
197 while (lf < cnt && hdrbuf[lf] != '\n')
198 lf++;
199 bin.reset();
200 bin.skip(lf);
201 if (lf < cnt && hdrbuf[lf] == '\n')
202 bin.skip(1);
203 return RawParseUtils.decode(Constants.CHARSET, hdrbuf, 0, lf);
206 @Override
207 protected void doFetch(final ProgressMonitor monitor,
208 final Collection<Ref> want) throws TransportException {
209 verifyPrerequisites();
210 try {
211 final IndexPack ip = IndexPack.create(local, in);
212 ip.setFixThin(true);
213 ip.index(monitor);
214 ip.renameAndOpenPack();
215 } catch (IOException err) {
216 close();
217 throw new TransportException(uri, err.getMessage(), err);
218 } catch (RuntimeException err) {
219 close();
220 throw new TransportException(uri, err.getMessage(), err);
224 private void verifyPrerequisites() throws TransportException {
225 if (prereqs.isEmpty())
226 return;
228 final RevWalk rw = new RevWalk(local);
229 final RevFlag PREREQ = rw.newFlag("PREREQ");
230 final RevFlag SEEN = rw.newFlag("SEEN");
232 final List<ObjectId> missing = new ArrayList<ObjectId>();
233 final List<RevObject> commits = new ArrayList<RevObject>();
234 for (final ObjectId p : prereqs) {
235 try {
236 final RevCommit c = rw.parseCommit(p);
237 if (!c.has(PREREQ)) {
238 c.add(PREREQ);
239 commits.add(c);
241 } catch (MissingObjectException notFound) {
242 missing.add(p);
243 } catch (IOException err) {
244 throw new TransportException(uri, "Cannot read commit "
245 + p.name(), err);
248 if (!missing.isEmpty())
249 throw new MissingBundlePrerequisiteException(uri, missing);
251 for (final Ref r : local.getAllRefs().values()) {
252 try {
253 rw.markStart(rw.parseCommit(r.getObjectId()));
254 } catch (IOException readError) {
255 // If we cannot read the value of the ref skip it.
259 int remaining = commits.size();
260 try {
261 RevCommit c;
262 while ((c = rw.next()) != null) {
263 if (c.has(PREREQ)) {
264 c.add(SEEN);
265 if (--remaining == 0)
266 break;
269 } catch (IOException err) {
270 throw new TransportException(uri, "Cannot read object", err);
273 if (remaining > 0) {
274 for (final RevObject o : commits) {
275 if (!o.has(SEEN))
276 missing.add(o);
278 throw new MissingBundlePrerequisiteException(uri, missing);
282 @Override
283 public void close() {
284 if (in != null) {
285 try {
286 in.close();
287 } catch (IOException ie) {
288 // Ignore close failures.
289 } finally {
290 in = null;
291 bin = null;
296 class RewindBufferedInputStream extends BufferedInputStream {
297 RewindBufferedInputStream(final InputStream src) {
298 super(src);
301 int buffered() {
302 return (count - pos);