diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java new file mode 100644 index 000000000..bfff14d9d --- /dev/null +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2014 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.pgm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.lib.CLIRepositoryTestCase; +import org.eclipse.jgit.lib.Repository; +import org.junit.Before; +import org.junit.Test; + +public class RepoTest extends CLIRepositoryTestCase { + private Repository defaultDb; + private Repository notDefaultDb; + private Repository groupADb; + private Repository groupBDb; + + private String rootUri; + private String defaultUri; + private String notDefaultUri; + private String groupAUri; + private String groupBUri; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + defaultDb = createWorkRepository(); + Git git = new Git(defaultDb); + JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "world"); + git.add().addFilepattern("hello.txt").call(); + git.commit().setMessage("Initial commit").call(); + + notDefaultDb = createWorkRepository(); + git = new Git(notDefaultDb); + JGitTestUtil.writeTrashFile(notDefaultDb, "world.txt", "hello"); + git.add().addFilepattern("world.txt").call(); + git.commit().setMessage("Initial commit").call(); + + groupADb = createWorkRepository(); + git = new Git(groupADb); + JGitTestUtil.writeTrashFile(groupADb, "a.txt", "world"); + git.add().addFilepattern("a.txt").call(); + git.commit().setMessage("Initial commit").call(); + + groupBDb = createWorkRepository(); + git = new Git(groupBDb); + JGitTestUtil.writeTrashFile(groupBDb, "b.txt", "world"); + git.add().addFilepattern("b.txt").call(); + git.commit().setMessage("Initial commit").call(); + + resolveRelativeUris(); + } + + @Test + public void testAddRepoManifest() throws Exception { + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append(""); + writeTrashFile("manifest.xml", xmlContent.toString()); + StringBuilder cmd = new StringBuilder("git repo --base-uri=\"") + .append(rootUri) + .append("\" --groups=\"all,-a\" \"") + .append(db.getWorkTree().getAbsolutePath()) + .append("/manifest.xml\""); + execute(cmd.toString()); + + File file = new File(db.getWorkTree(), "foo/hello.txt"); + assertFalse("\"all,-a\" doesn't have foo", file.exists()); + file = new File(db.getWorkTree(), "bar/world.txt"); + assertTrue("\"all,-a\" has bar", file.exists()); + file = new File(db.getWorkTree(), "a/a.txt"); + assertFalse("\"all,-a\" doesn't have a", file.exists()); + file = new File(db.getWorkTree(), "b/b.txt"); + assertTrue("\"all,-a\" has have b", file.exists()); + } + + private void resolveRelativeUris() { + // Find the longest common prefix ends with "/" as rootUri. + defaultUri = defaultDb.getDirectory().toURI().toString(); + notDefaultUri = notDefaultDb.getDirectory().toURI().toString(); + groupAUri = groupADb.getDirectory().toURI().toString(); + groupBUri = groupBDb.getDirectory().toURI().toString(); + int start = 0; + while (start <= defaultUri.length()) { + int newStart = defaultUri.indexOf('/', start + 1); + String prefix = defaultUri.substring(0, newStart); + if (!notDefaultUri.startsWith(prefix) || + !groupAUri.startsWith(prefix) || + !groupBUri.startsWith(prefix)) { + start++; + rootUri = defaultUri.substring(0, start); + defaultUri = defaultUri.substring(start); + notDefaultUri = notDefaultUri.substring(start); + groupAUri = groupAUri.substring(start); + groupBUri = groupBUri.substring(start); + return; + } + start = newStart; + } + } +} diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF index 2b1d34cbd..b052e0467 100644 --- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF @@ -17,6 +17,7 @@ Import-Package: org.apache.commons.compress.archivers;version="[1.3,2.0)", org.eclipse.jgit.diff;version="[3.4.0,3.5.0)", org.eclipse.jgit.dircache;version="[3.4.0,3.5.0)", org.eclipse.jgit.errors;version="[3.4.0,3.5.0)", + org.eclipse.jgit.gitrepo;version="[3.4.0,3.5.0)", org.eclipse.jgit.internal.storage.file;version="[3.4.0,3.5.0)", org.eclipse.jgit.internal.storage.pack;version="[3.4.0,3.5.0)", org.eclipse.jgit.lib;version="[3.4.0,3.5.0)", diff --git a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin index 2ca6009cd..e1b05491b 100644 --- a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin +++ b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin @@ -24,6 +24,7 @@ org.eclipse.jgit.pgm.MergeBase org.eclipse.jgit.pgm.Push org.eclipse.jgit.pgm.ReceivePack org.eclipse.jgit.pgm.Reflog +org.eclipse.jgit.pgm.Repo org.eclipse.jgit.pgm.Reset org.eclipse.jgit.pgm.RevList org.eclipse.jgit.pgm.RevParse diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties index 372f1c1bf..7d5b87fdd 100644 --- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties +++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties @@ -231,6 +231,7 @@ usage_archive=zip up files from the named tree usage_archiveFormat=archive format. Currently supported formats: 'tar', 'zip', 'tgz', 'tbz2', 'txz' usage_archiveOutput=output file to write the archive to usage_archivePrefix=string to prepend to each pathname in the archive +usage_baseUri=the base URI of the repo manifest file. e.g. https://android.googlesource.com/platform/ usage_blameLongRevision=show long revision usage_blameRange=annotate only the given range usage_blameRawTimestamp=show raw timestamp @@ -277,6 +278,7 @@ usage_forceCheckout=when switching branches, proceed even if the index or the wo usage_forceCreateBranchEvenExists=force create branch even exists usage_forceReplacingAnExistingTag=force replacing an existing tag usage_getAndSetOptions=Get and set repository or global options +usage_groups=Restrict manifest projects to ones with specified group(s), use "-" for excluding [default|all|G1,G2,G3|G4,-G5,-G6] usage_hostnameOrIpToListenOn=hostname (or ip) to listen on usage_indexFileFormatToCreate=index file format to create usage_ignoreWhitespace=ignore all whitespace @@ -300,7 +302,9 @@ usage_noRenames=disable rename detection usage_noShowStandardNotes=Disable showing notes from the standard /refs/notes/commits branch usage_onlyMatchAgainstAlreadyTrackedFiles=Only match against already tracked files in the index rather than the working tree usage_outputFile=Output file +usage_parseRepoManifest=Parse a repo manifest file and add submodules usage_path=path +usage_pathToXml=path to the repo manifest XML file usage_performFsckStyleChecksOnReceive=perform fsck style checks on receive usage_portNumberToListenOn=port number to listen on usage_printOnlyBranchesThatContainTheCommit=print only branches that contain the commit diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java new file mode 100644 index 000000000..9b191e679 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2014, 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.pgm; + +import org.eclipse.jgit.gitrepo.RepoCommand; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +@Command(common = true, usage = "usage_parseRepoManifest") +class Repo extends TextBuiltin { + + @Option(name = "--base-uri", aliases = { "-u" }, usage = "usage_baseUri") + private String uri; + + @Option(name = "--groups", aliases = { "-g" }, usage = "usage_groups") + private String groups = "default"; //$NON-NLS-1$ + + @Argument(required = true, usage = "usage_pathToXml") + private String path; + + @Override + protected void run() throws Exception { + new RepoCommand(db) + .setURI(uri) + .setPath(path) + .setGroups(groups) + .call(); + } +} diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index 01e3e7d52..cda3fd181 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -18,6 +18,7 @@ Import-Package: com.googlecode.javaewah;version="[0.7.9,0.8.0)", org.eclipse.jgit.errors;version="[3.4.0,3.5.0)", org.eclipse.jgit.events;version="[3.4.0,3.5.0)", org.eclipse.jgit.fnmatch;version="[3.4.0,3.5.0)", + org.eclipse.jgit.gitrepo;version="[3.4.0,3.5.0)", org.eclipse.jgit.ignore;version="[3.4.0,3.5.0)", org.eclipse.jgit.internal;version="[3.4.0,3.5.0)", org.eclipse.jgit.internal.storage.dfs;version="[3.4.0,3.5.0)", 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 new file mode 100644 index 000000000..27d322079 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2014, 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.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.Repository; +import org.junit.Test; + +public class RepoCommandTest extends RepositoryTestCase { + + private Repository defaultDb; + private Repository notDefaultDb; + private Repository groupADb; + private Repository groupBDb; + + private String rootUri; + private String defaultUri; + private String notDefaultUri; + private String groupAUri; + private String groupBUri; + + public void setUp() throws Exception { + super.setUp(); + + defaultDb = createWorkRepository(); + Git git = new Git(defaultDb); + JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "world"); + git.add().addFilepattern("hello.txt").call(); + git.commit().setMessage("Initial commit").call(); + + notDefaultDb = createWorkRepository(); + git = new Git(notDefaultDb); + JGitTestUtil.writeTrashFile(notDefaultDb, "world.txt", "hello"); + git.add().addFilepattern("world.txt").call(); + git.commit().setMessage("Initial commit").call(); + + groupADb = createWorkRepository(); + git = new Git(groupADb); + JGitTestUtil.writeTrashFile(groupADb, "a.txt", "world"); + git.add().addFilepattern("a.txt").call(); + git.commit().setMessage("Initial commit").call(); + + groupBDb = createWorkRepository(); + git = new Git(groupBDb); + JGitTestUtil.writeTrashFile(groupBDb, "b.txt", "world"); + git.add().addFilepattern("b.txt").call(); + git.commit().setMessage("Initial commit").call(); + + resolveRelativeUris(); + } + + @Test + public void testAddRepoManifest() throws Exception { + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append(""); + writeTrashFile("manifest.xml", xmlContent.toString()); + RepoCommand command = new RepoCommand(db); + command.setPath(db.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri) + .call(); + File hello = new File(db.getWorkTree(), "foo/hello.txt"); + assertTrue("submodule was checked out", hello.exists()); + BufferedReader reader = new BufferedReader(new FileReader(hello)); + String content = reader.readLine(); + reader.close(); + assertEquals("submodule content is as expected.", "world", content); + } + + @Test + public void testRepoManifestGroups() throws Exception { + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append(""); + + // default should have foo, a & b + Repository localDb = createWorkRepository(); + JGitTestUtil.writeTrashFile(localDb, "manifest.xml", xmlContent.toString()); + RepoCommand command = new RepoCommand(localDb); + command.setPath(localDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri) + .call(); + File file = new File(localDb.getWorkTree(), "foo/hello.txt"); + assertTrue("default has foo", file.exists()); + file = new File(localDb.getWorkTree(), "bar/world.txt"); + assertFalse("default doesn't have bar", file.exists()); + file = new File(localDb.getWorkTree(), "a/a.txt"); + assertTrue("default has a", file.exists()); + file = new File(localDb.getWorkTree(), "b/b.txt"); + assertTrue("default has b", file.exists()); + + // all,-a should have bar & b + localDb = createWorkRepository(); + JGitTestUtil.writeTrashFile(localDb, "manifest.xml", xmlContent.toString()); + command = new RepoCommand(localDb); + command.setPath(localDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri) + .setGroups("all,-a") + .call(); + file = new File(localDb.getWorkTree(), "foo/hello.txt"); + assertFalse("\"all,-a\" doesn't have foo", file.exists()); + file = new File(localDb.getWorkTree(), "bar/world.txt"); + assertTrue("\"all,-a\" has bar", file.exists()); + file = new File(localDb.getWorkTree(), "a/a.txt"); + assertFalse("\"all,-a\" doesn't have a", file.exists()); + file = new File(localDb.getWorkTree(), "b/b.txt"); + assertTrue("\"all,-a\" has have b", file.exists()); + } + + @Test + public void testRepoManifestCopyfile() throws Exception { + Repository localDb = createWorkRepository(); + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append(""); + JGitTestUtil.writeTrashFile(localDb, "manifest.xml", xmlContent.toString()); + RepoCommand command = new RepoCommand(localDb); + command.setPath(localDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri) + .call(); + // The original file should exist + File hello = new File(localDb.getWorkTree(), "foo/hello.txt"); + assertTrue("The original file exists", hello.exists()); + BufferedReader reader = new BufferedReader(new FileReader(hello)); + String content = reader.readLine(); + reader.close(); + assertEquals("The original file has expected content", "world", content); + // The dest file should also exist + hello = new File(localDb.getWorkTree(), "Hello"); + assertTrue("The destination file exists", hello.exists()); + reader = new BufferedReader(new FileReader(hello)); + content = reader.readLine(); + reader.close(); + assertEquals("The destination file has expected content", "world", content); + } + + private void resolveRelativeUris() { + // Find the longest common prefix ends with "/" as rootUri. + defaultUri = defaultDb.getDirectory().toURI().toString(); + notDefaultUri = notDefaultDb.getDirectory().toURI().toString(); + groupAUri = groupADb.getDirectory().toURI().toString(); + groupBUri = groupBDb.getDirectory().toURI().toString(); + int start = 0; + while (start <= defaultUri.length()) { + int newStart = defaultUri.indexOf('/', start + 1); + String prefix = defaultUri.substring(0, newStart); + if (!notDefaultUri.startsWith(prefix) || + !groupAUri.startsWith(prefix) || + !groupBUri.startsWith(prefix)) { + start++; + rootUri = defaultUri.substring(0, start); + defaultUri = defaultUri.substring(start); + notDefaultUri = notDefaultUri.substring(start); + groupAUri = groupAUri.substring(start); + groupBUri = groupBUri.substring(start); + return; + } + start = newStart; + } + } +} diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index e1b91380a..1fa0ad9cf 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -45,6 +45,9 @@ Export-Package: org.eclipse.jgit.api;version="3.4.0"; org.eclipse.jgit.events;version="3.4.0"; uses:="org.eclipse.jgit.lib", org.eclipse.jgit.fnmatch;version="3.4.0", + org.eclipse.jgit.gitrepo;version="3.4.0"; + uses:="org.eclipse.jgit.api, + org.eclipse.jgit.lib", org.eclipse.jgit.ignore;version="3.4.0", org.eclipse.jgit.internal;version="3.4.0";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test", org.eclipse.jgit.internal.storage.dfs;version="3.4.0";x-friends:="org.eclipse.jgit.test", diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties new file mode 100644 index 000000000..29aa51ccd --- /dev/null +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties @@ -0,0 +1,5 @@ +copyFileFailed=Error occurred during execution of copyfile rule. +errorNoDefault=Error: no default remote in file {0}. +errorParsingManifestFile=Error occurred during parsing manifest file {0}. +invalidManifest=Invalid manifest. +repoCommitMessage=Added repo manifest. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java new file mode 100644 index 000000000..475fbacaf --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2014, 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.FileOutputStream; +import java.io.IOException; +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.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jgit.api.AddCommand; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.GitCommand; +import org.eclipse.jgit.api.SubmoduleAddCommand; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.gitrepo.internal.RepoText; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; + +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. + * + * This will parse a repo XML manifest, convert it into .gitmodules file and the + * repository config file. + * + * @see git-repo project page + * @since 3.4 + */ +public class RepoCommand extends GitCommand { + + private String path; + private String uri; + private String groups; + + private Git git; + private ProgressMonitor monitor; + + private static class CopyFile { + final String src; + final String dest; + final String relativeDest; + + CopyFile(Repository repo, String path, String src, String dest) { + this.src = repo.getWorkTree() + "/" + path + "/" + src; //$NON-NLS-1$ //$NON-NLS-2$ + this.relativeDest = dest; + this.dest = repo.getWorkTree() + "/" + dest; //$NON-NLS-1$ + } + + void copy() throws IOException { + FileInputStream input = new FileInputStream(src); + try { + FileOutputStream output = new FileOutputStream(dest); + try { + FileChannel channel = input.getChannel(); + output.getChannel().transferFrom(channel, 0, channel.size()); + } finally { + output.close(); + } + } finally { + input.close(); + } + } + } + + private static class Project { + final String name; + final String path; + final Set groups; + final List copyfiles; + + Project(String name, String path, String groups) { + this.name = name; + this.path = path; + 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); + } + } + + private static class XmlManifest extends DefaultHandler { + private final RepoCommand command; + private final String filename; + private final String baseUrl; + private final Map remotes; + private final List projects; + private final Set plusGroups; + private final Set minusGroups; + private String defaultRemote; + private Project currentProject; + + XmlManifest(RepoCommand command, String filename, String baseUrl, String groups) { + this.command = command; + this.filename = filename; + this.baseUrl = baseUrl; + 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() throws IOException { + final XMLReader xr; + try { + xr = XMLReaderFactory.createXMLReader(); + } catch (SAXException e) { + throw new IOException(JGitText.get().noXMLParserAvailable); + } + xr.setContentHandler(this); + final FileInputStream in = new FileInputStream(filename); + try { + xr.parse(new InputSource(in)); + } catch (SAXException e) { + IOException error = new IOException(MessageFormat.format( + RepoText.get().errorParsingManifestFile, filename)); + error.initCause(e); + throw error; + } finally { + in.close(); + } + } + + @Override + public void startElement( + String uri, + String localName, + String qName, + Attributes attributes) throws SAXException { + if ("project".equals(qName)) { //$NON-NLS-1$ + currentProject = new Project( //$NON-NLS-1$ + attributes.getValue("name"), //$NON-NLS-1$ + attributes.getValue("path"), //$NON-NLS-1$ + attributes.getValue("groups")); //$NON-NLS-1$ + } else if ("remote".equals(qName)) { //$NON-NLS-1$ + remotes.put(attributes.getValue("name"), //$NON-NLS-1$ + attributes.getValue("fetch")); //$NON-NLS-1$ + } else if ("default".equals(qName)) { //$NON-NLS-1$ + defaultRemote = attributes.getValue("remote"); //$NON-NLS-1$ + } 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$ + } + } + + @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 { + if (defaultRemote == null) { + throw new SAXException(MessageFormat.format( + RepoText.get().errorNoDefault, filename)); + } + final String remoteUrl; + try { + URI uri = new URI(String.format("%s/%s/", baseUrl, remotes.get(defaultRemote))); //$NON-NLS-1$ + remoteUrl = uri.normalize().toString(); + } catch (URISyntaxException e) { + throw new SAXException(e); + } + for (Project proj : projects) { + if (inGroups(proj)) { + String url = remoteUrl + proj.name; + command.addSubmodule(url, proj.path); + for (CopyFile copyfile : proj.copyfiles) { + try { + copyfile.copy(); + } catch (IOException e) { + throw new SAXException( + RepoText.get().copyFileFailed, e); + } + AddCommand add = command.git + .add() + .addFilepattern(copyfile.relativeDest); + try { + add.call(); + } catch (GitAPIException e) { + throw new SAXException(e); + } + } + } + } + } + + 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; + } + } + + private static class ManifestErrorException extends GitAPIException { + ManifestErrorException(Throwable cause) { + super(RepoText.get().invalidManifest, cause); + } + } + + /** + * @param repo + */ + public RepoCommand(final Repository repo) { + super(repo); + } + + /** + * Set path to the manifest XML file + * + * @param path + * (with / as separator) + * @return this command + */ + public RepoCommand setPath(final String path) { + this.path = path; + return this; + } + + /** + * Set base URI of the pathes inside the XML + * + * @param uri + * @return this command + */ + public RepoCommand setURI(final String uri) { + this.uri = uri; + return this; + } + + /** + * Set groups to sync + * + * @param groups groups separated by comma, examples: default|all|G1,-G2,-G3 + * @return this command + */ + public RepoCommand setGroups(final String groups) { + this.groups = groups; + return this; + } + + /** + * The progress monitor associated with the clone operation. By default, + * this is set to NullProgressMonitor + * + * @see org.eclipse.jgit.lib.NullProgressMonitor + * @param monitor + * @return this command + */ + public RepoCommand setProgressMonitor(final ProgressMonitor monitor) { + this.monitor = monitor; + return this; + } + + @Override + public RevCommit call() throws GitAPIException { + checkCallable(); + if (path == null || path.length() == 0) + throw new IllegalArgumentException(JGitText.get().pathNotConfigured); + if (uri == null || uri.length() == 0) + throw new IllegalArgumentException(JGitText.get().uriNotConfigured); + + git = new Git(repo); + XmlManifest manifest = new XmlManifest(this, path, uri, groups); + try { + manifest.read(); + } catch (IOException e) { + throw new ManifestErrorException(e); + } + + return git + .commit() + .setMessage(RepoText.get().repoCommitMessage) + .call(); + } + + private void addSubmodule(String url, String name) throws SAXException { + SubmoduleAddCommand add = git + .submoduleAdd() + .setPath(name) + .setURI(url); + if (monitor != null) + add.setProgressMonitor(monitor); + try { + add.call(); + } catch (GitAPIException e) { + throw new SAXException(e); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java new file mode 100644 index 000000000..1313fff0d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2014, 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.internal; + +import org.eclipse.jgit.nls.NLS; +import org.eclipse.jgit.nls.TranslationBundle; + +/** + * Translation bundle for repo command + */ +public class RepoText extends TranslationBundle { + + /** + * @return an instance of this translation bundle + */ + public static RepoText get() { + return NLS.getBundleFor(RepoText.class); + } + + // @formatter:off + /***/ public String copyFileFailed; + /***/ public String errorNoDefault; + /***/ public String errorParsingManifestFile; + /***/ public String invalidManifest; + /***/ public String repoCommitMessage; +}