Use pack-*.keep files during fetch and receive-pack to prevent GC
[jgit.git] / org.spearce.jgit / src / org / spearce / jgit / transport / BundleFetchConnection.java
blob14e0c7d9c162a79a7a3bcc0acbb02071a719f9e2
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.
39 package org.spearce.jgit.transport;
41 import java.io.BufferedInputStream;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.util.ArrayList;
45 import java.util.Collection;
46 import java.util.Collections;
47 import java.util.HashSet;
48 import java.util.LinkedHashMap;
49 import java.util.List;
50 import java.util.Set;
52 import org.spearce.jgit.errors.MissingBundlePrerequisiteException;
53 import org.spearce.jgit.errors.MissingObjectException;
54 import org.spearce.jgit.errors.PackProtocolException;
55 import org.spearce.jgit.errors.TransportException;
56 import org.spearce.jgit.lib.Constants;
57 import org.spearce.jgit.lib.ObjectId;
58 import org.spearce.jgit.lib.PackLock;
59 import org.spearce.jgit.lib.ProgressMonitor;
60 import org.spearce.jgit.lib.Ref;
61 import org.spearce.jgit.revwalk.RevCommit;
62 import org.spearce.jgit.revwalk.RevFlag;
63 import org.spearce.jgit.revwalk.RevObject;
64 import org.spearce.jgit.revwalk.RevWalk;
65 import org.spearce.jgit.util.NB;
66 import org.spearce.jgit.util.RawParseUtils;
68 /**
69 * Fetch connection for bundle based classes. It used by
70 * instances of {@link TransportBundle}
72 class BundleFetchConnection extends BaseFetchConnection {
74 private final Transport transport;
76 InputStream bin;
78 final Set<ObjectId> prereqs = new HashSet<ObjectId>();
80 private String lockMessage;
82 private PackLock packLock;
84 BundleFetchConnection(Transport transportBundle, final InputStream src) throws TransportException {
85 transport = transportBundle;
86 bin = new BufferedInputStream(src, IndexPack.BUFFER_SIZE);
87 try {
88 switch (readSignature()) {
89 case 2:
90 readBundleV2();
91 break;
92 default:
93 throw new TransportException(transport.uri, "not a bundle");
95 } catch (TransportException err) {
96 close();
97 throw err;
98 } catch (IOException err) {
99 close();
100 throw new TransportException(transport.uri, err.getMessage(), err);
101 } catch (RuntimeException err) {
102 close();
103 throw new TransportException(transport.uri, err.getMessage(), err);
107 private int readSignature() throws IOException {
108 final String rev = readLine(new byte[1024]);
109 if (TransportBundle.V2_BUNDLE_SIGNATURE.equals(rev))
110 return 2;
111 throw new TransportException(transport.uri, "not a bundle");
114 private void readBundleV2() throws IOException {
115 final byte[] hdrbuf = new byte[1024];
116 final LinkedHashMap<String, Ref> avail = new LinkedHashMap<String, Ref>();
117 for (;;) {
118 String line = readLine(hdrbuf);
119 if (line.length() == 0)
120 break;
122 if (line.charAt(0) == '-') {
123 prereqs.add(ObjectId.fromString(line.substring(1, 41)));
124 continue;
127 final String name = line.substring(41, line.length());
128 final ObjectId id = ObjectId.fromString(line.substring(0, 40));
129 final Ref prior = avail.put(name, new Ref(Ref.Storage.NETWORK,
130 name, id));
131 if (prior != null)
132 throw duplicateAdvertisement(name);
134 available(avail);
137 private PackProtocolException duplicateAdvertisement(final String name) {
138 return new PackProtocolException(transport.uri,
139 "duplicate advertisements of " + name);
142 private String readLine(final byte[] hdrbuf) throws IOException {
143 bin.mark(hdrbuf.length);
144 final int cnt = bin.read(hdrbuf);
145 int lf = 0;
146 while (lf < cnt && hdrbuf[lf] != '\n')
147 lf++;
148 bin.reset();
149 NB.skipFully(bin, lf);
150 if (lf < cnt && hdrbuf[lf] == '\n')
151 NB.skipFully(bin, 1);
152 return RawParseUtils.decode(Constants.CHARSET, hdrbuf, 0, lf);
155 public boolean didFetchTestConnectivity() {
156 return false;
159 @Override
160 protected void doFetch(final ProgressMonitor monitor,
161 final Collection<Ref> want, final Set<ObjectId> have)
162 throws TransportException {
163 verifyPrerequisites();
164 try {
165 final IndexPack ip = newIndexPack();
166 ip.index(monitor);
167 packLock = ip.renameAndOpenPack(lockMessage);
168 } catch (IOException err) {
169 close();
170 throw new TransportException(transport.uri, err.getMessage(), err);
171 } catch (RuntimeException err) {
172 close();
173 throw new TransportException(transport.uri, err.getMessage(), err);
177 public void setPackLockMessage(final String message) {
178 lockMessage = message;
181 public Collection<PackLock> getPackLocks() {
182 if (packLock != null)
183 return Collections.singleton(packLock);
184 return Collections.<PackLock> emptyList();
187 private IndexPack newIndexPack() throws IOException {
188 final IndexPack ip = IndexPack.create(transport.local, bin);
189 ip.setFixThin(true);
190 ip.setObjectChecking(transport.isCheckFetchedObjects());
191 return ip;
194 private void verifyPrerequisites() throws TransportException {
195 if (prereqs.isEmpty())
196 return;
198 final RevWalk rw = new RevWalk(transport.local);
199 final RevFlag PREREQ = rw.newFlag("PREREQ");
200 final RevFlag SEEN = rw.newFlag("SEEN");
202 final List<ObjectId> missing = new ArrayList<ObjectId>();
203 final List<RevObject> commits = new ArrayList<RevObject>();
204 for (final ObjectId p : prereqs) {
205 try {
206 final RevCommit c = rw.parseCommit(p);
207 if (!c.has(PREREQ)) {
208 c.add(PREREQ);
209 commits.add(c);
211 } catch (MissingObjectException notFound) {
212 missing.add(p);
213 } catch (IOException err) {
214 throw new TransportException(transport.uri, "Cannot read commit "
215 + p.name(), err);
218 if (!missing.isEmpty())
219 throw new MissingBundlePrerequisiteException(transport.uri, missing);
221 for (final Ref r : transport.local.getAllRefs().values()) {
222 try {
223 rw.markStart(rw.parseCommit(r.getObjectId()));
224 } catch (IOException readError) {
225 // If we cannot read the value of the ref skip it.
229 int remaining = commits.size();
230 try {
231 RevCommit c;
232 while ((c = rw.next()) != null) {
233 if (c.has(PREREQ)) {
234 c.add(SEEN);
235 if (--remaining == 0)
236 break;
239 } catch (IOException err) {
240 throw new TransportException(transport.uri, "Cannot read object", err);
243 if (remaining > 0) {
244 for (final RevObject o : commits) {
245 if (!o.has(SEEN))
246 missing.add(o);
248 throw new MissingBundlePrerequisiteException(transport.uri, missing);
252 @Override
253 public void close() {
254 if (bin != null) {
255 try {
256 bin.close();
257 } catch (IOException ie) {
258 // Ignore close failures.
259 } finally {
260 bin = null;