From 92c6f2f97bb478ac0d4731cacafaef52ac0d08d7 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 19 Dec 2011 09:38:11 -0800 Subject: [PATCH] Add comand support for git-submodule Adds the following commands: - Add - Init - Status - Sync - Update This also updates AddCommand so that file patterns added that are submodules can be staged in the index. Change-Id: Ie5112aa26430e5a2a3acd65a7b0e1d76067dc545 Signed-off-by: Kevin Sawicki Signed-off-by: Chris Aniszczyk --- org.eclipse.jgit.test/META-INF/MANIFEST.MF | 1 + .../jgit/submodule/SubmoduleAddTest.java | 172 +++++++ .../jgit/submodule/SubmoduleInitTest.java | 126 +++++ .../jgit/submodule/SubmoduleStatusTest.java | 359 +++++++++++++++ .../jgit/submodule/SubmoduleSyncTest.java | 140 ++++++ .../jgit/submodule/SubmoduleUpdateTest.java | 187 ++++++++ .../jgit/submodule/SubmoduleWalkTest.java | 177 +++++++ org.eclipse.jgit/META-INF/MANIFEST.MF | 1 + .../org/eclipse/jgit/JGitText.properties | 3 + .../src/org/eclipse/jgit/JGitText.java | 3 + .../src/org/eclipse/jgit/api/AddCommand.java | 43 +- .../eclipse/jgit/api/SubmoduleAddCommand.java | 211 +++++++++ .../jgit/api/SubmoduleInitCommand.java | 130 ++++++ .../jgit/api/SubmoduleStatusCommand.java | 148 ++++++ .../jgit/api/SubmoduleSyncCommand.java | 157 +++++++ .../jgit/api/SubmoduleUpdateCommand.java | 190 ++++++++ .../org/eclipse/jgit/lib/ConfigConstants.java | 9 + .../src/org/eclipse/jgit/lib/Constants.java | 3 + .../jgit/submodule/SubmoduleStatus.java | 115 +++++ .../jgit/submodule/SubmoduleStatusType.java | 64 +++ .../eclipse/jgit/submodule/SubmoduleWalk.java | 435 ++++++++++++++++++ 21 files changed, 2661 insertions(+), 13 deletions(-) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleSyncTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleStatus.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleStatusType.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index e98a5aa7f..3b85c9e8e 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -33,6 +33,7 @@ Import-Package: org.eclipse.jgit;version="[1.3.0,1.4.0)", org.eclipse.jgit.revwalk.filter;version="[1.3.0,1.4.0)", org.eclipse.jgit.storage.file;version="[1.3.0,1.4.0)", org.eclipse.jgit.storage.pack;version="[1.3.0,1.4.0)", + org.eclipse.jgit.submodule;version="[1.3.0,1.4.0)", org.eclipse.jgit.transport;version="[1.3.0,1.4.0)", org.eclipse.jgit.treewalk;version="[1.3.0,1.4.0)", org.eclipse.jgit.treewalk.filter;version="[1.3.0,1.4.0)", diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java new file mode 100644 index 000000000..d14d11b62 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2011, GitHub 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.submodule; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.text.MessageFormat; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.Status; +import org.eclipse.jgit.api.SubmoduleAddCommand; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheEditor; +import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.Test; + +/** + * Unit tests of {@link org.eclipse.jgit.api.SubmoduleAddCommand} + */ +public class SubmoduleAddTest extends RepositoryTestCase { + + @Test + public void commandWithNullPath() { + try { + new SubmoduleAddCommand(db).setURI("uri").call(); + fail("Exception not thrown"); + } catch (IllegalArgumentException e) { + assertEquals(JGitText.get().pathNotConfigured, e.getMessage()); + } + } + + @Test + public void commandWithEmptyPath() { + try { + new SubmoduleAddCommand(db).setPath("").setURI("uri").call(); + fail("Exception not thrown"); + } catch (IllegalArgumentException e) { + assertEquals(JGitText.get().pathNotConfigured, e.getMessage()); + } + } + + @Test + public void commandWithNullUri() { + try { + new SubmoduleAddCommand(db).setPath("sub").call(); + fail("Exception not thrown"); + } catch (IllegalArgumentException e) { + assertEquals(JGitText.get().uriNotConfigured, e.getMessage()); + } + } + + @Test + public void commandWithEmptyUri() { + try { + new SubmoduleAddCommand(db).setPath("sub").setURI("").call(); + fail("Exception not thrown"); + } catch (IllegalArgumentException e) { + assertEquals(JGitText.get().uriNotConfigured, e.getMessage()); + } + } + + @Test + public void addSubmodule() throws Exception { + Git git = new Git(db); + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + RevCommit commit = git.commit().setMessage("create file").call(); + + SubmoduleAddCommand command = new SubmoduleAddCommand(db); + String path = "sub"; + command.setPath(path); + String uri = db.getDirectory().toURI().toString(); + command.setURI(uri); + Repository repo = command.call(); + assertNotNull(repo); + + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertEquals(path, generator.getPath()); + assertEquals(commit, generator.getObjectId()); + assertEquals(uri, generator.getModulesUrl()); + assertEquals(path, generator.getModulesPath()); + assertEquals(uri, generator.getConfigUrl()); + assertNotNull(generator.getRepository()); + assertEquals(commit, repo.resolve(Constants.HEAD)); + + Status status = Git.wrap(db).status().call(); + assertTrue(status.getAdded().contains(Constants.DOT_GIT_MODULES)); + assertTrue(status.getAdded().contains(path)); + } + + @Test + public void addExistentSubmodule() throws Exception { + final ObjectId id = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + SubmoduleAddCommand command = new SubmoduleAddCommand(db); + command.setPath(path); + command.setURI("git://server/repo.git"); + try { + command.call(); + fail("Exception not thrown"); + } catch (JGitInternalException e) { + assertEquals( + MessageFormat.format(JGitText.get().submoduleExists, path), + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java new file mode 100644 index 000000000..727368f34 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2011, GitHub 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.submodule; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; + +import org.eclipse.jgit.api.SubmoduleInitCommand; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheEditor; +import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.junit.Test; + +/** + * Unit tests of {@link SubmoduleInitCommand} + */ +public class SubmoduleInitTest extends RepositoryTestCase { + + @Test + public void repositoryWithNoSubmodules() { + SubmoduleInitCommand command = new SubmoduleInitCommand(db); + Collection modules = command.call(); + assertNotNull(modules); + assertTrue(modules.isEmpty()); + } + + @Test + public void repositoryWithUninitializedModule() throws IOException, + ConfigInvalidException { + final ObjectId id = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertNull(generator.getConfigUrl()); + assertNull(generator.getConfigUpdate()); + + FileBasedConfig modulesConfig = new FileBasedConfig(new File( + db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS()); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + String url = "git://server/repo.git"; + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, url); + String update = "rebase"; + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_UPDATE, update); + modulesConfig.save(); + + SubmoduleInitCommand command = new SubmoduleInitCommand(db); + Collection modules = command.call(); + assertNotNull(modules); + assertEquals(1, modules.size()); + assertEquals(path, modules.iterator().next()); + + generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertEquals(url, generator.getConfigUrl()); + assertEquals(update, generator.getConfigUpdate()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java new file mode 100644 index 000000000..dc79d8425 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2011, GitHub 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.submodule; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.SubmoduleStatusCommand; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheEditor; +import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.junit.Test; + +/** + * Unit tests of {@link SubmoduleStatusCommand} + */ +public class SubmoduleStatusTest extends RepositoryTestCase { + + @Test + public void repositoryWithNoSubmodules() { + SubmoduleStatusCommand command = new SubmoduleStatusCommand(db); + Map statuses = command.call(); + assertNotNull(statuses); + assertTrue(statuses.isEmpty()); + } + + @Test + public void repositoryWithMissingSubmodule() throws IOException { + final ObjectId id = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + SubmoduleStatusCommand command = new SubmoduleStatusCommand(db); + Map statuses = command.call(); + assertNotNull(statuses); + assertEquals(1, statuses.size()); + Entry module = statuses.entrySet().iterator() + .next(); + assertNotNull(module); + assertEquals(path, module.getKey()); + SubmoduleStatus status = module.getValue(); + assertNotNull(status); + assertEquals(path, status.getPath()); + assertEquals(id, status.getIndexId()); + assertEquals(SubmoduleStatusType.MISSING, status.getType()); + } + + @Test + public void repositoryWithUninitializedSubmodule() throws IOException { + final ObjectId id = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + FileBasedConfig modulesConfig = new FileBasedConfig(new File( + db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS()); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, "git://server/repo.git"); + modulesConfig.save(); + + SubmoduleStatusCommand command = new SubmoduleStatusCommand(db); + Map statuses = command.call(); + assertNotNull(statuses); + assertEquals(1, statuses.size()); + Entry module = statuses.entrySet().iterator() + .next(); + assertNotNull(module); + assertEquals(path, module.getKey()); + SubmoduleStatus status = module.getValue(); + assertNotNull(status); + assertEquals(path, status.getPath()); + assertEquals(id, status.getIndexId()); + assertEquals(SubmoduleStatusType.UNINITIALIZED, status.getType()); + } + + @Test + public void repositoryWithNoHeadInSubmodule() throws IOException { + final ObjectId id = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + String url = "git://server/repo.git"; + StoredConfig config = db.getConfig(); + config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, url); + config.save(); + + FileBasedConfig modulesConfig = new FileBasedConfig(new File( + db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS()); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, url); + modulesConfig.save(); + + Repository subRepo = Git.init().setBare(false) + .setDirectory(new File(db.getWorkTree(), path)).call() + .getRepository(); + assertNotNull(subRepo); + + SubmoduleStatusCommand command = new SubmoduleStatusCommand(db); + Map statuses = command.call(); + assertNotNull(statuses); + assertEquals(1, statuses.size()); + Entry module = statuses.entrySet().iterator() + .next(); + assertNotNull(module); + assertEquals(path, module.getKey()); + SubmoduleStatus status = module.getValue(); + assertNotNull(status); + assertEquals(path, status.getPath()); + assertEquals(id, status.getIndexId()); + assertEquals(SubmoduleStatusType.UNINITIALIZED, status.getType()); + } + + @Test + public void repositoryWithNoSubmoduleRepository() throws IOException { + final ObjectId id = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + String url = "git://server/repo.git"; + StoredConfig config = db.getConfig(); + config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, url); + config.save(); + + FileBasedConfig modulesConfig = new FileBasedConfig(new File( + db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS()); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, url); + modulesConfig.save(); + + SubmoduleStatusCommand command = new SubmoduleStatusCommand(db); + Map statuses = command.call(); + assertNotNull(statuses); + assertEquals(1, statuses.size()); + Entry module = statuses.entrySet().iterator() + .next(); + assertNotNull(module); + assertEquals(path, module.getKey()); + SubmoduleStatus status = module.getValue(); + assertNotNull(status); + assertEquals(path, status.getPath()); + assertEquals(id, status.getIndexId()); + assertEquals(SubmoduleStatusType.UNINITIALIZED, status.getType()); + } + + @Test + public void repositoryWithInitializedSubmodule() throws IOException { + final ObjectId id = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + String url = "git://server/repo.git"; + StoredConfig config = db.getConfig(); + config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, url); + config.save(); + + FileBasedConfig modulesConfig = new FileBasedConfig(new File( + db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS()); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, url); + modulesConfig.save(); + + Repository subRepo = Git.init().setBare(false) + .setDirectory(new File(db.getWorkTree(), path)).call() + .getRepository(); + assertNotNull(subRepo); + + RefUpdate update = subRepo.updateRef(Constants.HEAD, true); + update.setNewObjectId(id); + update.forceUpdate(); + + SubmoduleStatusCommand command = new SubmoduleStatusCommand(db); + Map statuses = command.call(); + assertNotNull(statuses); + assertEquals(1, statuses.size()); + Entry module = statuses.entrySet().iterator() + .next(); + assertNotNull(module); + assertEquals(path, module.getKey()); + SubmoduleStatus status = module.getValue(); + assertNotNull(status); + assertEquals(path, status.getPath()); + assertEquals(id, status.getIndexId()); + assertEquals(SubmoduleStatusType.INITIALIZED, status.getType()); + } + + @Test + public void repositoryWithDifferentRevCheckedOutSubmodule() + throws IOException { + final ObjectId id = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + String url = "git://server/repo.git"; + StoredConfig config = db.getConfig(); + config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, url); + config.save(); + + FileBasedConfig modulesConfig = new FileBasedConfig(new File( + db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS()); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, url); + modulesConfig.save(); + + Repository subRepo = Git.init().setBare(false) + .setDirectory(new File(db.getWorkTree(), path)).call() + .getRepository(); + assertNotNull(subRepo); + + RefUpdate update = subRepo.updateRef(Constants.HEAD, true); + update.setNewObjectId(ObjectId + .fromString("aaaa0000aaaa0000aaaa0000aaaa0000aaaa0000")); + update.forceUpdate(); + + SubmoduleStatusCommand command = new SubmoduleStatusCommand(db); + Map statuses = command.call(); + assertNotNull(statuses); + assertEquals(1, statuses.size()); + Entry module = statuses.entrySet().iterator() + .next(); + assertNotNull(module); + assertEquals(path, module.getKey()); + SubmoduleStatus status = module.getValue(); + assertNotNull(status); + assertEquals(path, status.getPath()); + assertEquals(id, status.getIndexId()); + assertEquals(update.getNewObjectId(), status.getHeadId()); + assertEquals(SubmoduleStatusType.REV_CHECKED_OUT, status.getType()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleSyncTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleSyncTest.java new file mode 100644 index 000000000..f61aad2f3 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleSyncTest.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2011, GitHub 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.submodule; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.SubmoduleSyncCommand; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheEditor; +import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.junit.Test; + +/** + * Unit tests of {@link SubmoduleSyncCommand} + */ +public class SubmoduleSyncTest extends RepositoryTestCase { + + @Test + public void repositoryWithNoSubmodules() { + SubmoduleSyncCommand command = new SubmoduleSyncCommand(db); + Map modules = command.call(); + assertNotNull(modules); + assertTrue(modules.isEmpty()); + } + + @Test + public void repositoryWithSubmodule() throws Exception { + writeTrashFile("file.txt", "content"); + Git git = Git.wrap(db); + git.add().addFilepattern("file.txt").call(); + git.commit().setMessage("create file").call(); + + final ObjectId id = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + FileBasedConfig modulesConfig = new FileBasedConfig(new File( + db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS()); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + String url = "git://server/repo.git"; + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, url); + modulesConfig.save(); + + Repository subRepo = Git.cloneRepository() + .setURI(db.getDirectory().toURI().toString()) + .setDirectory(new File(db.getWorkTree(), path)).call() + .getRepository(); + assertNotNull(subRepo); + + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertNull(generator.getConfigUrl()); + assertEquals(url, generator.getModulesUrl()); + + SubmoduleSyncCommand command = new SubmoduleSyncCommand(db); + Map synced = command.call(); + assertNotNull(synced); + assertEquals(1, synced.size()); + Entry module = synced.entrySet().iterator().next(); + assertEquals(path, module.getKey()); + assertEquals(url, module.getValue()); + + generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + assertEquals(url, generator.getConfigUrl()); + StoredConfig submoduleConfig = generator.getRepository().getConfig(); + assertEquals(url, submoduleConfig.getString( + ConfigConstants.CONFIG_REMOTE_SECTION, + Constants.DEFAULT_REMOTE_NAME, ConfigConstants.CONFIG_KEY_URL)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java new file mode 100644 index 000000000..9bb4a63aa --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2011, GitHub 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.submodule; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.SubmoduleUpdateCommand; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheEditor; +import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.junit.Test; + +/** + * Unit tests of {@link SubmoduleUpdateCommand} + */ +public class SubmoduleUpdateTest extends RepositoryTestCase { + + @Test + public void repositoryWithNoSubmodules() { + SubmoduleUpdateCommand command = new SubmoduleUpdateCommand(db); + Collection modules = command.call(); + assertNotNull(modules); + assertTrue(modules.isEmpty()); + } + + @Test + public void repositoryWithSubmodule() throws Exception { + writeTrashFile("file.txt", "content"); + Git git = Git.wrap(db); + git.add().addFilepattern("file.txt").call(); + final RevCommit commit = git.commit().setMessage("create file").call(); + + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(commit); + } + }); + editor.commit(); + + StoredConfig config = db.getConfig(); + config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, db.getDirectory().toURI() + .toString()); + config.save(); + + FileBasedConfig modulesConfig = new FileBasedConfig(new File( + db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS()); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + modulesConfig.save(); + + SubmoduleUpdateCommand command = new SubmoduleUpdateCommand(db); + Collection updated = command.call(); + assertNotNull(updated); + assertEquals(1, updated.size()); + assertEquals(path, updated.iterator().next()); + + SubmoduleWalk generator = SubmoduleWalk.forIndex(db); + assertTrue(generator.next()); + Repository subRepo = generator.getRepository(); + assertNotNull(subRepo); + assertEquals(commit, subRepo.resolve(Constants.HEAD)); + } + + @Test + public void repositoryWithUnconfiguredSubmodule() throws IOException { + final ObjectId id = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + FileBasedConfig modulesConfig = new FileBasedConfig(new File( + db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS()); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + String url = "git://server/repo.git"; + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, url); + String update = "rebase"; + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_UPDATE, update); + modulesConfig.save(); + + SubmoduleUpdateCommand command = new SubmoduleUpdateCommand(db); + Collection updated = command.call(); + assertNotNull(updated); + assertTrue(updated.isEmpty()); + } + + @Test + public void repositoryWithInitializedSubmodule() throws IOException { + final ObjectId id = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + Repository subRepo = Git.init().setBare(false) + .setDirectory(new File(db.getWorkTree(), path)).call() + .getRepository(); + assertNotNull(subRepo); + + SubmoduleUpdateCommand command = new SubmoduleUpdateCommand(db); + Collection updated = command.call(); + assertNotNull(updated); + assertTrue(updated.isEmpty()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java new file mode 100644 index 000000000..c4bf33b8b --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2011, GitHub 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.submodule; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheEditor; +import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.junit.Test; + +/** + * Unit tests of {@link SubmoduleWalk} + */ +public class SubmoduleWalkTest extends RepositoryTestCase { + + @Test + public void repositoryWithNoSubmodules() throws IOException { + SubmoduleWalk gen = SubmoduleWalk.forIndex(db); + assertFalse(gen.next()); + assertNull(gen.getPath()); + assertEquals(ObjectId.zeroId(), gen.getObjectId()); + } + + @Test + public void repositoryWithRootLevelSubmodule() throws IOException, + ConfigInvalidException { + final ObjectId id = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + SubmoduleWalk gen = SubmoduleWalk.forIndex(db); + assertTrue(gen.next()); + assertEquals(path, gen.getPath()); + assertEquals(id, gen.getObjectId()); + assertEquals(new File(db.getWorkTree(), path), gen.getDirectory()); + assertEquals(new File(db.getWorkTree(), path + File.separatorChar + + Constants.DOT_GIT), gen.getGitDirectory()); + assertNull(gen.getConfigUpdate()); + assertNull(gen.getConfigUrl()); + assertNull(gen.getModulesPath()); + assertNull(gen.getModulesUpdate()); + assertNull(gen.getModulesUrl()); + assertNull(gen.getRepository()); + assertFalse(gen.next()); + } + + @Test + public void repositoryWithNestedSubmodule() throws IOException, + ConfigInvalidException { + final ObjectId id = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path = "sub/dir/final"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id); + } + }); + editor.commit(); + + SubmoduleWalk gen = SubmoduleWalk.forIndex(db); + assertTrue(gen.next()); + assertEquals(path, gen.getPath()); + assertEquals(id, gen.getObjectId()); + assertEquals(new File(db.getWorkTree(), path), gen.getDirectory()); + assertEquals(new File(db.getWorkTree(), path + File.separatorChar + + Constants.DOT_GIT), gen.getGitDirectory()); + assertNull(gen.getConfigUpdate()); + assertNull(gen.getConfigUrl()); + assertNull(gen.getModulesPath()); + assertNull(gen.getModulesUpdate()); + assertNull(gen.getModulesUrl()); + assertNull(gen.getRepository()); + assertFalse(gen.next()); + } + + @Test + public void generatorFilteredToOneOfTwoSubmodules() throws IOException { + final ObjectId id1 = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); + final String path1 = "sub1"; + final ObjectId id2 = ObjectId + .fromString("abcd1234abcd1234abcd1234abcd1234abcd1235"); + final String path2 = "sub2"; + DirCache cache = db.lockDirCache(); + DirCacheEditor editor = cache.editor(); + editor.add(new PathEdit(path1) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id1); + } + }); + editor.add(new PathEdit(path2) { + + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(id2); + } + }); + editor.commit(); + + SubmoduleWalk gen = SubmoduleWalk.forIndex(db); + gen.setFilter(PathFilter.create(path1)); + assertTrue(gen.next()); + assertEquals(path1, gen.getPath()); + assertEquals(id1, gen.getObjectId()); + assertFalse(gen.next()); + } +} diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index f867cc9b6..8d5574970 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -26,6 +26,7 @@ Export-Package: org.eclipse.jgit;version="1.3.0", org.eclipse.jgit.storage.dfs;version="1.3.0", org.eclipse.jgit.storage.file;version="1.3.0", org.eclipse.jgit.storage.pack;version="1.3.0", + org.eclipse.jgit.submodule;version="1.3.0", org.eclipse.jgit.transport;version="1.3.0", org.eclipse.jgit.transport.resolver;version="1.3.0", org.eclipse.jgit.treewalk;version="1.3.0", diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties index 403d5fce7..aa0eaa77d 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties @@ -346,6 +346,7 @@ packfileCorruptionDetected=Packfile corruption detected: {0} packfileIsTruncated=Packfile is truncated. packingCancelledDuringObjectsWriting=Packing cancelled during objects writing packWriterStatistics=Total {0,number,#0} (delta {1,number,#0}), reused {2,number,#0} (delta {3,number,#0}) +pathNotConfigured=Submodule path is not configured pathIsNotInWorkingDir=Path is not in working dir peeledLineBeforeRef=Peeled line before ref. peerDidNotSupplyACompleteObjectGraph=peer did not supply a complete object graph @@ -421,6 +422,7 @@ sourceRefNotSpecifiedForRefspec=Source ref not specified for refspec: {0} staleRevFlagsOn=Stale RevFlags on {0} startingReadStageWithoutWrittenRequestDataPendingIsNotSupported=Starting read stage without written request data pending is not supported statelessRPCRequiresOptionToBeEnabled=stateless RPC requires {0} to be enabled +submoduleExists=Submodule ''{0}'' already exists in the index submodulesNotSupported=Submodules are not supported symlinkCannotBeWrittenAsTheLinkTarget=Symlink "{0}" cannot be written as the link target cannot be read from within Java. systemConfigFileInvalid=Systen wide config file {0} is invalid {1} @@ -480,6 +482,7 @@ unsupportedPackIndexVersion=Unsupported pack index version {0} unsupportedPackVersion=Unsupported pack version {0}. updatingReferences=Updating references updatingRefFailed=Updating the ref {0} to {1} failed. ReturnCode from RefUpdate.update() was {2} +uriNotConfigured=Submodule URI not configured uriNotFound={0} not found userConfigFileInvalid=User config file {0} invalid {1} walkFailure=Walk failure. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java index ced568171..e04c7edb4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java @@ -407,6 +407,7 @@ public class JGitText extends TranslationBundle { /***/ public String packingCancelledDuringObjectsWriting; /***/ public String packWriterStatistics; /***/ public String pathIsNotInWorkingDir; + /***/ public String pathNotConfigured; /***/ public String peeledLineBeforeRef; /***/ public String peerDidNotSupplyACompleteObjectGraph; /***/ public String prefixRemote; @@ -481,6 +482,7 @@ public class JGitText extends TranslationBundle { /***/ public String staleRevFlagsOn; /***/ public String startingReadStageWithoutWrittenRequestDataPendingIsNotSupported; /***/ public String statelessRPCRequiresOptionToBeEnabled; + /***/ public String submoduleExists; /***/ public String submodulesNotSupported; /***/ public String symlinkCannotBeWrittenAsTheLinkTarget; /***/ public String systemConfigFileInvalid; @@ -540,6 +542,7 @@ public class JGitText extends TranslationBundle { /***/ public String unsupportedPackVersion; /***/ public String updatingReferences; /***/ public String updatingRefFailed; + /***/ public String uriNotConfigured; /***/ public String uriNotFound; /***/ public String userConfigFileInvalid; /***/ public String walkFailure; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java index fcc58d179..7e670304d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java @@ -43,6 +43,7 @@ */ package org.eclipse.jgit.api; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Collection; @@ -57,6 +58,8 @@ import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.treewalk.FileTreeIterator; @@ -168,20 +171,34 @@ public class AddCommand extends GitCommand { DirCacheEntry entry = new DirCacheEntry(path); if (c == null || c.getDirCacheEntry() == null || !c.getDirCacheEntry().isAssumeValid()) { - entry.setLength(sz); - entry.setLastModified(f.getEntryLastModified()); - entry.setFileMode(f.getEntryFileMode()); - - InputStream in = f.openEntryStream(); - try { - entry.setObjectId(inserter.insert( - Constants.OBJ_BLOB, sz, in)); - } finally { - in.close(); + FileMode mode = f.getEntryFileMode(); + entry.setFileMode(mode); + + if (FileMode.GITLINK != mode) { + entry.setLength(sz); + entry.setLastModified(f + .getEntryLastModified()); + InputStream in = f.openEntryStream(); + try { + entry.setObjectId(inserter.insert( + Constants.OBJ_BLOB, sz, in)); + } finally { + in.close(); + } + builder.add(entry); + lastAddedFile = path; + } else { + Repository subRepo = Git.open( + new File(repo.getWorkTree(), path)) + .getRepository(); + ObjectId subRepoHead = subRepo + .resolve(Constants.HEAD); + if (subRepoHead != null) { + entry.setObjectId(subRepoHead); + builder.add(entry); + lastAddedFile = path; + } } - - builder.add(entry); - lastAddedFile = path; } else { builder.add(c.getDirCacheEntry()); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java new file mode 100644 index 000000000..0b5200481 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2011, GitHub 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.api; + +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.api.errors.NoFilepatternException; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.submodule.SubmoduleWalk; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.eclipse.jgit.treewalk.filter.TreeFilter; + +/** + * A class used to execute a submodule add command. + * + * This will clone the configured submodule, register the submodule in the + * .gitmodules file and the repository config file, and also add the submodule + * and .gitmodules file to the index. + * + * @see Git documentation about submodules + */ +public class SubmoduleAddCommand extends GitCommand { + + private String path; + + private String uri; + + private ProgressMonitor monitor; + + private CredentialsProvider credentialsProvider; + + /** + * @param repo + */ + public SubmoduleAddCommand(final Repository repo) { + super(repo); + } + + /** + * Set repository-relative path of submodule + * + * @param path + * @return this command + */ + public SubmoduleAddCommand setPath(final String path) { + this.path = path; + return this; + } + + /** + * Set URI to clone submodule from + * + * @param uri + * @return this command + */ + public SubmoduleAddCommand setURI(final String uri) { + this.uri = uri; + return this; + } + + /** + * The progress monitor associated with the clone operation. By default, + * this is set to NullProgressMonitor + * + * @see NullProgressMonitor + * @param monitor + * @return this command + */ + public SubmoduleAddCommand setProgressMonitor(final ProgressMonitor monitor) { + this.monitor = monitor; + return this; + } + + /** + * @param credentialsProvider + * the {@link CredentialsProvider} to use + * @return this command + */ + public SubmoduleAddCommand setCredentialsProvider( + final CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + return this; + } + + /** + * Is the configured already a submodule in the index? + * + * @return true if submodule exists in index, false otherwise + * @throws IOException + */ + protected boolean submoduleExists() throws IOException { + TreeFilter filter = PathFilter.create(path); + return SubmoduleWalk.forIndex(repo).setFilter(filter).next(); + } + + public Repository call() throws JGitInternalException { + 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); + + try { + if (submoduleExists()) + throw new JGitInternalException(MessageFormat.format( + JGitText.get().submoduleExists, path)); + } catch (IOException e) { + throw new JGitInternalException(e.getMessage(), e); + } + + // Clone submodule repository + File moduleDirectory = SubmoduleWalk.getSubmoduleDirectory(repo, path); + CloneCommand clone = Git.cloneRepository(); + clone.setDirectory(moduleDirectory); + clone.setURI(uri); + if (monitor != null) + clone.setProgressMonitor(monitor); + if (credentialsProvider != null) + clone.setCredentialsProvider(credentialsProvider); + Repository subRepo = clone.call().getRepository(); + + // Save submodule URL to parent repository's config + StoredConfig config = repo.getConfig(); + config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, uri); + try { + config.save(); + } catch (IOException e) { + throw new JGitInternalException(e.getMessage(), e); + } + + // Save path and URL to parent repository's .gitmodules file + FileBasedConfig modulesConfig = new FileBasedConfig(new File( + repo.getWorkTree(), Constants.DOT_GIT_MODULES), repo.getFS()); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH, path); + modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL, uri); + try { + modulesConfig.save(); + } catch (IOException e) { + throw new JGitInternalException(e.getMessage(), e); + } + + AddCommand add = new AddCommand(repo); + // Add .gitmodules file to parent repository's index + add.addFilepattern(Constants.DOT_GIT_MODULES); + // Add submodule directory to parent repository's index + add.addFilepattern(path); + try { + add.call(); + } catch (NoFilepatternException e) { + throw new JGitInternalException(e.getMessage(), e); + } + + return subRepo; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java new file mode 100644 index 000000000..ad8f02e47 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2011, GitHub 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.api; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.submodule.SubmoduleWalk; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; + +/** + * A class used to execute a submodule init command. + * + * This will copy the 'url' and 'update' fields from the working tree + * .gitmodules file to a repository's config file for each submodule not + * currently present in the repository's config file. + * + * @see Git documentation about submodules + */ +public class SubmoduleInitCommand extends GitCommand> { + + private final Collection paths; + + /** + * @param repo + */ + public SubmoduleInitCommand(final Repository repo) { + super(repo); + paths = new ArrayList(); + } + + /** + * Add repository-relative submodule path to initialize + * + * @param path + * @return this command + */ + public SubmoduleInitCommand addPath(final String path) { + paths.add(path); + return this; + } + + public Collection call() throws JGitInternalException { + checkCallable(); + + try { + SubmoduleWalk generator = SubmoduleWalk.forIndex(repo); + if (!paths.isEmpty()) + generator.setFilter(PathFilterGroup.createFromStrings(paths)); + StoredConfig config = repo.getConfig(); + List initialized = new ArrayList(); + while (generator.next()) { + // Ignore entry if URL is already present in config file + if (generator.getConfigUrl() != null) + continue; + + String path = generator.getPath(); + // Copy 'url' and 'update' fields from .gitmodules to config + // file + String url = generator.getModulesUrl(); + String update = generator.getModulesUpdate(); + if (url != null) + config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, + path, ConfigConstants.CONFIG_KEY_URL, url); + if (update != null) + config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, + path, ConfigConstants.CONFIG_KEY_UPDATE, update); + if (url != null || update != null) + initialized.add(path); + } + // Save repository config if any values were updated + if (!initialized.isEmpty()) + config.save(); + return initialized; + } catch (IOException e) { + throw new JGitInternalException(e.getMessage(), e); + } catch (ConfigInvalidException e) { + throw new JGitInternalException(e.getMessage(), e); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java new file mode 100644 index 000000000..054258378 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2011, GitHub 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.api; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.submodule.SubmoduleWalk; +import org.eclipse.jgit.submodule.SubmoduleStatus; +import org.eclipse.jgit.submodule.SubmoduleStatusType; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; + +/** + * A class used to execute a submodule status command. + * + * @see Git documentation about submodules + */ +public class SubmoduleStatusCommand extends + GitCommand> { + + private final Collection paths; + + /** + * @param repo + */ + public SubmoduleStatusCommand(final Repository repo) { + super(repo); + paths = new ArrayList(); + } + + /** + * Add repository-relative submodule path to limit status reporting to + * + * @param path + * @return this command + */ + public SubmoduleStatusCommand addPath(final String path) { + paths.add(path); + return this; + } + + public Map call() throws JGitInternalException { + checkCallable(); + + try { + SubmoduleWalk generator = SubmoduleWalk.forIndex(repo); + if (!paths.isEmpty()) + generator.setFilter(PathFilterGroup.createFromStrings(paths)); + Map statuses = new HashMap(); + while (generator.next()) { + SubmoduleStatus status = getStatus(generator); + statuses.put(status.getPath(), status); + } + return statuses; + } catch (IOException e) { + throw new JGitInternalException(e.getMessage(), e); + } catch (ConfigInvalidException e) { + throw new JGitInternalException(e.getMessage(), e); + } + } + + private SubmoduleStatus getStatus(SubmoduleWalk generator) + throws IOException, ConfigInvalidException { + ObjectId id = generator.getObjectId(); + String path = generator.getPath(); + + // Report missing if no path in .gitmodules file + if (generator.getModulesPath() == null) + return new SubmoduleStatus(SubmoduleStatusType.MISSING, path, id); + + // Report uninitialized if no URL in config file + if (generator.getConfigUrl() == null) + return new SubmoduleStatus(SubmoduleStatusType.UNINITIALIZED, path, + id); + + // Report uninitialized if no submodule repository + Repository subRepo = generator.getRepository(); + if (subRepo == null) + return new SubmoduleStatus(SubmoduleStatusType.UNINITIALIZED, path, + id); + + ObjectId headId = subRepo.resolve(Constants.HEAD); + + // Report uninitialized if no HEAD commit in submodule repository + if (headId == null) + return new SubmoduleStatus(SubmoduleStatusType.UNINITIALIZED, path, + id, headId); + + // Report checked out if HEAD commit is different than index commit + if (!headId.equals(id)) + return new SubmoduleStatus(SubmoduleStatusType.REV_CHECKED_OUT, + path, id, headId); + + // Report initialized if HEAD commit is the same as the index commit + return new SubmoduleStatus(SubmoduleStatusType.INITIALIZED, path, id, + headId); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java new file mode 100644 index 000000000..43647a0c6 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2011, GitHub 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.api; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.submodule.SubmoduleWalk; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; + +/** + * A class used to execute a submodule sync command. + * + * This will set the remote URL in a submodule's repository to the current value + * in the .gitmodules file. + * + * @see Git documentation about submodules + */ +public class SubmoduleSyncCommand extends GitCommand> { + + private final Collection paths; + + /** + * @param repo + */ + public SubmoduleSyncCommand(final Repository repo) { + super(repo); + paths = new ArrayList(); + } + + /** + * Add repository-relative submodule path to synchronize + * + * @param path + * @return this command + */ + public SubmoduleSyncCommand addPath(final String path) { + paths.add(path); + return this; + } + + /** + * Get branch that HEAD currently points to + * + * @param subRepo + * @return shortened branch name, null on failures + * @throws IOException + */ + protected String getHeadBranch(final Repository subRepo) throws IOException { + Ref head = subRepo.getRef(Constants.HEAD); + if (head != null && head.isSymbolic()) + return Repository.shortenRefName(head.getLeaf().getName()); + else + return null; + } + + public Map call() throws JGitInternalException { + checkCallable(); + + try { + SubmoduleWalk generator = SubmoduleWalk.forIndex(repo); + if (!paths.isEmpty()) + generator.setFilter(PathFilterGroup.createFromStrings(paths)); + Map synced = new HashMap(); + StoredConfig config = repo.getConfig(); + while (generator.next()) { + String remoteUrl = generator.getModulesUrl(); + if (remoteUrl == null) + continue; + + String path = generator.getPath(); + config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, + path, ConfigConstants.CONFIG_KEY_URL, remoteUrl); + synced.put(path, remoteUrl); + + Repository subRepo = generator.getRepository(); + if (subRepo == null) + continue; + + StoredConfig subConfig = subRepo.getConfig(); + // Get name of remote associated with current branch and + // fall back to default remote name as last resort + String branch = getHeadBranch(subRepo); + String remote = null; + if (branch != null) + remote = subConfig.getString( + ConfigConstants.CONFIG_BRANCH_SECTION, branch, + ConfigConstants.CONFIG_KEY_REMOTE); + if (remote == null) + remote = Constants.DEFAULT_REMOTE_NAME; + + subConfig.setString(ConfigConstants.CONFIG_REMOTE_SECTION, + remote, ConfigConstants.CONFIG_KEY_URL, remoteUrl); + subConfig.save(); + } + if (!synced.isEmpty()) + config.save(); + return synced; + } catch (IOException e) { + throw new JGitInternalException(e.getMessage(), e); + } catch (ConfigInvalidException e) { + throw new JGitInternalException(e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java new file mode 100644 index 000000000..72daa489e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2011, GitHub 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.api; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.dircache.DirCacheCheckout; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.submodule.SubmoduleWalk; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; + +/** + * A class used to execute a submodule update command. + * + * @see Git documentation about submodules + */ +public class SubmoduleUpdateCommand extends GitCommand> { + + private ProgressMonitor monitor; + + private CredentialsProvider credentialsProvider; + + private final Collection paths; + + /** + * @param repo + */ + public SubmoduleUpdateCommand(final Repository repo) { + super(repo); + paths = new ArrayList(); + } + + /** + * The progress monitor associated with the clone operation. By default, + * this is set to NullProgressMonitor + * + * @see NullProgressMonitor + * @param monitor + * @return this command + */ + public SubmoduleUpdateCommand setProgressMonitor( + final ProgressMonitor monitor) { + this.monitor = monitor; + return this; + } + + /** + * @param credentialsProvider + * the {@link CredentialsProvider} to use + * @return this command + */ + public SubmoduleUpdateCommand setCredentialsProvider( + final CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + return this; + } + + /** + * Add repository-relative submodule path to initialize + * + * @param path + * @return this command + */ + public SubmoduleUpdateCommand addPath(final String path) { + paths.add(path); + return this; + } + + public Collection call() throws JGitInternalException { + checkCallable(); + + try { + SubmoduleWalk generator = SubmoduleWalk.forIndex(repo); + if (!paths.isEmpty()) + generator.setFilter(PathFilterGroup.createFromStrings(paths)); + List updated = new ArrayList(); + while (generator.next()) { + // Skip submodules not registered in .gitmodules file + if (generator.getModulesPath() == null) + continue; + // Skip submodules not registered in parent repository's config + String url = generator.getConfigUrl(); + if (url == null) + continue; + + Repository submoduleRepo = generator.getRepository(); + // Clone repository is not present + if (submoduleRepo == null) { + CloneCommand clone = Git.cloneRepository(); + clone.setURI(url); + clone.setDirectory(generator.getDirectory()); + if (monitor != null) + clone.setProgressMonitor(monitor); + if (credentialsProvider != null) + clone.setCredentialsProvider(credentialsProvider); + submoduleRepo = clone.call().getRepository(); + } + + RevWalk walk = new RevWalk(submoduleRepo); + RevCommit commit = walk.parseCommit(generator.getObjectId()); + + String update = generator.getConfigUpdate(); + if (ConfigConstants.CONFIG_KEY_MERGE.equals(update)) { + MergeCommand merge = new MergeCommand(submoduleRepo); + merge.include(commit); + merge.call(); + } else if (ConfigConstants.CONFIG_KEY_REBASE.equals(update)) { + RebaseCommand rebase = new RebaseCommand(submoduleRepo); + rebase.setUpstream(commit); + rebase.call(); + } else { + // Checkout commit referenced in parent repository's index + // as a detached HEAD + DirCacheCheckout co = new DirCacheCheckout(submoduleRepo, + submoduleRepo.lockDirCache(), commit.getTree()); + co.setFailOnConflict(true); + co.checkout(); + RefUpdate refUpdate = submoduleRepo.updateRef( + Constants.HEAD, true); + refUpdate.setNewObjectId(commit); + refUpdate.forceUpdate(); + } + updated.add(generator.getPath()); + } + return updated; + } catch (IOException e) { + throw new JGitInternalException(e.getMessage(), e); + } catch (ConfigInvalidException e) { + throw new JGitInternalException(e.getMessage(), e); + } catch (GitAPIException e) { + throw new JGitInternalException(e.getMessage(), e); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java index 41862c7e4..ce11e6663 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java @@ -72,6 +72,9 @@ public class ConfigConstants { /** The "workflow" section */ public static final String CONFIG_WORKFLOW_SECTION = "workflow"; + /** The "submodule" section */ + public static final String CONFIG_SUBMODULE_SECTION = "submodule"; + /** The "algorithm" key */ public static final String CONFIG_KEY_ALGORITHM = "algorithm"; @@ -160,4 +163,10 @@ public class ConfigConstants { /** The "defaultsourceref" key */ public static final String CONFIG_KEY_DEFBRANCHSTARTPOINT = "defbranchstartpoint"; + + /** The "path" key */ + public static final String CONFIG_KEY_PATH = "path"; + + /** The "update" key */ + public static final String CONFIG_KEY_UPDATE = "update"; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java index 1d77cc174..6621e54c5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java @@ -328,6 +328,9 @@ public final class Constants { /** Name of the ignore file */ public static final String DOT_GIT_IGNORE = ".gitignore"; + /** Name of the submodules file */ + public static final String DOT_GIT_MODULES = ".gitmodules"; + /** * Create a new digest function for objects. * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleStatus.java b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleStatus.java new file mode 100644 index 000000000..2298f4b17 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleStatus.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2011, GitHub 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.submodule; + +import org.eclipse.jgit.lib.ObjectId; + +/** + * Status class containing the type, path, and commit id of the submodule. + */ +public class SubmoduleStatus { + + private final SubmoduleStatusType type; + + private final String path; + + private final ObjectId indexId; + + private final ObjectId headId; + + /** + * Create submodule status + * + * @param type + * @param path + * @param indexId + */ + public SubmoduleStatus(final SubmoduleStatusType type, final String path, + final ObjectId indexId) { + this(type, path, indexId, null); + } + + /** + * Create submodule status + * + * @param type + * @param path + * @param indexId + * @param headId + */ + public SubmoduleStatus(final SubmoduleStatusType type, final String path, + final ObjectId indexId, final ObjectId headId) { + this.type = type; + this.path = path; + this.indexId = indexId; + this.headId = headId; + } + + /** + * @return type + */ + public SubmoduleStatusType getType() { + return type; + } + + /** + * @return path + */ + public String getPath() { + return path; + } + + /** + * @return index object id + */ + public ObjectId getIndexId() { + return indexId; + } + + /** + * @return HEAD object id + */ + public ObjectId getHeadId() { + return headId; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleStatusType.java b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleStatusType.java new file mode 100644 index 000000000..be145f3d6 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleStatusType.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011, GitHub 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.submodule; + +/** + * Enumeration of different statuses that a submodule can be in + */ +public enum SubmoduleStatusType { + + /** Submodule's configuration is missing */ + MISSING, + + /** Submodule's Git repository is not initialized */ + UNINITIALIZED, + + /** Submodule's Git repository is initialized */ + INITIALIZED, + + /** + * Submodule commit checked out is different than the commit referenced in + * the index tree + */ + REV_CHECKED_OUT; +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java new file mode 100644 index 000000000..ebe994826 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2011, GitHub 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.submodule; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.jgit.dircache.DirCacheIterator; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryBuilder; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.treewalk.AbstractTreeIterator; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.eclipse.jgit.treewalk.filter.TreeFilter; +import org.eclipse.jgit.util.FS; + +/** + * Walker that visits all submodule entries found in a tree + */ +public class SubmoduleWalk { + + /** + * Create a generator to walk over the submodule entries currently in the + * index + * + * @param repository + * @return generator over submodule index entries + * @throws IOException + */ + public static SubmoduleWalk forIndex(Repository repository) + throws IOException { + SubmoduleWalk generator = new SubmoduleWalk(repository); + generator.setTree(new DirCacheIterator(repository.readDirCache())); + return generator; + } + + /** + * Create a generator and advance it to the submodule entry at the given + * path + * + * @param repository + * @param treeId + * @param path + * @return generator at given path, null if no submodule at given path + * @throws IOException + */ + public static SubmoduleWalk forPath(Repository repository, + AnyObjectId treeId, String path) throws IOException { + SubmoduleWalk generator = new SubmoduleWalk(repository); + generator.setTree(treeId); + PathFilter filter = PathFilter.create(path); + generator.setFilter(filter); + while (generator.next()) + if (filter.isDone(generator.walk)) + return generator; + return null; + } + + /** + * Create a generator and advance it to the submodule entry at the given + * path + * + * @param repository + * @param iterator + * @param path + * @return generator at given path, null if no submodule at given path + * @throws IOException + */ + public static SubmoduleWalk forPath(Repository repository, + AbstractTreeIterator iterator, String path) throws IOException { + SubmoduleWalk generator = new SubmoduleWalk(repository); + generator.setTree(iterator); + PathFilter filter = PathFilter.create(path); + generator.setFilter(filter); + while (generator.next()) + if (filter.isDone(generator.walk)) + return generator; + return null; + } + + /** + * Get submodule directory + * + * @param parent + * @param path + * @return directory + */ + public static File getSubmoduleDirectory(final Repository parent, + final String path) { + return new File(parent.getWorkTree(), path); + } + + /** + * Get submodule repository + * + * @param parent + * @param path + * @return repository or null if repository doesn't exist + * @throws IOException + */ + public static Repository getSubmoduleRepository(final Repository parent, + final String path) throws IOException { + File directory = getSubmoduleGitDirectory(parent, path); + if (!directory.isDirectory()) + return null; + try { + return new RepositoryBuilder().setMustExist(true) + .setFS(FS.DETECTED).setGitDir(directory).build(); + } catch (RepositoryNotFoundException e) { + return null; + } + } + + /** + * Get the .git directory for a repository submodule path + * + * @param parent + * @param path + * @return .git for submodule repository + */ + public static File getSubmoduleGitDirectory(final Repository parent, + final String path) { + return new File(getSubmoduleDirectory(parent, path), Constants.DOT_GIT); + } + + private final Repository repository; + + private final TreeWalk walk; + + private StoredConfig repoConfig; + + private FileBasedConfig modulesConfig; + + private String path; + + /** + * Create submodule generator + * + * @param repository + * @throws IOException + */ + public SubmoduleWalk(final Repository repository) throws IOException { + this.repository = repository; + repoConfig = repository.getConfig(); + walk = new TreeWalk(repository); + walk.setRecursive(true); + } + + private void loadModulesConfig() throws IOException, ConfigInvalidException { + if (modulesConfig == null) { + File modulesFile = new File(repository.getWorkTree(), + Constants.DOT_GIT_MODULES); + FileBasedConfig config = new FileBasedConfig(modulesFile, + repository.getFS()); + config.load(); + modulesConfig = config; + } + } + + /** + * Set tree filter + * + * @param filter + * @return this generator + */ + public SubmoduleWalk setFilter(TreeFilter filter) { + walk.setFilter(filter); + return this; + } + + /** + * Set the tree iterator used for finding submodule entries + * + * @param iterator + * @return this generator + * @throws CorruptObjectException + */ + public SubmoduleWalk setTree(final AbstractTreeIterator iterator) + throws CorruptObjectException { + walk.addTree(iterator); + return this; + } + + /** + * Set the tree used for finding submodule entries + * + * @param treeId + * @return this generator + * @throws IOException + * @throws IncorrectObjectTypeException + * @throws MissingObjectException + */ + public SubmoduleWalk setTree(final AnyObjectId treeId) throws IOException { + walk.addTree(treeId); + return this; + } + + /** + * Reset generator and start new submodule walk + * + * @return this generator + */ + public SubmoduleWalk reset() { + repoConfig = repository.getConfig(); + modulesConfig = null; + walk.reset(); + return this; + } + + /** + * Get directory that will be the root of the submodule's local repository + * + * @return submodule repository directory + */ + public File getDirectory() { + return getSubmoduleDirectory(repository, path); + } + + /** + * Get the .git directory for the current submodule entry + * + * @return .git for submodule repository + */ + public File getGitDirectory() { + return getSubmoduleGitDirectory(repository, path); + } + + /** + * Advance to next submodule in the index tree. + * + * The object id and path of the next entry can be obtained by calling + * {@link #getObjectId()} and {@link #getPath()}. + * + * @return true if entry found, false otherwise + * @throws IOException + */ + public boolean next() throws IOException { + while (walk.next()) { + if (FileMode.GITLINK != walk.getFileMode(0)) + continue; + path = walk.getPathString(); + return true; + } + path = null; + return false; + } + + /** + * Get path of current submodule entry + * + * @return path + */ + public String getPath() { + return path; + } + + /** + * Get object id of current submodule entry + * + * @return object id + */ + public ObjectId getObjectId() { + return walk.getObjectId(0); + } + + /** + * Get the configured path for current entry. This will be the value from + * the .gitmodules file in the current repository's working tree. + * + * @return configured path + * @throws ConfigInvalidException + * @throws IOException + */ + public String getModulesPath() throws IOException, ConfigInvalidException { + loadModulesConfig(); + return modulesConfig.getString( + ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_PATH); + } + + /** + * Get the configured remote URL for current entry. This will be the value + * from the repository's config. + * + * @return configured URL + * @throws ConfigInvalidException + * @throws IOException + */ + public String getConfigUrl() throws IOException, ConfigInvalidException { + return repoConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION, + path, ConfigConstants.CONFIG_KEY_URL); + } + + /** + * Get the configured remote URL for current entry. This will be the value + * from the .gitmodules file in the current repository's working tree. + * + * @return configured URL + * @throws ConfigInvalidException + * @throws IOException + */ + public String getModulesUrl() throws IOException, ConfigInvalidException { + loadModulesConfig(); + return modulesConfig.getString( + ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_URL); + } + + /** + * Get the configured update field for current entry. This will be the value + * from the repository's config. + * + * @return update value + * @throws ConfigInvalidException + * @throws IOException + */ + public String getConfigUpdate() throws IOException, ConfigInvalidException { + return repoConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION, + path, ConfigConstants.CONFIG_KEY_UPDATE); + } + + /** + * Get the configured update field for current entry. This will be the value + * from the .gitmodules file in the current repository's working tree. + * + * @return update value + * @throws ConfigInvalidException + * @throws IOException + */ + public String getModulesUpdate() throws IOException, ConfigInvalidException { + loadModulesConfig(); + return modulesConfig.getString( + ConfigConstants.CONFIG_SUBMODULE_SECTION, path, + ConfigConstants.CONFIG_KEY_UPDATE); + } + + /** + * Does the current submodule entry have a .git directory in the parent + * repository's working tree? + * + * @return true if .git directory exists, false otherwise + */ + public boolean hasGitDirectory() { + return getGitDirectory().isDirectory(); + } + + /** + * Get repository for current submodule entry + * + * @see #hasGitDirectory() + * @return repository or null if non-existent + * @throws IOException + */ + public Repository getRepository() throws IOException { + return getSubmoduleRepository(repository, path); + } + + /** + * Get commit id that HEAD points to in the current submodule's repository + * + * @return object id of HEAD reference + * @throws IOException + */ + public ObjectId getHead() throws IOException { + Repository subRepo = getRepository(); + return subRepo != null ? subRepo.resolve(Constants.HEAD) : null; + } + + /** + * Get ref that HEAD points to in the current submodule's repository + * + * @return ref name, null on failures + * @throws IOException + */ + public String getHeadRef() throws IOException { + Repository subRepo = getRepository(); + if (subRepo == null) + return null; + Ref head = subRepo.getRef(Constants.HEAD); + return head != null ? head.getLeaf().getName() : null; + } +}