View Javadoc
1   /*
2    * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
3    * Copyright (C) 2008-2010, Google Inc.
4    * Copyright (C) 2008-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
5    * Copyright (C) 2009, Sasa Zivkov <sasa.zivkov@sap.com>
6    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
7    *
8    * This program and the accompanying materials are made available under the
9    * terms of the Eclipse Distribution License v. 1.0 which is available at
10   * https://www.eclipse.org/org/documents/edl-v10.php.
11   *
12   * SPDX-License-Identifier: BSD-3-Clause
13   */
14  
15  package org.eclipse.jgit.transport;
16  
17  import static java.nio.charset.StandardCharsets.UTF_8;
18  
19  import java.io.BufferedInputStream;
20  import java.io.EOFException;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.text.MessageFormat;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.LinkedHashMap;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  
33  import org.eclipse.jgit.errors.MissingBundlePrerequisiteException;
34  import org.eclipse.jgit.errors.MissingObjectException;
35  import org.eclipse.jgit.errors.PackProtocolException;
36  import org.eclipse.jgit.errors.TransportException;
37  import org.eclipse.jgit.internal.JGitText;
38  import org.eclipse.jgit.internal.storage.file.PackLock;
39  import org.eclipse.jgit.lib.NullProgressMonitor;
40  import org.eclipse.jgit.lib.ObjectId;
41  import org.eclipse.jgit.lib.ObjectIdRef;
42  import org.eclipse.jgit.lib.ObjectInserter;
43  import org.eclipse.jgit.lib.ProgressMonitor;
44  import org.eclipse.jgit.lib.Ref;
45  import org.eclipse.jgit.revwalk.RevCommit;
46  import org.eclipse.jgit.revwalk.RevFlag;
47  import org.eclipse.jgit.revwalk.RevObject;
48  import org.eclipse.jgit.revwalk.RevWalk;
49  import org.eclipse.jgit.util.IO;
50  import org.eclipse.jgit.util.RawParseUtils;
51  
52  /**
53   * Fetch connection for bundle based classes. It used by
54   * instances of {@link TransportBundle}
55   */
56  class BundleFetchConnection extends BaseFetchConnection {
57  
58  	private final Transport transport;
59  
60  	InputStream bin;
61  
62  	final Map<ObjectId, String> prereqs = new HashMap<>();
63  
64  	private String lockMessage;
65  
66  	private PackLock packLock;
67  
68  	BundleFetchConnection(Transport transportBundle, InputStream src) throws TransportException {
69  		transport = transportBundle;
70  		bin = new BufferedInputStream(src);
71  		try {
72  			switch (readSignature()) {
73  			case 2:
74  				readBundleV2();
75  				break;
76  			default:
77  				throw new TransportException(transport.uri, JGitText.get().notABundle);
78  			}
79  		} catch (TransportException err) {
80  			close();
81  			throw err;
82  		} catch (IOException | RuntimeException err) {
83  			close();
84  			throw new TransportException(transport.uri, err.getMessage(), err);
85  		}
86  	}
87  
88  	private int readSignature() throws IOException {
89  		final String rev = readLine(new byte[1024]);
90  		if (TransportBundle.V2_BUNDLE_SIGNATURE.equals(rev))
91  			return 2;
92  		throw new TransportException(transport.uri, JGitText.get().notABundle);
93  	}
94  
95  	private void readBundleV2() throws IOException {
96  		final byte[] hdrbuf = new byte[1024];
97  		final LinkedHashMap<String, Ref> avail = new LinkedHashMap<>();
98  		for (;;) {
99  			String line = readLine(hdrbuf);
100 			if (line.length() == 0)
101 				break;
102 
103 			if (line.charAt(0) == '-') {
104 				ObjectId id = ObjectId.fromString(line.substring(1, 41));
105 				String shortDesc = null;
106 				if (line.length() > 42)
107 					shortDesc = line.substring(42);
108 				prereqs.put(id, shortDesc);
109 				continue;
110 			}
111 
112 			final String name = line.substring(41, line.length());
113 			final ObjectId id = ObjectId.fromString(line.substring(0, 40));
114 			final Ref prior = avail.put(name, new ObjectIdRef.Unpeeled(
115 					Ref.Storage.NETWORK, name, id));
116 			if (prior != null)
117 				throw duplicateAdvertisement(name);
118 		}
119 		available(avail);
120 	}
121 
122 	private PackProtocolException duplicateAdvertisement(String name) {
123 		return new PackProtocolException(transport.uri,
124 				MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, name));
125 	}
126 
127 	private String readLine(byte[] hdrbuf) throws IOException {
128 		StringBuilder line = new StringBuilder();
129 		boolean done = false;
130 		while (!done) {
131 			bin.mark(hdrbuf.length);
132 			final int cnt = bin.read(hdrbuf);
133 			if (cnt < 0) {
134 				throw new EOFException(JGitText.get().shortReadOfBlock);
135 			}
136 			int lf = 0;
137 			while (lf < cnt && hdrbuf[lf] != '\n') {
138 				lf++;
139 			}
140 			bin.reset();
141 			IO.skipFully(bin, lf);
142 			if (lf < cnt && hdrbuf[lf] == '\n') {
143 				IO.skipFully(bin, 1);
144 				done = true;
145 			}
146 			line.append(RawParseUtils.decode(UTF_8, hdrbuf, 0, lf));
147 		}
148 		return line.toString();
149 	}
150 
151 	/** {@inheritDoc} */
152 	@Override
153 	public boolean didFetchTestConnectivity() {
154 		return false;
155 	}
156 
157 	/** {@inheritDoc} */
158 	@Override
159 	protected void doFetch(final ProgressMonitor monitor,
160 			final Collection<Ref> want, final Set<ObjectId> have)
161 			throws TransportException {
162 		verifyPrerequisites();
163 		try {
164 			try (ObjectInserter ins = transport.local.newObjectInserter()) {
165 				PackParser parser = ins.newPackParser(bin);
166 				parser.setAllowThin(true);
167 				parser.setObjectChecker(transport.getObjectChecker());
168 				parser.setLockMessage(lockMessage);
169 				packLock = parser.parse(NullProgressMonitor.INSTANCE);
170 				ins.flush();
171 			}
172 		} catch (IOException | RuntimeException err) {
173 			close();
174 			throw new TransportException(transport.uri, err.getMessage(), err);
175 		}
176 	}
177 
178 	/** {@inheritDoc} */
179 	@Override
180 	public void setPackLockMessage(String message) {
181 		lockMessage = message;
182 	}
183 
184 	/** {@inheritDoc} */
185 	@Override
186 	public Collection<PackLock> getPackLocks() {
187 		if (packLock != null)
188 			return Collections.singleton(packLock);
189 		return Collections.<PackLock> emptyList();
190 	}
191 
192 	private void verifyPrerequisites() throws TransportException {
193 		if (prereqs.isEmpty())
194 			return;
195 
196 		try (RevWalkRevWalk.html#RevWalk">RevWalk rw = new RevWalk(transport.local)) {
197 			final RevFlag PREREQ = rw.newFlag("PREREQ"); //$NON-NLS-1$
198 			final RevFlag SEEN = rw.newFlag("SEEN"); //$NON-NLS-1$
199 
200 			final Map<ObjectId, String> missing = new HashMap<>();
201 			final List<RevObject> commits = new ArrayList<>();
202 			for (Map.Entry<ObjectId, String> e : prereqs.entrySet()) {
203 				ObjectId p = e.getKey();
204 				try {
205 					final RevCommit c = rw.parseCommit(p);
206 					if (!c.has(PREREQ)) {
207 						c.add(PREREQ);
208 						commits.add(c);
209 					}
210 				} catch (MissingObjectException notFound) {
211 					missing.put(p, e.getValue());
212 				} catch (IOException err) {
213 					throw new TransportException(transport.uri, MessageFormat
214 							.format(JGitText.get().cannotReadCommit, p.name()),
215 							err);
216 				}
217 			}
218 			if (!missing.isEmpty())
219 				throw new MissingBundlePrerequisiteException(transport.uri,
220 						missing);
221 
222 			List<Ref> localRefs;
223 			try {
224 				localRefs = transport.local.getRefDatabase().getRefs();
225 			} catch (IOException e) {
226 				throw new TransportException(transport.uri, e.getMessage(), e);
227 			}
228 			for (Ref r : localRefs) {
229 				try {
230 					rw.markStart(rw.parseCommit(r.getObjectId()));
231 				} catch (IOException readError) {
232 					// If we cannot read the value of the ref skip it.
233 				}
234 			}
235 
236 			int remaining = commits.size();
237 			try {
238 				RevCommit c;
239 				while ((c = rw.next()) != null) {
240 					if (c.has(PREREQ)) {
241 						c.add(SEEN);
242 						if (--remaining == 0)
243 							break;
244 					}
245 				}
246 			} catch (IOException err) {
247 				throw new TransportException(transport.uri,
248 						JGitText.get().cannotReadObject, err);
249 			}
250 
251 			if (remaining > 0) {
252 				for (RevObject o : commits) {
253 					if (!o.has(SEEN))
254 						missing.put(o, prereqs.get(o));
255 				}
256 				throw new MissingBundlePrerequisiteException(transport.uri,
257 						missing);
258 			}
259 		}
260 	}
261 
262 	/** {@inheritDoc} */
263 	@Override
264 	public void close() {
265 		if (bin != null) {
266 			try {
267 				bin.close();
268 			} catch (IOException ie) {
269 				// Ignore close failures.
270 			} finally {
271 				bin = null;
272 			}
273 		}
274 	}
275 }