1
2
3
4
5
6
7
8
9
10
11
12
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
53
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
151 @Override
152 public boolean didFetchTestConnectivity() {
153 return false;
154 }
155
156
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
178 @Override
179 public void setPackLockMessage(String message) {
180 lockMessage = message;
181 }
182
183
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");
197 final RevFlag SEEN = rw.newFlag("SEEN");
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
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
262 @Override
263 public void close() {
264 if (bin != null) {
265 try {
266 bin.close();
267 } catch (IOException ie) {
268
269 } finally {
270 bin = null;
271 }
272 }
273 }
274 }