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