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 public @NonNull List<RepoProject> getFilteredProjects() {
362 return filteredProjects;
363 }
364
365
366 void removeNotInGroup() {
367 Iterator<RepoProject> iter = filteredProjects.iterator();
368 while (iter.hasNext())
369 if (!inGroups(iter.next()))
370 iter.remove();
371 }
372
373
374 void removeOverlaps() {
375 Collections.sort(filteredProjects);
376 Iterator<RepoProject> iter = filteredProjects.iterator();
377 if (!iter.hasNext())
378 return;
379 RepoProject last = iter.next();
380 while (iter.hasNext()) {
381 RepoProject p = iter.next();
382 if (last.isAncestorOf(p))
383 iter.remove();
384 else
385 last = p;
386 }
387 removeNestedCopyAndLinkfiles();
388 }
389
390 private void removeNestedCopyAndLinkfiles() {
391 for (RepoProject proj : filteredProjects) {
392 List<CopyFile> copyfiles = new ArrayList<>(proj.getCopyFiles());
393 proj.clearCopyFiles();
394 for (CopyFile copyfile : copyfiles) {
395 if (!isNestedReferencefile(copyfile)) {
396 proj.addCopyFile(copyfile);
397 }
398 }
399 List<LinkFile> linkfiles = new ArrayList<>(proj.getLinkFiles());
400 proj.clearLinkFiles();
401 for (LinkFile linkfile : linkfiles) {
402 if (!isNestedReferencefile(linkfile)) {
403 proj.addLinkFile(linkfile);
404 }
405 }
406 }
407 }
408
409 boolean inGroups(RepoProject proj) {
410 for (String group : minusGroups) {
411 if (proj.inGroup(group)) {
412
413 return false;
414 }
415 }
416 if (plusGroups.isEmpty() || plusGroups.contains("all")) {
417
418 return true;
419 }
420 for (String group : plusGroups) {
421 if (proj.inGroup(group))
422 return true;
423 }
424 return false;
425 }
426
427 private boolean isNestedReferencefile(ReferenceFile referencefile) {
428 if (referencefile.dest.indexOf('/') == -1) {
429
430 return false;
431 }
432 for (RepoProject proj : filteredProjects) {
433 if (proj.getPath().compareTo(referencefile.dest) > 0) {
434
435
436 return false;
437 }
438 if (proj.isAncestorOf(referencefile.dest)) {
439 return true;
440 }
441 }
442 return false;
443 }
444
445 private static class Remote {
446 final String fetch;
447 final String revision;
448
449 Remote(String fetch, String revision) {
450 this.fetch = fetch;
451 this.revision = revision;
452 }
453 }
454 }