diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java new file mode 100644 index 000000000..1005b39ec --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.gitrepo; + +import static org.junit.Assert.assertTrue; + +import java.io.StringBufferInputStream; +import java.util.HashSet; +import java.util.Set; + +import org.junit.Test; + +public class ManifestParserTest { + + @Test + public void testManifestParser() throws Exception { + String baseUrl = "https://git.google.com/"; + StringBuilder xmlContent = new StringBuilder(); + Set results = new HashSet(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append(""); + + ManifestParser parser = new ManifestParser( + null, null, "master", baseUrl, null, null); + parser.read(new StringBufferInputStream(xmlContent.toString())); + // Unfiltered projects should have them all. + results.clear(); + results.add("foo"); + results.add("bar"); + results.add("foo/a"); + results.add("b"); + for (RepoProject proj : parser.getProjects()) { + String msg = String.format( + "project \"%s\" should be included in unfiltered projects", + proj.path); + assertTrue(msg, results.contains(proj.path)); + results.remove(proj.path); + } + assertTrue( + "Unfiltered projects shouldn't contain any unexpected results", + results.isEmpty()); + // Filtered projects should have foo & b + results.clear(); + results.add("foo"); + results.add("b"); + for (RepoProject proj : parser.getFilteredProjects()) { + String msg = String.format( + "project \"%s\" should be included in filtered projects", + proj.path); + assertTrue(msg, results.contains(proj.path)); + results.remove(proj.path); + } + assertTrue( + "Filtered projects shouldn't contain any unexpected results", + results.isEmpty()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java index 3d86cfd5b..0caf9c6c2 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java @@ -49,6 +49,8 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileReader; +import java.util.HashSet; +import java.util.Set; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.junit.JGitTestUtil; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java new file mode 100644 index 000000000..2d509381e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.gitrepo; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.gitrepo.RepoProject.CopyFile; +import org.eclipse.jgit.gitrepo.internal.RepoText; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.Repository; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.helpers.XMLReaderFactory; + +/** + * Repo XML manifest parser. + * + * @see git-repo project page + * @since 4.0 + */ +public class ManifestParser extends DefaultHandler { + private final String filename; + private final String baseUrl; + private final String defaultBranch; + private final Repository rootRepo; + private final Map remotes; + private final Set plusGroups; + private final Set minusGroups; + private List projects; + private List filteredProjects; + private String defaultRemote; + private String defaultRevision; + private IncludedFileReader includedReader; + private int xmlInRead; + private RepoProject currentProject; + + /** + * A callback to read included xml files. + */ + public interface IncludedFileReader { + /** + * Read a file from the same base dir of the manifest xml file. + * + * @param path + * The relative path to the file to read + * @return the {@code InputStream} of the file. + * @throws GitAPIException + * @throws IOException + */ + public InputStream readIncludeFile(String path) + throws GitAPIException, IOException; + } + + /** + * @param includedReader + * @param filename + * @param defaultBranch + * @param baseUrl + * @param groups + * @param rootRepo + */ + public ManifestParser(IncludedFileReader includedReader, String filename, + String defaultBranch, String baseUrl, String groups, + Repository rootRepo) { + this.includedReader = includedReader; + this.filename = filename; + this.defaultBranch = defaultBranch; + this.rootRepo = rootRepo; + + // Strip trailing /s to match repo behavior. + int lastIndex = baseUrl.length() - 1; + while (lastIndex >= 0 && baseUrl.charAt(lastIndex) == '/') + lastIndex--; + this.baseUrl = baseUrl.substring(0, lastIndex + 1); + + plusGroups = new HashSet(); + minusGroups = new HashSet(); + if (groups == null || groups.length() == 0 + || groups.equals("default")) { //$NON-NLS-1$ + // default means "all,-notdefault" + minusGroups.add("notdefault"); //$NON-NLS-1$ + } else { + for (String group : groups.split(",")) { //$NON-NLS-1$ + if (group.startsWith("-")) //$NON-NLS-1$ + minusGroups.add(group.substring(1)); + else + plusGroups.add(group); + } + } + + remotes = new HashMap(); + projects = new ArrayList(); + filteredProjects = new ArrayList(); + } + + /** + * Read the xml file. + * + * @param inputStream + */ + public void read(InputStream inputStream) throws IOException { + xmlInRead++; + final XMLReader xr; + try { + xr = XMLReaderFactory.createXMLReader(); + } catch (SAXException e) { + throw new IOException(JGitText.get().noXMLParserAvailable); + } + xr.setContentHandler(this); + try { + xr.parse(new InputSource(inputStream)); + } catch (SAXException e) { + IOException error = new IOException( + RepoText.get().errorParsingManifestFile); + error.initCause(e); + throw error; + } + } + + @Override + public void startElement( + String uri, + String localName, + String qName, + Attributes attributes) throws SAXException { + if ("project".equals(qName)) { //$NON-NLS-1$ + currentProject = new RepoProject( + attributes.getValue("name"), //$NON-NLS-1$ + attributes.getValue("path"), //$NON-NLS-1$ + attributes.getValue("revision"), //$NON-NLS-1$ + attributes.getValue("remote"), //$NON-NLS-1$ + attributes.getValue("groups")); //$NON-NLS-1$ + } else if ("remote".equals(qName)) { //$NON-NLS-1$ + String alias = attributes.getValue("alias"); //$NON-NLS-1$ + String fetch = attributes.getValue("fetch"); //$NON-NLS-1$ + remotes.put(attributes.getValue("name"), fetch); //$NON-NLS-1$ + if (alias != null) + remotes.put(alias, fetch); + } else if ("default".equals(qName)) { //$NON-NLS-1$ + defaultRemote = attributes.getValue("remote"); //$NON-NLS-1$ + defaultRevision = attributes.getValue("revision"); //$NON-NLS-1$ + if (defaultRevision == null) + defaultRevision = defaultBranch; + } else if ("copyfile".equals(qName)) { //$NON-NLS-1$ + if (currentProject == null) + throw new SAXException(RepoText.get().invalidManifest); + currentProject.addCopyFile(new CopyFile( + rootRepo, + currentProject.path, + attributes.getValue("src"), //$NON-NLS-1$ + attributes.getValue("dest"))); //$NON-NLS-1$ + } else if ("include".equals(qName)) { //$NON-NLS-1$ + String name = attributes.getValue("name"); //$NON-NLS-1$ + InputStream is = null; + if (includedReader != null) { + try { + is = includedReader.readIncludeFile(name); + } catch (Exception e) { + throw new SAXException(MessageFormat.format( + RepoText.get().errorIncludeFile, name), e); + } + } else if (filename != null) { + int index = filename.lastIndexOf('/'); + String path = filename.substring(0, index + 1) + name; + try { + is = new FileInputStream(path); + } catch (IOException e) { + throw new SAXException(MessageFormat.format( + RepoText.get().errorIncludeFile, path), e); + } + } + if (is == null) { + throw new SAXException( + RepoText.get().errorIncludeNotImplemented); + } + try { + read(is); + } catch (IOException e) { + throw new SAXException(e); + } + } + } + + @Override + public void endElement( + String uri, + String localName, + String qName) throws SAXException { + if ("project".equals(qName)) { //$NON-NLS-1$ + projects.add(currentProject); + currentProject = null; + } + } + + @Override + public void endDocument() throws SAXException { + xmlInRead--; + if (xmlInRead != 0) + return; + + // Only do the following after we finished reading everything. + Map remoteUrls = new HashMap(); + URI baseUri; + try { + baseUri = new URI(baseUrl); + } catch (URISyntaxException e) { + throw new SAXException(e); + } + for (RepoProject proj : projects) { + String remote = proj.remote; + if (remote == null) { + if (defaultRemote == null) { + if (filename != null) + throw new SAXException(MessageFormat.format( + RepoText.get().errorNoDefaultFilename, + filename)); + else + throw new SAXException( + RepoText.get().errorNoDefault); + } + remote = defaultRemote; + } + String remoteUrl = remoteUrls.get(remote); + if (remoteUrl == null) { + remoteUrl = baseUri.resolve(remotes.get(remote)).toString(); + if (!remoteUrl.endsWith("/")) //$NON-NLS-1$ + remoteUrl = remoteUrl + "/"; //$NON-NLS-1$ + remoteUrls.put(remote, remoteUrl); + } + proj.setUrl(remoteUrl + proj.name) + .setDefaultRevision(defaultRevision); + } + + filteredProjects.addAll(projects); + removeNotInGroup(); + removeOverlaps(); + } + + /** + * Getter for projects. + */ + public List getProjects() { + return projects; + } + + /** + * Getter for filterdProjects. + */ + public List getFilteredProjects() { + return filteredProjects; + } + + /** Remove projects that are not in our desired groups. */ + void removeNotInGroup() { + Iterator iter = filteredProjects.iterator(); + while (iter.hasNext()) + if (!inGroups(iter.next())) + iter.remove(); + } + + /** Remove projects that sits in a subdirectory of any other project. */ + void removeOverlaps() { + Collections.sort(filteredProjects); + Iterator iter = filteredProjects.iterator(); + if (!iter.hasNext()) + return; + RepoProject last = iter.next(); + while (iter.hasNext()) { + RepoProject p = iter.next(); + if (last.isAncestorOf(p)) + iter.remove(); + else + last = p; + } + } + + boolean inGroups(RepoProject proj) { + for (String group : minusGroups) { + if (proj.groups.contains(group)) { + // minus groups have highest priority. + return false; + } + } + if (plusGroups.isEmpty() || plusGroups.contains("all")) { //$NON-NLS-1$ + // empty plus groups means "all" + return true; + } + for (String group : plusGroups) { + if (proj.groups.contains(group)) + return true; + } + return false; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java index 0cf5c7e48..e8a9e18f1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java @@ -44,22 +44,13 @@ import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; -import java.net.URISyntaxException; -import java.nio.channels.FileChannel; import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Set; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.GitCommand; @@ -70,6 +61,8 @@ import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.gitrepo.ManifestParser.IncludedFileReader; +import org.eclipse.jgit.gitrepo.RepoProject.CopyFile; import org.eclipse.jgit.gitrepo.internal.RepoText; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.CommitBuilder; @@ -89,12 +82,6 @@ import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.util.FileUtils; -import org.xml.sax.Attributes; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.XMLReader; -import org.xml.sax.helpers.DefaultHandler; -import org.xml.sax.helpers.XMLReaderFactory; /** * A class used to execute a repo command. @@ -124,7 +111,7 @@ public class RepoCommand extends GitCommand { private InputStream inputStream; private IncludedFileReader includedReader; - private List bareProjects; + private List bareProjects; private Git git; private ProgressMonitor monitor; @@ -221,341 +208,6 @@ protected byte[] readFileFromRepo(Repository repo, } } - /** - * A callback to read included xml files. - * - * @since 3.5 - */ - public interface IncludedFileReader { - /** - * Read a file from the same base dir of the manifest xml file. - * - * @param path - * The relative path to the file to read - * @return the {@code InputStream} of the file. - * @throws GitAPIException - * @throws IOException - */ - public InputStream readIncludeFile(String path) - throws GitAPIException, IOException; - } - - private static class CopyFile { - final Repository repo; - final String path; - final String src; - final String dest; - - CopyFile(Repository repo, String path, String src, String dest) { - this.repo = repo; - this.path = path; - this.src = src; - this.dest = dest; - } - - void copy() throws IOException { - File srcFile = new File(repo.getWorkTree(), - path + "/" + src); //$NON-NLS-1$ - File destFile = new File(repo.getWorkTree(), dest); - FileInputStream input = new FileInputStream(srcFile); - try { - FileOutputStream output = new FileOutputStream(destFile); - try { - FileChannel channel = input.getChannel(); - output.getChannel().transferFrom(channel, 0, channel.size()); - } finally { - output.close(); - } - } finally { - input.close(); - } - } - } - - private static class Project implements Comparable { - final String name; - final String path; - final String revision; - final String remote; - final Set groups; - final List copyfiles; - - Project(String name, String path, String revision, - String remote, String groups) { - this.name = name; - if (path != null) - this.path = path; - else - this.path = name; - this.revision = revision; - this.remote = remote; - this.groups = new HashSet(); - if (groups != null && groups.length() > 0) - this.groups.addAll(Arrays.asList(groups.split(","))); //$NON-NLS-1$ - copyfiles = new ArrayList(); - } - - void addCopyFile(CopyFile copyfile) { - copyfiles.add(copyfile); - } - - String getPathWithSlash() { - if (path.endsWith("/")) //$NON-NLS-1$ - return path; - else - return path + "/"; //$NON-NLS-1$ - } - - boolean isAncestorOf(Project that) { - return that.getPathWithSlash().startsWith(this.getPathWithSlash()); - } - - @Override - public boolean equals(Object o) { - if (o instanceof Project) { - Project that = (Project) o; - return this.getPathWithSlash().equals(that.getPathWithSlash()); - } - return false; - } - - @Override - public int hashCode() { - return this.getPathWithSlash().hashCode(); - } - - public int compareTo(Project that) { - return this.getPathWithSlash().compareTo(that.getPathWithSlash()); - } - } - - private static class XmlManifest extends DefaultHandler { - private final RepoCommand command; - private final String filename; - private final String baseUrl; - private final Map remotes; - private final Set plusGroups; - private final Set minusGroups; - private List projects; - private String defaultRemote; - private String defaultRevision; - private IncludedFileReader includedReader; - private int xmlInRead; - private Project currentProject; - - XmlManifest(RepoCommand command, IncludedFileReader includedReader, - String filename, String baseUrl, String groups) { - this.command = command; - this.includedReader = includedReader; - this.filename = filename; - - // Strip trailing /s to match repo behavior. - int lastIndex = baseUrl.length() - 1; - while (lastIndex >= 0 && baseUrl.charAt(lastIndex) == '/') - lastIndex--; - this.baseUrl = baseUrl.substring(0, lastIndex + 1); - - remotes = new HashMap(); - projects = new ArrayList(); - plusGroups = new HashSet(); - minusGroups = new HashSet(); - if (groups == null || groups.length() == 0 || groups.equals("default")) { //$NON-NLS-1$ - // default means "all,-notdefault" - minusGroups.add("notdefault"); //$NON-NLS-1$ - } else { - for (String group : groups.split(",")) { //$NON-NLS-1$ - if (group.startsWith("-")) //$NON-NLS-1$ - minusGroups.add(group.substring(1)); - else - plusGroups.add(group); - } - } - } - - void read(InputStream inputStream) throws IOException { - xmlInRead++; - final XMLReader xr; - try { - xr = XMLReaderFactory.createXMLReader(); - } catch (SAXException e) { - throw new IOException(JGitText.get().noXMLParserAvailable); - } - xr.setContentHandler(this); - try { - xr.parse(new InputSource(inputStream)); - } catch (SAXException e) { - IOException error = new IOException( - RepoText.get().errorParsingManifestFile); - error.initCause(e); - throw error; - } - } - - @Override - public void startElement( - String uri, - String localName, - String qName, - Attributes attributes) throws SAXException { - if ("project".equals(qName)) { //$NON-NLS-1$ - currentProject = new Project( - attributes.getValue("name"), //$NON-NLS-1$ - attributes.getValue("path"), //$NON-NLS-1$ - attributes.getValue("revision"), //$NON-NLS-1$ - attributes.getValue("remote"), //$NON-NLS-1$ - attributes.getValue("groups")); //$NON-NLS-1$ - } else if ("remote".equals(qName)) { //$NON-NLS-1$ - String alias = attributes.getValue("alias"); //$NON-NLS-1$ - String fetch = attributes.getValue("fetch"); //$NON-NLS-1$ - remotes.put(attributes.getValue("name"), fetch); //$NON-NLS-1$ - if (alias != null) - remotes.put(alias, fetch); - } else if ("default".equals(qName)) { //$NON-NLS-1$ - defaultRemote = attributes.getValue("remote"); //$NON-NLS-1$ - defaultRevision = attributes.getValue("revision"); //$NON-NLS-1$ - if (defaultRevision == null) - defaultRevision = command.branch; - } else if ("copyfile".equals(qName)) { //$NON-NLS-1$ - if (currentProject == null) - throw new SAXException(RepoText.get().invalidManifest); - currentProject.addCopyFile(new CopyFile( - command.repo, - currentProject.path, - attributes.getValue("src"), //$NON-NLS-1$ - attributes.getValue("dest"))); //$NON-NLS-1$ - } else if ("include".equals(qName)) { //$NON-NLS-1$ - String name = attributes.getValue("name"); //$NON-NLS-1$ - InputStream is = null; - if (includedReader != null) { - try { - is = includedReader.readIncludeFile(name); - } catch (Exception e) { - throw new SAXException(MessageFormat.format( - RepoText.get().errorIncludeFile, name), e); - } - } else if (filename != null) { - int index = filename.lastIndexOf('/'); - String path = filename.substring(0, index + 1) + name; - try { - is = new FileInputStream(path); - } catch (IOException e) { - throw new SAXException(MessageFormat.format( - RepoText.get().errorIncludeFile, path), e); - } - } - if (is == null) { - throw new SAXException( - RepoText.get().errorIncludeNotImplemented); - } - try { - read(is); - } catch (IOException e) { - throw new SAXException(e); - } - } - } - - @Override - public void endElement( - String uri, - String localName, - String qName) throws SAXException { - if ("project".equals(qName)) { //$NON-NLS-1$ - projects.add(currentProject); - currentProject = null; - } - } - - @Override - public void endDocument() throws SAXException { - xmlInRead--; - if (xmlInRead != 0) - return; - - // Only do the following after we finished reading everything. - removeNotInGroup(); - removeOverlaps(); - - Map remoteUrls = new HashMap(); - URI baseUri; - try { - baseUri = new URI(baseUrl); - } catch (URISyntaxException e) { - throw new SAXException(e); - } - for (Project proj : projects) { - String remote = proj.remote; - if (remote == null) { - if (defaultRemote == null) { - if (filename != null) - throw new SAXException(MessageFormat.format( - RepoText.get().errorNoDefaultFilename, - filename)); - else - throw new SAXException( - RepoText.get().errorNoDefault); - } - remote = defaultRemote; - } - String remoteUrl = remoteUrls.get(remote); - if (remoteUrl == null) { - remoteUrl = baseUri.resolve(remotes.get(remote)).toString(); - if (!remoteUrl.endsWith("/")) //$NON-NLS-1$ - remoteUrl = remoteUrl + "/"; //$NON-NLS-1$ - remoteUrls.put(remote, remoteUrl); - } - - command.addSubmodule(remoteUrl + proj.name, - proj.path, - proj.revision == null - ? defaultRevision : proj.revision, - proj.copyfiles); - } - } - - /** Remove projects that are not in our desired groups. */ - void removeNotInGroup() { - Iterator iter = projects.iterator(); - while (iter.hasNext()) - if (!inGroups(iter.next())) - iter.remove(); - } - - /** Remove projects that sits in a subdirectory of any other project. */ - void removeOverlaps() { - Collections.sort(projects); - Iterator iter = projects.iterator(); - if (!iter.hasNext()) - return; - Project last = iter.next(); - while (iter.hasNext()) { - Project p = iter.next(); - if (last.isAncestorOf(p)) - iter.remove(); - else - last = p; - } - } - - boolean inGroups(Project proj) { - for (String group : minusGroups) { - if (proj.groups.contains(group)) { - // minus groups have highest priority. - return false; - } - } - if (plusGroups.isEmpty() || plusGroups.contains("all")) { //$NON-NLS-1$ - // empty plus groups means "all" - return true; - } - for (String group : plusGroups) { - if (proj.groups.contains(group)) - return true; - } - return false; - } - } - @SuppressWarnings("serial") private static class ManifestErrorException extends GitAPIException { ManifestErrorException(Throwable cause) { @@ -715,7 +367,7 @@ public RevCommit call() throws GitAPIException { } if (repo.isBare()) { - bareProjects = new ArrayList(); + bareProjects = new ArrayList(); if (author == null) author = new PersonIdent(repo); if (callback == null) @@ -723,11 +375,17 @@ public RevCommit call() throws GitAPIException { } else git = new Git(repo); - XmlManifest manifest = new XmlManifest( - this, includedReader, path, uri, groups); + ManifestParser parser = new ManifestParser( + includedReader, path, branch, uri, groups, repo); try { - manifest.read(inputStream); - } catch (IOException e) { + parser.read(inputStream); + for (RepoProject proj : parser.getFilteredProjects()) { + addSubmodule(proj.url, + proj.path, + proj.getRevision(), + proj.copyfiles); + } + } catch (GitAPIException | IOException e) { throw new ManifestErrorException(e); } } finally { @@ -745,7 +403,7 @@ public RevCommit call() throws GitAPIException { ObjectInserter inserter = repo.newObjectInserter(); try (RevWalk rw = new RevWalk(repo)) { Config cfg = new Config(); - for (Project proj : bareProjects) { + for (RepoProject proj : bareProjects) { String name = proj.path; String nameUri = proj.name; cfg.setString("submodule", name, "path", name); //$NON-NLS-1$ //$NON-NLS-2$ @@ -835,9 +493,9 @@ public RevCommit call() throws GitAPIException { } private void addSubmodule(String url, String name, String revision, - List copyfiles) throws SAXException { + List copyfiles) throws GitAPIException, IOException { if (repo.isBare()) { - Project proj = new Project(url, name, revision, null, null); + RepoProject proj = new RepoProject(url, name, revision, null, null); proj.copyfiles.addAll(copyfiles); bareProjects.add(proj); } else { @@ -848,24 +506,18 @@ private void addSubmodule(String url, String name, String revision, if (monitor != null) add.setProgressMonitor(monitor); - try { - Repository subRepo = add.call(); - if (revision != null) { - try (Git sub = new Git(subRepo)) { - sub.checkout().setName(findRef(revision, subRepo)) - .call(); - } - subRepo.close(); - git.add().addFilepattern(name).call(); + Repository subRepo = add.call(); + if (revision != null) { + try (Git sub = new Git(subRepo)) { + sub.checkout().setName(findRef(revision, subRepo)) + .call(); } - for (CopyFile copyfile : copyfiles) { - copyfile.copy(); - git.add().addFilepattern(copyfile.dest).call(); - } - } catch (GitAPIException e) { - throw new SAXException(e); - } catch (IOException e) { - throw new SAXException(e); + subRepo.close(); + git.add().addFilepattern(name).call(); + } + for (CopyFile copyfile : copyfiles) { + copyfile.copy(); + git.add().addFilepattern(copyfile.dest).call(); } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java new file mode 100644 index 000000000..c4648abca --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.gitrepo; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jgit.lib.Repository; + +/** + * The representation of a repo sub project. + * + * @see git-repo project page + * @since 4.0 + */ +public class RepoProject implements Comparable { + final String name; + final String path; + final String revision; + final String remote; + final Set groups; + final List copyfiles; + String url; + String defaultRevision; + + /** + * The representation of a copy file configuration. + */ + public static class CopyFile { + final Repository repo; + final String path; + final String src; + final String dest; + + /** + * @param repo + * @param path + * the path of the project containing this copyfile config. + * @param src + * @param dest + */ + public CopyFile(Repository repo, String path, String src, String dest) { + this.repo = repo; + this.path = path; + this.src = src; + this.dest = dest; + } + + /** + * Do the copy file action. + */ + public void copy() throws IOException { + File srcFile = new File(repo.getWorkTree(), + path + "/" + src); //$NON-NLS-1$ + File destFile = new File(repo.getWorkTree(), dest); + FileInputStream input = new FileInputStream(srcFile); + try { + FileOutputStream output = new FileOutputStream(destFile); + try { + FileChannel channel = input.getChannel(); + output.getChannel().transferFrom(channel, 0, channel.size()); + } finally { + output.close(); + } + } finally { + input.close(); + } + } + } + + /** + * @param name + * @param path + * @param revision + * @param remote + * @param groups + */ + public RepoProject(String name, String path, String revision, + String remote, String groups) { + this.name = name; + if (path != null) + this.path = path; + else + this.path = name; + this.revision = revision; + this.remote = remote; + this.groups = new HashSet(); + if (groups != null && groups.length() > 0) + this.groups.addAll(Arrays.asList(groups.split(","))); //$NON-NLS-1$ + copyfiles = new ArrayList(); + } + + /** + * Set the url of the sub repo. + * + * @param url + * @return this for chaining. + */ + public RepoProject setUrl(String url) { + this.url = url; + return this; + } + + /** + * Set the default revision for the sub repo. + * + * @param defaultRevision + * @return this for chaining. + */ + public RepoProject setDefaultRevision(String defaultRevision) { + this.defaultRevision = defaultRevision; + return this; + } + + /** + * Get the revision of the sub repo. + * + * @return revision if set, or default revision. + */ + public String getRevision() { + return revision == null ? defaultRevision : revision; + } + + /** + * Add a copy file configuration. + * + * @param copyfile + */ + public void addCopyFile(CopyFile copyfile) { + copyfiles.add(copyfile); + } + + String getPathWithSlash() { + if (path.endsWith("/")) //$NON-NLS-1$ + return path; + else + return path + "/"; //$NON-NLS-1$ + } + + /** + * Check if this sub repo is the ancestor of another sub repo. + */ + public boolean isAncestorOf(RepoProject that) { + return that.getPathWithSlash().startsWith(this.getPathWithSlash()); + } + + @Override + public boolean equals(Object o) { + if (o instanceof RepoProject) { + RepoProject that = (RepoProject) o; + return this.getPathWithSlash().equals(that.getPathWithSlash()); + } + return false; + } + + @Override + public int hashCode() { + return this.getPathWithSlash().hashCode(); + } + + @Override + public int compareTo(RepoProject that) { + return this.getPathWithSlash().compareTo(that.getPathWithSlash()); + } +} +