diff options
author | David Turner <dturner@twosigma.com> | 2017-10-13 14:56:04 -0400 |
---|---|---|
committer | Matthias Sohn <matthias.sohn@sap.com> | 2017-12-27 23:47:25 +0100 |
commit | 243fba9a0ae7e4f6679338c8b7175cc2a0ec06f2 (patch) | |
tree | 982319759cd9946f0d46e28dfd6ce058b526961c /org.eclipse.jgit | |
parent | 4bbc74ba407e95954bb444df456d37a21363ce6c (diff) | |
download | jgit-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')
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; |