1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 package org.eclipse.jgit.gitrepo;
44
45 import java.io.FileInputStream;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.net.URI;
49 import java.net.URISyntaxException;
50 import java.text.MessageFormat;
51 import java.util.ArrayList;
52 import java.util.Collections;
53 import java.util.HashMap;
54 import java.util.HashSet;
55 import java.util.Iterator;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.Set;
59
60 import org.eclipse.jgit.annotations.NonNull;
61 import org.eclipse.jgit.api.errors.GitAPIException;
62 import org.eclipse.jgit.gitrepo.RepoProject.CopyFile;
63 import org.eclipse.jgit.gitrepo.RepoProject.LinkFile;
64 import org.eclipse.jgit.gitrepo.RepoProject.ReferenceFile;
65 import org.eclipse.jgit.gitrepo.internal.RepoText;
66 import org.eclipse.jgit.internal.JGitText;
67 import org.eclipse.jgit.lib.Repository;
68 import org.xml.sax.Attributes;
69 import org.xml.sax.InputSource;
70 import org.xml.sax.SAXException;
71 import org.xml.sax.XMLReader;
72 import org.xml.sax.helpers.DefaultHandler;
73 import org.xml.sax.helpers.XMLReaderFactory;
74
75
76
77
78
79
80
81 public class ManifestParser extends DefaultHandler {
82 private final String filename;
83 private final URI baseUrl;
84 private final String defaultBranch;
85 private final Repository rootRepo;
86 private final Map<String, Remote> remotes;
87 private final Set<String> plusGroups;
88 private final Set<String> minusGroups;
89 private final List<RepoProject> projects;
90 private final List<RepoProject> filteredProjects;
91 private final IncludedFileReader includedReader;
92
93 private String defaultRemote;
94 private String defaultRevision;
95 private int xmlInRead;
96 private RepoProject currentProject;
97
98
99
100
101 public interface IncludedFileReader {
102
103
104
105
106
107
108
109
110
111 public InputStream readIncludeFile(String path)
112 throws GitAPIException, IOException;
113 }
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133 public ManifestParser(IncludedFileReader includedReader, String filename,
134 String defaultBranch, String baseUrl, String groups,
135 Repository rootRepo) {
136 this.includedReader = includedReader;
137 this.filename = filename;
138 this.defaultBranch = defaultBranch;
139 this.rootRepo = rootRepo;
140 this.baseUrl = normalizeEmptyPath(URI.create(baseUrl));
141
142 plusGroups = new HashSet<>();
143 minusGroups = new HashSet<>();
144 if (groups == null || groups.length() == 0
145 || groups.equals("default")) {
146
147 minusGroups.add("notdefault");
148 } else {
149 for (String group : groups.split(",")) {
150 if (group.startsWith("-"))
151 minusGroups.add(group.substring(1));
152 else
153 plusGroups.add(group);
154 }
155 }
156
157 remotes = new HashMap<>();
158 projects = new ArrayList<>();
159 filteredProjects = new ArrayList<>();
160 }
161
162
163
164
165
166
167
168
169 public void read(InputStream inputStream) throws IOException {
170 xmlInRead++;
171 final XMLReader xr;
172 try {
173 xr = XMLReaderFactory.createXMLReader();
174 } catch (SAXException e) {
175 throw new IOException(JGitText.get().noXMLParserAvailable);
176 }
177 xr.setContentHandler(this);
178 try {
179 xr.parse(new InputSource(inputStream));
180 } catch (SAXException e) {
181 throw new IOException(RepoText.get().errorParsingManifestFile, e);
182 }
183 }
184
185
186 @Override
187 public void startElement(
188 String uri,
189 String localName,
190 String qName,
191 Attributes attributes) throws SAXException {
192 if ("project".equals(qName)) {
193 if (attributes.getValue("name") == null) {
194 throw new SAXException(RepoText.get().invalidManifest);
195 }
196 currentProject = new RepoProject(
197 attributes.getValue("name"),
198 attributes.getValue("path"),
199 attributes.getValue("revision"),
200 attributes.getValue("remote"),
201 attributes.getValue("groups"));
202 currentProject.setRecommendShallow(
203 attributes.getValue("clone-depth"));
204 } else if ("remote".equals(qName)) {
205 String alias = attributes.getValue("alias");
206 String fetch = attributes.getValue("fetch");
207 String revision = attributes.getValue("revision");
208 Remote remote = new Remote(fetch, revision);
209 remotes.put(attributes.getValue("name"), remote);
210 if (alias != null)
211 remotes.put(alias, remote);
212 } else if ("default".equals(qName)) {
213 defaultRemote = attributes.getValue("remote");
214 defaultRevision = attributes.getValue("revision");
215 } else if ("copyfile".equals(qName)) {
216 if (currentProject == null)
217 throw new SAXException(RepoText.get().invalidManifest);
218 currentProject.addCopyFile(new CopyFile(
219 rootRepo,
220 currentProject.getPath(),
221 attributes.getValue("src"),
222 attributes.getValue("dest")));
223 } else if ("linkfile".equals(qName)) {
224 if (currentProject == null) {
225 throw new SAXException(RepoText.get().invalidManifest);
226 }
227 currentProject.addLinkFile(new LinkFile(
228 rootRepo,
229 currentProject.getPath(),
230 attributes.getValue("src"),
231 attributes.getValue("dest")));
232 } else if ("include".equals(qName)) {
233 String name = attributes.getValue("name");
234 if (includedReader != null) {
235 try (InputStream is = includedReader.readIncludeFile(name)) {
236 if (is == null) {
237 throw new SAXException(
238 RepoText.get().errorIncludeNotImplemented);
239 }
240 read(is);
241 } catch (Exception e) {
242 throw new SAXException(MessageFormat.format(
243 RepoText.get().errorIncludeFile, name), e);
244 }
245 } else if (filename != null) {
246 int index = filename.lastIndexOf('/');
247 String path = filename.substring(0, index + 1) + name;
248 try (InputStream is = new FileInputStream(path)) {
249 read(is);
250 } catch (IOException e) {
251 throw new SAXException(MessageFormat.format(
252 RepoText.get().errorIncludeFile, path), e);
253 }
254 }
255 } else if ("remove-project".equals(qName)) {
256 String name = attributes.getValue("name");
257 projects.removeIf((p) -> p.getName().equals(name));
258 }
259 }
260
261
262 @Override
263 public void endElement(
264 String uri,
265 String localName,
266 String qName) throws SAXException {
267 if ("project".equals(qName)) {
268 projects.add(currentProject);
269 currentProject = null;
270 }
271 }
272
273
274 @Override
275 public void endDocument() throws SAXException {
276 xmlInRead--;
277 if (xmlInRead != 0)
278 return;
279
280
281 Map<String, URI> remoteUrls = new HashMap<>();
282 if (defaultRevision == null && defaultRemote != null) {
283 Remote remote = remotes.get(defaultRemote);
284 if (remote != null) {
285 defaultRevision = remote.revision;
286 }
287 if (defaultRevision == null) {
288 defaultRevision = defaultBranch;
289 }
290 }
291 for (RepoProject proj : projects) {
292 String remote = proj.getRemote();
293 String revision = defaultRevision;
294 if (remote == null) {
295 if (defaultRemote == null) {
296 if (filename != null)
297 throw new SAXException(MessageFormat.format(
298 RepoText.get().errorNoDefaultFilename,
299 filename));
300 else
301 throw new SAXException(
302 RepoText.get().errorNoDefault);
303 }
304 remote = defaultRemote;
305 } else {
306 Remote r = remotes.get(remote);
307 if (r != null && r.revision != null) {
308 revision = r.revision;
309 }
310 }
311 URI remoteUrl = remoteUrls.get(remote);
312 if (remoteUrl == null) {
313 String fetch = remotes.get(remote).fetch;
314 if (fetch == null) {
315 throw new SAXException(MessageFormat
316 .format(RepoText.get().errorNoFetch, remote));
317 }
318 remoteUrl = normalizeEmptyPath(baseUrl.resolve(fetch));
319 remoteUrls.put(remote, remoteUrl);
320 }
321 proj.setUrl(remoteUrl.resolve(proj.getName()).toString())
322 .setDefaultRevision(revision);
323 }
324
325 filteredProjects.addAll(projects);
326 removeNotInGroup();
327 removeOverlaps();
328 }
329
330 static URI normalizeEmptyPath(URI u) {
331
332
333
334 if (u.getHost() != null && !u.getHost().isEmpty() &&
335 (u.getPath() == null || u.getPath().isEmpty())) {
336 try {
337 return new URI(u.getScheme(),
338 u.getUserInfo(), u.getHost(), u.getPort(),
339 "/", u.getQuery(), u.getFragment());
340 } catch (URISyntaxException x) {
341 throw new IllegalArgumentException(x.getMessage(), x);
342 }
343 }
344 return u;
345 }
346
347
348
349
350
351
352 public List<RepoProject> getProjects() {
353 return projects;
354 }
355
356
357
358
359
360
361 @NonNull
362 public List<RepoProject> getFilteredProjects() {
363 return filteredProjects;
364 }
365
366
367 void removeNotInGroup() {
368 Iterator<RepoProject> iter = filteredProjects.iterator();
369 while (iter.hasNext())
370 if (!inGroups(iter.next()))
371 iter.remove();
372 }
373
374
375 void removeOverlaps() {
376 Collections.sort(filteredProjects);
377 Iterator<RepoProject> iter = filteredProjects.iterator();
378 if (!iter.hasNext())
379 return;
380 RepoProject last = iter.next();
381 while (iter.hasNext()) {
382 RepoProject p = iter.next();
383 if (last.isAncestorOf(p))
384 iter.remove();
385 else
386 last = p;
387 }
388 removeNestedCopyAndLinkfiles();
389 }
390
391 private void removeNestedCopyAndLinkfiles() {
392 for (RepoProject proj : filteredProjects) {
393 List<CopyFile> copyfiles = new ArrayList<>(proj.getCopyFiles());
394 proj.clearCopyFiles();
395 for (CopyFile copyfile : copyfiles) {
396 if (!isNestedReferencefile(copyfile)) {
397 proj.addCopyFile(copyfile);
398 }
399 }
400 List<LinkFile> linkfiles = new ArrayList<>(proj.getLinkFiles());
401 proj.clearLinkFiles();
402 for (LinkFile linkfile : linkfiles) {
403 if (!isNestedReferencefile(linkfile)) {
404 proj.addLinkFile(linkfile);
405 }
406 }
407 }
408 }
409
410 boolean inGroups(RepoProject proj) {
411 for (String group : minusGroups) {
412 if (proj.inGroup(group)) {
413
414 return false;
415 }
416 }
417 if (plusGroups.isEmpty() || plusGroups.contains("all")) {
418
419 return true;
420 }
421 for (String group : plusGroups) {
422 if (proj.inGroup(group))
423 return true;
424 }
425 return false;
426 }
427
428 private boolean isNestedReferencefile(ReferenceFile referencefile) {
429 if (referencefile.dest.indexOf('/') == -1) {
430
431 return false;
432 }
433 for (RepoProject proj : filteredProjects) {
434 if (proj.getPath().compareTo(referencefile.dest) > 0) {
435
436
437 return false;
438 }
439 if (proj.isAncestorOf(referencefile.dest)) {
440 return true;
441 }
442 }
443 return false;
444 }
445
446 private static class Remote {
447 final String fetch;
448 final String revision;
449
450 Remote(String fetch, String revision) {
451 this.fetch = fetch;
452 this.revision = revision;
453 }
454 }
455 }