aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit
diff options
context:
space:
mode:
authorDavid Turner <dturner@twosigma.com>2017-10-13 14:56:04 -0400
committerMatthias Sohn <matthias.sohn@sap.com>2017-12-27 23:47:25 +0100
commit243fba9a0ae7e4f6679338c8b7175cc2a0ec06f2 (patch)
tree982319759cd9946f0d46e28dfd6ce058b526961c /org.eclipse.jgit
parent4bbc74ba407e95954bb444df456d37a21363ce6c (diff)
downloadjgit-243fba9a0ae7e4f6679338c8b7175cc2a0ec06f2.tar.gz
jgit-243fba9a0ae7e4f6679338c8b7175cc2a0ec06f2.zip
Add a command to deinitialize submodules
Change-Id: Iaaefc2cbafbf083d6ab158b1c378ec69cc76d282 Signed-off-by: David Turner <dturner@twosigma.com> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
Diffstat (limited to 'org.eclipse.jgit')
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java319
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitResult.java105
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java2
5 files changed, 440 insertions, 1 deletions
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index f626724c4e..45032de9bd 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -286,6 +286,7 @@ expectedACKNAKFoundEOF=Expected ACK/NAK, found EOF
expectedACKNAKGot=Expected ACK/NAK, got: {0}
expectedBooleanStringValue=Expected boolean string value
expectedCharacterEncodingGuesses=Expected {0} character encoding guesses
+expectedDirectoryNotSubmodule=Expected submodule ''{0}'' to be a directory
expectedEOFReceived=expected EOF; received ''{0}'' instead
expectedGot=expected ''{0}'', got ''{1}''
expectedLessThanGot=expected less than ''{0}'', got ''{1}''
@@ -701,6 +702,7 @@ unexpectedOddResult=odd: {0} + {1} - {2}
unexpectedRefReport={0}: unexpected ref report: {1}
unexpectedReportLine=unexpected report line: {0}
unexpectedReportLine2={0} unexpected report line: {1}
+unexpectedSubmoduleStatus=Unexpected submodule status: ''{0}''
unknownOrUnsupportedCommand=Unknown or unsupported command "{0}", only "{1}" is allowed.
unknownDIRCVersion=Unknown DIRC version {0}
unknownHost=unknown host
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
index 516620b437..400a7dfe48 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
@@ -623,7 +623,18 @@ public class Git implements AutoCloseable {
}
/**
- * Return a command object to execute a {@code submodule status} command
+ * Returns a command object to execute a {@code submodule deinit} command
+ *
+ * @return a {@link org.eclipse.jgit.api.SubmoduleDeinitCommand} used to
+ * remove a submodule's working tree manifestation
+ * @since 4.10
+ */
+ public SubmoduleDeinitCommand submoduleDeinit() {
+ return new SubmoduleDeinitCommand(repo);
+ }
+
+ /**
+ * Returns a command object to execute a {@code submodule status} command
*
* @return a {@link org.eclipse.jgit.api.SubmoduleStatusCommand} used to
* report the status of a repository's configured submodules
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java
new file mode 100644
index 0000000000..569a8e3596
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2017, Two Sigma Open Source
+ * 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 static org.eclipse.jgit.util.FileUtils.RECURSIVE;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.api.errors.NoHeadException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.submodule.SubmoduleWalk;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
+import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.eclipse.jgit.util.FileUtils;
+
+/**
+ * A class used to execute a submodule deinit command.
+ * <p>
+ * This will remove the module(s) from the working tree, but won't affect
+ * .git/modules.
+ *
+ * @since 4.10
+ * @see <a href=
+ * "http://www.kernel.org/pub/software/scm/git/docs/git-submodule.html"
+ * >Git documentation about submodules</a>
+ */
+public class SubmoduleDeinitCommand
+ extends GitCommand<Collection<SubmoduleDeinitResult>> {
+
+ private final Collection<String> paths;
+
+ private boolean force;
+
+ /**
+ * Constructor of SubmoduleDeinitCommand
+ *
+ * @param repo
+ */
+ public SubmoduleDeinitCommand(Repository repo) {
+ super(repo);
+ paths = new ArrayList<>();
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ *
+ * @return the set of repositories successfully deinitialized.
+ * @throws NoSuchSubmoduleException
+ * if any of the submodules which we might want to deinitialize
+ * don't exist
+ */
+ @Override
+ public Collection<SubmoduleDeinitResult> call() throws GitAPIException {
+ checkCallable();
+ try {
+ if (paths.isEmpty()) {
+ return Collections.emptyList();
+ }
+ for (String path : paths) {
+ if (!submoduleExists(path)) {
+ throw new NoSuchSubmoduleException(path);
+ }
+ }
+ List<SubmoduleDeinitResult> results = new ArrayList<>(paths.size());
+ try (RevWalk revWalk = new RevWalk(repo);
+ SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) {
+ generator.setFilter(PathFilterGroup.createFromStrings(paths));
+ StoredConfig config = repo.getConfig();
+ while (generator.next()) {
+ String path = generator.getPath();
+ String name = generator.getModuleName();
+ SubmoduleDeinitStatus status = checkDirty(revWalk, path);
+ switch (status) {
+ case SUCCESS:
+ deinit(path);
+ break;
+ case ALREADY_DEINITIALIZED:
+ break;
+ case DIRTY:
+ if (force) {
+ deinit(path);
+ status = SubmoduleDeinitStatus.FORCED;
+ }
+ break;
+ default:
+ throw new JGitInternalException(MessageFormat.format(
+ JGitText.get().unexpectedSubmoduleStatus,
+ status));
+ }
+
+ config.unsetSection(
+ ConfigConstants.CONFIG_SUBMODULE_SECTION, name);
+ results.add(new SubmoduleDeinitResult(path, status));
+ }
+ }
+ return results;
+ } catch (IOException e) {
+ throw new JGitInternalException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Recursively delete the *contents* of path, but leave path as an empty
+ * directory
+ *
+ * @param path
+ * the path to clean
+ * @throws IOException
+ */
+ private void deinit(String path) throws IOException {
+ File dir = new File(repo.getWorkTree(), path);
+ if (!dir.isDirectory()) {
+ throw new JGitInternalException(MessageFormat.format(
+ JGitText.get().expectedDirectoryNotSubmodule, path));
+ }
+ final File[] ls = dir.listFiles();
+ if (ls != null) {
+ for (int i = 0; i < ls.length; i++) {
+ FileUtils.delete(ls[i], RECURSIVE);
+ }
+ }
+ }
+
+ /**
+ * Check if a submodule is dirty. A submodule is dirty if there are local
+ * changes to the submodule relative to its HEAD, including untracked files.
+ * It is also dirty if the HEAD of the submodule does not match the value in
+ * the parent repo's index or HEAD.
+ *
+ * @param revWalk
+ * @param path
+ * @return status of the command
+ * @throws GitAPIException
+ * @throws IOException
+ */
+ private SubmoduleDeinitStatus checkDirty(RevWalk revWalk, String path)
+ throws GitAPIException, IOException {
+ Ref head = repo.exactRef("HEAD"); //$NON-NLS-1$
+ if (head == null) {
+ throw new NoHeadException(
+ JGitText.get().invalidRepositoryStateNoHead);
+ }
+ RevCommit headCommit = revWalk.parseCommit(head.getObjectId());
+ RevTree tree = headCommit.getTree();
+
+ ObjectId submoduleHead;
+ try (SubmoduleWalk w = SubmoduleWalk.forPath(repo, tree, path)) {
+ submoduleHead = w.getHead();
+ if (submoduleHead == null) {
+ // The submodule is not checked out.
+ return SubmoduleDeinitStatus.ALREADY_DEINITIALIZED;
+ }
+ if (!submoduleHead.equals(w.getObjectId())) {
+ // The submodule's current HEAD doesn't match the value in the
+ // outer repo's HEAD.
+ return SubmoduleDeinitStatus.DIRTY;
+ }
+ }
+
+ try (SubmoduleWalk w = SubmoduleWalk.forIndex(repo)) {
+ if (!w.next()) {
+ // The submodule does not exist in the index (shouldn't happen
+ // since we check this earlier)
+ return SubmoduleDeinitStatus.DIRTY;
+ }
+ if (!submoduleHead.equals(w.getObjectId())) {
+ // The submodule's current HEAD doesn't match the value in the
+ // outer repo's index.
+ return SubmoduleDeinitStatus.DIRTY;
+ }
+
+ Repository submoduleRepo = w.getRepository();
+
+ Status status = Git.wrap(submoduleRepo).status().call();
+ return status.isClean() ? SubmoduleDeinitStatus.SUCCESS
+ : SubmoduleDeinitStatus.DIRTY;
+ }
+ }
+
+ /**
+ * Check if this path is a submodule by checking the index, which is what
+ * git submodule deinit checks.
+ *
+ * @param path
+ * path of the submodule
+ *
+ * @return {@code true} if path exists and is a submodule in index,
+ * {@code false} otherwise
+ * @throws IOException
+ */
+ private boolean submoduleExists(String path) throws IOException {
+ TreeFilter filter = PathFilter.create(path);
+ try (SubmoduleWalk w = SubmoduleWalk.forIndex(repo)) {
+ return w.setFilter(filter).next();
+ }
+ }
+
+ /**
+ * Add repository-relative submodule path to deinitialize
+ *
+ * @param path
+ * (with <code>/</code> as separator)
+ * @return this command
+ */
+ public SubmoduleDeinitCommand addPath(String path) {
+ paths.add(path);
+ return this;
+ }
+
+ /**
+ * If {@code true}, call() will deinitialize modules with local changes;
+ * else it will refuse to do so.
+ *
+ * @param force
+ * @return {@code this}
+ */
+ public SubmoduleDeinitCommand setForce(boolean force) {
+ this.force = force;
+ return this;
+ }
+
+ /**
+ * The user tried to deinitialize a submodule that doesn't exist in the
+ * index.
+ */
+ public static class NoSuchSubmoduleException extends GitAPIException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor of NoSuchSubmoduleException
+ *
+ * @param path
+ * path of non-existing submodule
+ */
+ public NoSuchSubmoduleException(String path) {
+ super(MessageFormat.format(JGitText.get().noSuchSubmodule, path));
+ }
+ }
+
+ /**
+ * The effect of a submodule deinit command for a given path
+ */
+ public enum SubmoduleDeinitStatus {
+ /**
+ * The submodule was not initialized in the first place
+ */
+ ALREADY_DEINITIALIZED,
+ /**
+ * The submodule was deinitialized
+ */
+ SUCCESS,
+ /**
+ * The submodule had local changes, but was deinitialized successfully
+ */
+ FORCED,
+ /**
+ * The submodule had local changes and force was false
+ */
+ DIRTY,
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitResult.java
new file mode 100644
index 0000000000..5a9cbc1025
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitResult.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017, Two Sigma Open Source
+ * 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;
+
+/**
+ * The result of a submodule deinit command for a particular path
+ *
+ * @since 4.10
+ */
+public class SubmoduleDeinitResult {
+ private String path;
+
+ private SubmoduleDeinitCommand.SubmoduleDeinitStatus status;
+
+ /**
+ * Constructor for SubmoduleDeinitResult
+ *
+ * @param path
+ * path of the submodule
+ * @param status
+ */
+ public SubmoduleDeinitResult(String path,
+ SubmoduleDeinitCommand.SubmoduleDeinitStatus status) {
+ this.path = path;
+ this.status = status;
+ }
+
+ /**
+ * Get the path of the submodule
+ *
+ * @return path of the submodule
+ */
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * Set the path of the submodule
+ *
+ * @param path
+ * path of the submodule
+ */
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ /**
+ * Get the status of the command
+ *
+ * @return the status of the command
+ */
+ public SubmoduleDeinitCommand.SubmoduleDeinitStatus getStatus() {
+ return status;
+ }
+
+ /**
+ * Set the status of the command
+ *
+ * @param status
+ * the status of the command
+ */
+ public void setStatus(SubmoduleDeinitCommand.SubmoduleDeinitStatus status) {
+ this.status = status;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index 5241ea2328..e23d2534b6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -347,6 +347,7 @@ public class JGitText extends TranslationBundle {
/***/ public String expectedACKNAKGot;
/***/ public String expectedBooleanStringValue;
/***/ public String expectedCharacterEncodingGuesses;
+ /***/ public String expectedDirectoryNotSubmodule;
/***/ public String expectedEOFReceived;
/***/ public String expectedGot;
/***/ public String expectedLessThanGot;
@@ -762,6 +763,7 @@ public class JGitText extends TranslationBundle {
/***/ public String unexpectedRefReport;
/***/ public String unexpectedReportLine;
/***/ public String unexpectedReportLine2;
+ /***/ public String unexpectedSubmoduleStatus;
/***/ public String unknownOrUnsupportedCommand;
/***/ public String unknownDIRCVersion;
/***/ public String unknownHost;