Merge "Refactor to expose ManifestParser."
This commit is contained in:
commit
a990cce776
|
@ -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<String> results = new HashSet<String>();
|
||||
xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
|
||||
.append("<manifest>")
|
||||
.append("<remote name=\"remote1\" fetch=\".\" />")
|
||||
.append("<default revision=\"master\" remote=\"remote1\" />")
|
||||
.append("<project path=\"foo\" name=\"")
|
||||
.append("foo")
|
||||
.append("\" groups=\"a,test\" />")
|
||||
.append("<project path=\"bar\" name=\"")
|
||||
.append("bar")
|
||||
.append("\" groups=\"notdefault\" />")
|
||||
.append("<project path=\"foo/a\" name=\"")
|
||||
.append("a")
|
||||
.append("\" groups=\"a\" />")
|
||||
.append("<project path=\"b\" name=\"")
|
||||
.append("b")
|
||||
.append("\" groups=\"b\" />")
|
||||
.append("</manifest>");
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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 <a href="https://code.google.com/p/git-repo/">git-repo project page</a>
|
||||
* @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<String, String> remotes;
|
||||
private final Set<String> plusGroups;
|
||||
private final Set<String> minusGroups;
|
||||
private List<RepoProject> projects;
|
||||
private List<RepoProject> 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<String>();
|
||||
minusGroups = new HashSet<String>();
|
||||
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<String, String>();
|
||||
projects = new ArrayList<RepoProject>();
|
||||
filteredProjects = new ArrayList<RepoProject>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<String, String> remoteUrls = new HashMap<String, String>();
|
||||
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<RepoProject> getProjects() {
|
||||
return projects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for filterdProjects.
|
||||
*/
|
||||
public List<RepoProject> getFilteredProjects() {
|
||||
return filteredProjects;
|
||||
}
|
||||
|
||||
/** Remove projects that are not in our desired groups. */
|
||||
void removeNotInGroup() {
|
||||
Iterator<RepoProject> 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<RepoProject> 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;
|
||||
}
|
||||
}
|
|
@ -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<RevCommit> {
|
|||
private InputStream inputStream;
|
||||
private IncludedFileReader includedReader;
|
||||
|
||||
private List<Project> bareProjects;
|
||||
private List<RepoProject> 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<Project> {
|
||||
final String name;
|
||||
final String path;
|
||||
final String revision;
|
||||
final String remote;
|
||||
final Set<String> groups;
|
||||
final List<CopyFile> 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<String>();
|
||||
if (groups != null && groups.length() > 0)
|
||||
this.groups.addAll(Arrays.asList(groups.split(","))); //$NON-NLS-1$
|
||||
copyfiles = new ArrayList<CopyFile>();
|
||||
}
|
||||
|
||||
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<String, String> remotes;
|
||||
private final Set<String> plusGroups;
|
||||
private final Set<String> minusGroups;
|
||||
private List<Project> 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<String, String>();
|
||||
projects = new ArrayList<Project>();
|
||||
plusGroups = new HashSet<String>();
|
||||
minusGroups = new HashSet<String>();
|
||||
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<String, String> remoteUrls = new HashMap<String, String>();
|
||||
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<Project> 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<Project> 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<Project>();
|
||||
bareProjects = new ArrayList<RepoProject>();
|
||||
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<CopyFile> copyfiles) throws SAXException {
|
||||
List<CopyFile> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <a href="https://code.google.com/p/git-repo/">git-repo project page</a>
|
||||
* @since 4.0
|
||||
*/
|
||||
public class RepoProject implements Comparable<RepoProject> {
|
||||
final String name;
|
||||
final String path;
|
||||
final String revision;
|
||||
final String remote;
|
||||
final Set<String> groups;
|
||||
final List<CopyFile> 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<String>();
|
||||
if (groups != null && groups.length() > 0)
|
||||
this.groups.addAll(Arrays.asList(groups.split(","))); //$NON-NLS-1$
|
||||
copyfiles = new ArrayList<CopyFile>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue