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