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