summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Sohn <matthias.sohn@sap.com>2015-02-04 14:21:52 +0100
committerMatthias Sohn <matthias.sohn@sap.com>2015-02-04 14:21:52 +0100
commit21f667edbaa1727e08ea9356fcbfbefdee481b2f (patch)
treec845201e9a152f9828f2e2b265bd873f5c80c0eb
parent23ad3a37638f295e22a67ca16aefeeb53467e289 (diff)
parentfc801dd79f7c6eb3765e4652136f219480478bf6 (diff)
downloadjgit-21f667edbaa1727e08ea9356fcbfbefdee481b2f.tar.gz
jgit-21f667edbaa1727e08ea9356fcbfbefdee481b2f.zip
Merge branch 'stable-3.7'
* stable-3.7: Add option --orphan for checkout Prepare post 3.7.0.201502031740-rc1 builds JGit v3.7.0.201502031740-rc1 Support for the pre-commit hook Fix FileUtils.testRelativize_mixedCase which failed on Mac OS X Add a hook test Introduce hook support into the FS implementations If a pack isn't found on disk remove it from pack list Conflicts: org.eclipse.jgit.java7.test/META-INF/MANIFEST.MF Change-Id: I936acd24d47b911fa30ab29856094e1b2c6ac3db Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
-rw-r--r--org.eclipse.jgit.java7.test/META-INF/MANIFEST.MF1
-rw-r--r--org.eclipse.jgit.java7.test/src/org/eclipse/jgit/util/HookTest.java136
-rw-r--r--org.eclipse.jgit.java7/src/org/eclipse/jgit/util/FS_POSIX_Java7.java16
-rw-r--r--org.eclipse.jgit.java7/src/org/eclipse/jgit/util/FS_Win32_Java7Cygwin.java18
-rw-r--r--org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java12
-rw-r--r--org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties1
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java9
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java68
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java71
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RejectCommitException.java61
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java341
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java23
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java66
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/Hook.java149
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java112
22 files changed, 1117 insertions, 11 deletions
diff --git a/org.eclipse.jgit.java7.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.java7.test/META-INF/MANIFEST.MF
index ad8800103b..a1b2782cb5 100644
--- a/org.eclipse.jgit.java7.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.java7.test/META-INF/MANIFEST.MF
@@ -6,6 +6,7 @@ Bundle-Version: 4.0.0.qualifier
Bundle-Vendor: %provider_name
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Import-Package: org.eclipse.jgit.api;version="[4.0.0,4.1.0)",
+ org.eclipse.jgit.api.errors;version="[4.0.0,4.1.0)",
org.eclipse.jgit.diff;version="[4.0.0,4.1.0)",
org.eclipse.jgit.dircache;version="[4.0.0,4.1.0)",
org.eclipse.jgit.internal.storage.file;version="4.0.0",
diff --git a/org.eclipse.jgit.java7.test/src/org/eclipse/jgit/util/HookTest.java b/org.eclipse.jgit.java7.test/src/org/eclipse/jgit/util/HookTest.java
new file mode 100644
index 0000000000..96550889c8
--- /dev/null
+++ b/org.eclipse.jgit.java7.test/src/org/eclipse/jgit/util/HookTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2014 Matthias Sohn <matthias.sohn@sap.com>
+ * 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.RejectCommitException;
+import org.eclipse.jgit.junit.JGitTestUtil;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.junit.Assume;
+import org.junit.Test;
+
+public class HookTest extends RepositoryTestCase {
+
+ @Test
+ public void testFindHook() throws Exception {
+ assumeSupportedPlatform();
+
+ Hook h = Hook.PRE_COMMIT;
+ assertNull("no hook should be installed", FS.DETECTED.findHook(db, h));
+ File hookFile = writeHookFile(h.getName(),
+ "#!/bin/bash\necho \"test $1 $2\"");
+ assertEquals("exected to find pre-commit hook", hookFile,
+ FS.DETECTED.findHook(db, h));
+ }
+
+ @Test
+ public void testRunHook() throws Exception {
+ assumeSupportedPlatform();
+
+ Hook h = Hook.PRE_COMMIT;
+ writeHookFile(
+ h.getName(),
+ "#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\necho 1>&2 \"stderr\"");
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ByteArrayOutputStream err = new ByteArrayOutputStream();
+ ProcessResult res = FS.DETECTED.runIfPresent(db, h, new String[] {
+ "arg1", "arg2" },
+ new PrintStream(out), new PrintStream(err), "stdin");
+ assertEquals("unexpected hook output", "test arg1 arg2\nstdin\n",
+ out.toString("UTF-8"));
+ assertEquals("unexpected output on stderr stream", "stderr\n",
+ err.toString("UTF-8"));
+ assertEquals("unexpected exit code", 0, res.getExitCode());
+ assertEquals("unexpected process status", ProcessResult.Status.OK,
+ res.getStatus());
+ }
+
+ @Test
+ public void testPreCommitHook() throws Exception {
+ assumeSupportedPlatform();
+
+ Hook h = Hook.PRE_COMMIT;
+ writeHookFile(h.getName(),
+ "#!/bin/sh\necho \"test\"\n\necho 1>&2 \"stderr\"\nexit 1");
+ Git git = Git.wrap(db);
+ String path = "a.txt";
+ writeTrashFile(path, "content");
+ git.add().addFilepattern(path).call();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try {
+ git.commit().setMessage("commit")
+ .setHookOutputStream(new PrintStream(out)).call();
+ fail("expected pre-commit hook to abort commit");
+ } catch (RejectCommitException e) {
+ assertEquals("unexpected error message from pre-commit hook",
+ "Commit rejected by \"pre-commit\" hook.\nstderr\n",
+ e.getMessage());
+ assertEquals("unexpected output from pre-commit hook", "test\n",
+ out.toString());
+ } catch (Throwable e) {
+ fail("unexpected exception thrown by pre-commit hook: " + e);
+ }
+ }
+
+ private File writeHookFile(final String name, final String data)
+ throws IOException {
+ File path = new File(db.getWorkTree() + "/.git/hooks/", name);
+ JGitTestUtil.write(path, data);
+ FS.DETECTED.setExecute(path, true);
+ return path;
+ }
+
+ private void assumeSupportedPlatform() {
+ Assume.assumeTrue(FS.DETECTED instanceof FS_POSIX
+ || FS.DETECTED instanceof FS_Win32_Java7Cygwin);
+ }
+}
diff --git a/org.eclipse.jgit.java7/src/org/eclipse/jgit/util/FS_POSIX_Java7.java b/org.eclipse.jgit.java7/src/org/eclipse/jgit/util/FS_POSIX_Java7.java
index 4a73a9bcf5..300cf93bc8 100644
--- a/org.eclipse.jgit.java7/src/org/eclipse/jgit/util/FS_POSIX_Java7.java
+++ b/org.eclipse.jgit.java7/src/org/eclipse/jgit/util/FS_POSIX_Java7.java
@@ -53,6 +53,9 @@ import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Set;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+
/**
* FS implementation for Java7 on unix like systems
*/
@@ -344,4 +347,17 @@ public class FS_POSIX_Java7 extends FS_POSIX {
public String normalize(String name) {
return FileUtil.normalize(name);
}
+
+ /**
+ * @since 3.7
+ */
+ @Override
+ public File findHook(Repository repository, Hook hook) {
+ final File gitdir = repository.getDirectory();
+ final Path hookPath = gitdir.toPath().resolve(Constants.HOOKS)
+ .resolve(hook.getName());
+ if (Files.isExecutable(hookPath))
+ return hookPath.toFile();
+ return null;
+ }
}
diff --git a/org.eclipse.jgit.java7/src/org/eclipse/jgit/util/FS_Win32_Java7Cygwin.java b/org.eclipse.jgit.java7/src/org/eclipse/jgit/util/FS_Win32_Java7Cygwin.java
index e40d7cf0b5..b6e5d93885 100644
--- a/org.eclipse.jgit.java7/src/org/eclipse/jgit/util/FS_Win32_Java7Cygwin.java
+++ b/org.eclipse.jgit.java7/src/org/eclipse/jgit/util/FS_Win32_Java7Cygwin.java
@@ -45,6 +45,11 @@ package org.eclipse.jgit.util;
import java.io.File;
import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
/**
* FS for Java7 on Windows with Cygwin
@@ -135,4 +140,17 @@ public class FS_Win32_Java7Cygwin extends FS_Win32_Cygwin {
public Attributes getAttributes(File path) {
return FileUtil.getFileAttributesBasic(this, path);
}
+
+ /**
+ * @since 3.7
+ */
+ @Override
+ public File findHook(Repository repository, Hook hook) {
+ final File gitdir = repository.getDirectory();
+ final Path hookPath = gitdir.toPath().resolve(Constants.HOOKS)
+ .resolve(hook.getName());
+ if (Files.isExecutable(hookPath))
+ return hookPath.toFile();
+ return null;
+ }
}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
index 90284736cf..cef9b9e1a6 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
@@ -188,6 +188,18 @@ public class CheckoutTest extends CLIRepositoryTestCase {
assertEquals("Hello world a", read(fileA));
}
+ @Test
+ public void testCheckoutOrphan() throws Exception {
+ Git git = new Git(db);
+ git.commit().setMessage("initial commit").call();
+
+ assertEquals("Switched to a new branch 'new_branch'",
+ execute("git checkout --orphan new_branch"));
+ assertEquals("refs/heads/new_branch", db.getRef("HEAD").getTarget().getName());
+ RevCommit commit = git.commit().setMessage("orphan commit").call();
+ assertEquals(0, commit.getParentCount());
+ }
+
/**
* Steps:
* <ol>
diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
index 8a77bf928a..2806f91463 100644
--- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
+++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
@@ -345,3 +345,4 @@ usage_updateRemoteRefsFromAnotherRepository=Update remote refs from another repo
usage_useNameInsteadOfOriginToTrackUpstream=use <name> instead of 'origin' to track upstream
usage_checkoutBranchAfterClone=checkout named branch instead of remotes's HEAD
usage_viewCommitHistory=View commit history
+usage_orphan=Create a new orphan branch. The first commit made on this new branch will have no parents amd it will be the root of a new history totally disconnected from other branches and commits. \ No newline at end of file
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
index 8f911fd924..56d4fcff02 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
@@ -71,6 +71,9 @@ class Checkout extends TextBuiltin {
@Option(name = "--force", aliases = { "-f" }, usage = "usage_forceCheckout")
private boolean force = false;
+ @Option(name = "--orphan", usage = "usage_orphan")
+ private boolean orphan = false;
+
@Argument(required = true, index = 0, metaVar = "metaVar_name", usage = "usage_checkout")
private String name;
@@ -95,6 +98,7 @@ class Checkout extends TextBuiltin {
command.setCreateBranch(createBranch);
command.setName(name);
command.setForce(force);
+ command.setOrphan(orphan);
}
try {
String oldBranch = db.getBranch();
@@ -107,10 +111,9 @@ class Checkout extends TextBuiltin {
name));
return;
}
- if (createBranch)
+ if (createBranch || orphan)
outw.println(MessageFormat.format(
- CLIText.get().switchedToNewBranch,
- Repository.shortenRefName(ref.getName())));
+ CLIText.get().switchedToNewBranch, name));
else
outw.println(MessageFormat.format(
CLIText.get().switchedToBranch,
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java
index 7b1627854f..0d7d31b3ad 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java
@@ -50,6 +50,7 @@ import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
+import java.util.regex.Matcher;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.junit.After;
@@ -434,4 +435,71 @@ public class FileUtilTest {
String target = fs.readSymLink(new File(trash, "x"));
assertEquals("y", target);
}
+
+ @Test
+ public void testRelativize_doc() {
+ // This is the javadoc example
+ String base = toOSPathString("c:\\Users\\jdoe\\eclipse\\git\\project");
+ String other = toOSPathString("c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml");
+ String expected = toOSPathString("..\\another_project\\pom.xml");
+
+ String actual = FileUtils.relativize(base, other);
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testRelativize_mixedCase() {
+ SystemReader systemReader = SystemReader.getInstance();
+ String base = toOSPathString("C:\\git\\jgit");
+ String other = toOSPathString("C:\\Git\\test\\d\\f.txt");
+ String expectedCaseInsensitive = toOSPathString("..\\test\\d\\f.txt");
+ String expectedCaseSensitive = toOSPathString("..\\..\\Git\\test\\d\\f.txt");
+
+ if (systemReader.isWindows()) {
+ String actual = FileUtils.relativize(base, other);
+ assertEquals(expectedCaseInsensitive, actual);
+ } else if (systemReader.isMacOS()) {
+ String actual = FileUtils.relativize(base, other);
+ assertEquals(expectedCaseInsensitive, actual);
+ } else {
+ String actual = FileUtils.relativize(base, other);
+ assertEquals(expectedCaseSensitive, actual);
+ }
+ }
+
+ @Test
+ public void testRelativize_scheme() {
+ String base = toOSPathString("file:/home/eclipse/runtime-New_configuration/project_1/file.java");
+ String other = toOSPathString("file:/home/eclipse/runtime-New_configuration/project");
+ // 'file.java' is treated as a folder
+ String expected = toOSPathString("../../project");
+
+ String actual = FileUtils.relativize(base, other);
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testRelativize_equalPaths() {
+ String base = toOSPathString("file:/home/eclipse/runtime-New_configuration/project_1");
+ String other = toOSPathString("file:/home/eclipse/runtime-New_configuration/project_1");
+ String expected = "";
+
+ String actual = FileUtils.relativize(base, other);
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testRelativize_whitespaces() {
+ String base = toOSPathString("/home/eclipse 3.4/runtime New_configuration/project_1");
+ String other = toOSPathString("/home/eclipse 3.4/runtime New_configuration/project_1/file");
+ String expected = "file";
+
+ String actual = FileUtils.relativize(base, other);
+ assertEquals(expected, actual);
+ }
+
+ private String toOSPathString(String path) {
+ return path.replaceAll("/|\\\\",
+ Matcher.quoteReplacement(File.separator));
+ }
}
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 55cf3ea71e..efe61052b0 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -104,6 +104,7 @@ commitAlreadyExists=exists {0}
commitMessageNotSpecified=commit message not specified
commitOnRepoWithoutHEADCurrentlyNotSupported=Commit on repo without HEAD currently not supported
commitAmendOnInitialNotPossible=Amending is not possible on initial commit.
+commitRejectedByHook=Commit rejected by "{0}" hook.\n{1}
compressingObjects=Compressing objects
connectionFailed=connection failed
connectionTimeOut=Connection time out: {0}
@@ -193,6 +194,7 @@ errorListing=Error listing {0}
errorOccurredDuringUnpackingOnTheRemoteEnd=error occurred during unpacking on the remote end: {0}
errorReadingInfoRefs=error reading info/refs
errorSymlinksNotSupported=Symlinks are not supported with this OS/JRE
+exceptionCaughtDuringExecutionOfHook=Exception caught during execution of "{0}" hook.
exceptionCaughtDuringExecutionOfAddCommand=Exception caught during execution of add command
exceptionCaughtDuringExecutionOfArchiveCommand=Exception caught during execution of archive command
exceptionCaughtDuringExecutionOfCherryPickCommand=Exception caught during execution of cherry-pick command. {0}
@@ -206,6 +208,7 @@ exceptionCaughtDuringExecutionOfResetCommand=Exception caught during execution o
exceptionCaughtDuringExecutionOfRevertCommand=Exception caught during execution of revert command. {0}
exceptionCaughtDuringExecutionOfRmCommand=Exception caught during execution of rm command
exceptionCaughtDuringExecutionOfTagCommand=Exception caught during execution of tag command
+exceptionHookExecutionInterrupted=Execution of "{0}" hook interrupted.
exceptionOccurredDuringAddingOfOptionToALogCommand=Exception occurred during adding of {0} as option to a Log command
exceptionOccurredDuringReadingOfGIT_DIR=Exception occurred during reading of $GIT_DIR/{0}. {1}
exceptionWhileReadingPack=ERROR: Exception caught while accessing pack file {0}, the pack file might be corrupt
@@ -392,6 +395,7 @@ packObjectCountMismatch=Pack object count mismatch: pack {0} index {1}: {2}
packRefs=Pack refs
packSizeNotSetYet=Pack size not yet set since it has not yet been received
packTooLargeForIndexVersion1=Pack too large for index version 1
+packWasDeleted=Pack file {0} was deleted, removing it from pack list
packWriterStatistics=Total {0,number,#0} (delta {1,number,#0}), reused {2,number,#0} (delta {3,number,#0})
panicCantRenameIndexFile=Panic: index file {0} must be renamed to replace {1}; until then repository is corrupt
patchApplyException=Cannot apply: {0}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
index 30dcaa5b6f..d4eb0b3b21 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
@@ -169,7 +169,8 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> {
.setMessage(srcCommit.getFullMessage())
.setReflogComment(reflogPrefix + " " //$NON-NLS-1$
+ srcCommit.getShortMessage())
- .setAuthor(srcCommit.getAuthorIdent()).call();
+ .setAuthor(srcCommit.getAuthorIdent())
+ .setNoVerify(true).call();
cherryPickedRefs.add(src);
} else {
if (merger.failed())
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
index f8406e0231..93400aa4d1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -42,8 +42,10 @@
*/
package org.eclipse.jgit.api;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.PrintStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
@@ -56,6 +58,7 @@ import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.NoFilepatternException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.NoMessageException;
+import org.eclipse.jgit.api.errors.RejectCommitException;
import org.eclipse.jgit.api.errors.UnmergedPathsException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.dircache.DirCache;
@@ -84,6 +87,9 @@ import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.ChangeIdUtil;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.Hook;
+import org.eclipse.jgit.util.ProcessResult;
/**
* A class used to execute a {@code Commit} command. It has setters for all
@@ -120,10 +126,19 @@ public class CommitCommand extends GitCommand<RevCommit> {
private String reflogComment;
/**
+ * Setting this option bypasses the {@link Hook#PRE_COMMIT pre-commit} and
+ * {@link Hook#COMMIT_MSG commit-msg} hooks.
+ */
+ private boolean noVerify;
+
+ private PrintStream hookOutRedirect;
+
+ /**
* @param repo
*/
protected CommitCommand(Repository repo) {
super(repo);
+ hookOutRedirect = System.out;
}
/**
@@ -144,11 +159,14 @@ public class CommitCommand extends GitCommand<RevCommit> {
* else
* @throws WrongRepositoryStateException
* when repository is not in the right state for committing
+ * @throws RejectCommitException
+ * if there are either pre-commit or commit-msg hooks present in
+ * the repository and at least one of them rejects the commit.
*/
public RevCommit call() throws GitAPIException, NoHeadException,
NoMessageException, UnmergedPathsException,
- ConcurrentRefUpdateException,
- WrongRepositoryStateException {
+ ConcurrentRefUpdateException, WrongRepositoryStateException,
+ RejectCommitException {
checkCallable();
Collections.sort(only);
@@ -160,6 +178,23 @@ public class CommitCommand extends GitCommand<RevCommit> {
throw new WrongRepositoryStateException(MessageFormat.format(
JGitText.get().cannotCommitOnARepoWithState,
state.name()));
+
+ if (!noVerify) {
+ final ByteArrayOutputStream errorByteArray = new ByteArrayOutputStream();
+ final PrintStream hookErrRedirect = new PrintStream(
+ errorByteArray);
+ ProcessResult preCommitHookResult = FS.DETECTED.runIfPresent(
+ repo, Hook.PRE_COMMIT, new String[0], hookOutRedirect,
+ hookErrRedirect, null);
+ if (preCommitHookResult.getStatus() == ProcessResult.Status.OK
+ && preCommitHookResult.getExitCode() != 0) {
+ String errorMessage = MessageFormat.format(
+ JGitText.get().commitRejectedByHook, Hook.PRE_COMMIT.getName(),
+ errorByteArray.toString());
+ throw new RejectCommitException(errorMessage);
+ }
+ }
+
processOptions(state, rw);
if (all && !repo.isBare() && repo.getWorkTree() != null) {
@@ -733,4 +768,36 @@ public class CommitCommand extends GitCommand<RevCommit> {
return this;
}
+ /**
+ * Sets the {@link #noVerify} option on this commit command.
+ * <p>
+ * Both the {@link Hook#PRE_COMMIT pre-commit} and {@link Hook#COMMIT_MSG
+ * commit-msg} hooks can block a commit by their return value; setting this
+ * option to <code>true</code> will bypass these two hooks.
+ * </p>
+ *
+ * @param noVerify
+ * Whether this commit should be verified by the pre-commit and
+ * commit-msg hooks.
+ * @return {@code this}
+ * @since 3.7
+ */
+ public CommitCommand setNoVerify(boolean noVerify) {
+ this.noVerify = noVerify;
+ return this;
+ }
+
+ /**
+ * Set the output stream for hook scripts executed by this command. If not
+ * set it defaults to {@code System.out}.
+ *
+ * @param hookStdOut
+ * the output stream for hook scripts executed by this command
+ * @return {@code this}
+ * @since 3.7
+ */
+ public CommitCommand setHookOutputStream(PrintStream hookStdOut) {
+ this.hookOutRedirect = hookStdOut;
+ return this;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
index 47424a9074..7d3e823187 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -462,7 +462,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
String newMessage = interactiveHandler
.modifyCommitMessage(oldMessage);
newHead = new Git(repo).commit().setMessage(newMessage)
- .setAmend(true).call();
+ .setAmend(true).setNoVerify(true).call();
return null;
case EDIT:
rebaseState.createFile(AMEND, commitToPick.name());
@@ -768,15 +768,14 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
}
retNewHead = new Git(repo).commit()
.setMessage(stripCommentLines(commitMessage))
- .setAmend(true).call();
+ .setAmend(true).setNoVerify(true).call();
rebaseState.getFile(MESSAGE_SQUASH).delete();
rebaseState.getFile(MESSAGE_FIXUP).delete();
} else {
// Next step is either Squash or Fixup
- retNewHead = new Git(repo).commit()
- .setMessage(commitMessage).setAmend(true)
- .call();
+ retNewHead = new Git(repo).commit().setMessage(commitMessage)
+ .setAmend(true).setNoVerify(true).call();
}
return retNewHead;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RejectCommitException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RejectCommitException.java
new file mode 100644
index 0000000000..6036a2731b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RejectCommitException.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 Obeo.
+ * 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.errors;
+
+/**
+ * Exception thrown when a commit is rejected by a hook (either
+ * {@link org.eclipse.jgit.util.Hook#PRE_COMMIT pre-commit} or
+ * {@link org.eclipse.jgit.util.Hook#COMMIT_MSG commit-msg}).
+ *
+ * @since 3.7
+ */
+public class RejectCommitException extends GitAPIException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * @param message
+ */
+ public RejectCommitException(String message) {
+ super(message);
+ }
+}
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 fd38dc1491..8c81c95134 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -163,6 +163,7 @@ public class JGitText extends TranslationBundle {
/***/ public String commitMessageNotSpecified;
/***/ public String commitOnRepoWithoutHEADCurrentlyNotSupported;
/***/ public String commitAmendOnInitialNotPossible;
+ /***/ public String commitRejectedByHook;
/***/ public String compressingObjects;
/***/ public String connectionFailed;
/***/ public String connectionTimeOut;
@@ -252,6 +253,7 @@ public class JGitText extends TranslationBundle {
/***/ public String errorOccurredDuringUnpackingOnTheRemoteEnd;
/***/ public String errorReadingInfoRefs;
/***/ public String errorSymlinksNotSupported;
+ /***/ public String exceptionCaughtDuringExecutionOfHook;
/***/ public String exceptionCaughtDuringExecutionOfAddCommand;
/***/ public String exceptionCaughtDuringExecutionOfArchiveCommand;
/***/ public String exceptionCaughtDuringExecutionOfCherryPickCommand;
@@ -265,6 +267,7 @@ public class JGitText extends TranslationBundle {
/***/ public String exceptionCaughtDuringExecutionOfRevertCommand;
/***/ public String exceptionCaughtDuringExecutionOfRmCommand;
/***/ public String exceptionCaughtDuringExecutionOfTagCommand;
+ /***/ public String exceptionHookExecutionInterrupted;
/***/ public String exceptionOccurredDuringAddingOfOptionToALogCommand;
/***/ public String exceptionOccurredDuringReadingOfGIT_DIR;
/***/ public String exceptionWhileReadingPack;
@@ -451,6 +454,7 @@ public class JGitText extends TranslationBundle {
/***/ public String packRefs;
/***/ public String packSizeNotSetYet;
/***/ public String packTooLargeForIndexVersion1;
+ /***/ public String packWasDeleted;
/***/ public String packWriterStatistics;
/***/ public String panicCantRenameIndexFile;
/***/ public String patchApplyException;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
index 76fadefe53..adbe1f8652 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
@@ -557,6 +557,9 @@ public class ObjectDirectory extends FileObjectDatabase {
tmpl = JGitText.get().corruptPack;
// Assume the pack is corrupted, and remove it from the list.
removePack(p);
+ } else if (e instanceof FileNotFoundException) {
+ tmpl = JGitText.get().packWasDeleted;
+ removePack(p);
} else {
tmpl = JGitText.get().exceptionWhileReadingPack;
// Don't remove the pack from the list, as the error may be
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 705d54cfa3..ed0ed04d94 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -387,6 +387,13 @@ public final class Constants {
public static final String MODULES = "modules";
/**
+ * Name of the folder (inside gitDir) where the hooks are stored.
+ *
+ * @since 3.7
+ */
+ public static final String HOOKS = "hooks";
+
+ /**
* Create a new digest function for objects.
*
* @return a new digest object.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
index 0c63b190f2..081bf87c54 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -44,18 +44,31 @@
package org.eclipse.jgit.util;
import java.io.BufferedReader;
+import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintStream;
+import java.io.PrintWriter;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.MessageFormat;
import java.util.Arrays;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.SymlinksNotSupportedException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.ProcessResult.Status;
/** Abstraction to support various file system operations not in Java. */
public abstract class FS {
@@ -614,6 +627,288 @@ public abstract class FS {
}
/**
+ * See {@link FileUtils#relativize(String, String)}.
+ *
+ * @param base
+ * The path against which <code>other</code> should be
+ * relativized.
+ * @param other
+ * The path that will be made relative to <code>base</code>.
+ * @return A relative path that, when resolved against <code>base</code>,
+ * will yield the original <code>other</code>.
+ * @see FileUtils#relativize(String, String)
+ * @since 3.7
+ */
+ public String relativize(String base, String other) {
+ return FileUtils.relativize(base, other);
+ }
+
+ /**
+ * Checks whether the given hook is defined for the given repository, then
+ * runs it with the given arguments.
+ * <p>
+ * The hook's standard output and error streams will be redirected to
+ * <code>System.out</code> and <code>System.err</code> respectively. The
+ * hook will have no stdin.
+ * </p>
+ *
+ * @param repository
+ * The repository for which a hook should be run.
+ * @param hook
+ * The hook to be executed.
+ * @param args
+ * Arguments to pass to this hook. Cannot be <code>null</code>,
+ * but can be an empty array.
+ * @return The ProcessResult describing this hook's execution.
+ * @throws JGitInternalException
+ * if we fail to run the hook somehow. Causes may include an
+ * interrupted process or I/O errors.
+ * @since 3.7
+ */
+ public ProcessResult runIfPresent(Repository repository, final Hook hook,
+ String[] args) throws JGitInternalException {
+ return runIfPresent(repository, hook, args, System.out, System.err,
+ null);
+ }
+
+ /**
+ * Checks whether the given hook is defined for the given repository, then
+ * runs it with the given arguments.
+ *
+ * @param repository
+ * The repository for which a hook should be run.
+ * @param hook
+ * The hook to be executed.
+ * @param args
+ * Arguments to pass to this hook. Cannot be <code>null</code>,
+ * but can be an empty array.
+ * @param outRedirect
+ * A print stream on which to redirect the hook's stdout. Can be
+ * <code>null</code>, in which case the hook's standard output
+ * will be lost.
+ * @param errRedirect
+ * A print stream on which to redirect the hook's stderr. Can be
+ * <code>null</code>, in which case the hook's standard error
+ * will be lost.
+ * @param stdinArgs
+ * A string to pass on to the standard input of the hook. May be
+ * <code>null</code>.
+ * @return The ProcessResult describing this hook's execution.
+ * @throws JGitInternalException
+ * if we fail to run the hook somehow. Causes may include an
+ * interrupted process or I/O errors.
+ * @since 3.7
+ */
+ public ProcessResult runIfPresent(Repository repository, final Hook hook,
+ String[] args, PrintStream outRedirect, PrintStream errRedirect,
+ String stdinArgs) throws JGitInternalException {
+ return new ProcessResult(Status.NOT_SUPPORTED);
+ }
+
+ /**
+ * See
+ * {@link #runIfPresent(Repository, Hook, String[], PrintStream, PrintStream, String)}
+ * . Should only be called by FS supporting shell scripts execution.
+ *
+ * @param repository
+ * The repository for which a hook should be run.
+ * @param hook
+ * The hook to be executed.
+ * @param args
+ * Arguments to pass to this hook. Cannot be <code>null</code>,
+ * but can be an empty array.
+ * @param outRedirect
+ * A print stream on which to redirect the hook's stdout. Can be
+ * <code>null</code>, in which case the hook's standard output
+ * will be lost.
+ * @param errRedirect
+ * A print stream on which to redirect the hook's stderr. Can be
+ * <code>null</code>, in which case the hook's standard error
+ * will be lost.
+ * @param stdinArgs
+ * A string to pass on to the standard input of the hook. May be
+ * <code>null</code>.
+ * @return The ProcessResult describing this hook's execution.
+ * @throws JGitInternalException
+ * if we fail to run the hook somehow. Causes may include an
+ * interrupted process or I/O errors.
+ * @since 3.7
+ */
+ protected ProcessResult internalRunIfPresent(Repository repository,
+ final Hook hook, String[] args, PrintStream outRedirect,
+ PrintStream errRedirect, String stdinArgs)
+ throws JGitInternalException {
+ final File hookFile = findHook(repository, hook);
+ if (hookFile == null)
+ return new ProcessResult(Status.NOT_PRESENT);
+
+ final String hookPath = hookFile.getAbsolutePath();
+ final File runDirectory;
+ if (repository.isBare())
+ runDirectory = repository.getDirectory();
+ else
+ runDirectory = repository.getWorkTree();
+ final String cmd = relativize(runDirectory.getAbsolutePath(),
+ hookPath);
+ ProcessBuilder hookProcess = runInShell(cmd, args);
+ hookProcess.directory(runDirectory);
+ try {
+ return new ProcessResult(runProcess(hookProcess, outRedirect,
+ errRedirect, stdinArgs), Status.OK);
+ } catch (IOException e) {
+ throw new JGitInternalException(MessageFormat.format(
+ JGitText.get().exceptionCaughtDuringExecutionOfHook,
+ hook.getName()), e);
+ } catch (InterruptedException e) {
+ throw new JGitInternalException(MessageFormat.format(
+ JGitText.get().exceptionHookExecutionInterrupted,
+ hook.getName()), e);
+ }
+ }
+
+
+ /**
+ * Tries to find a hook matching the given one in the given repository.
+ *
+ * @param repository
+ * The repository within which to find a hook.
+ * @param hook
+ * The hook we're trying to find.
+ * @return The {@link File} containing this particular hook if it exists in
+ * the given repository, <code>null</code> otherwise.
+ * @since 3.7
+ */
+ public File findHook(Repository repository, final Hook hook) {
+ final File hookFile = new File(new File(repository.getDirectory(),
+ Constants.HOOKS), hook.getName());
+ return hookFile.isFile() ? hookFile : null;
+ }
+
+ /**
+ * Runs the given process until termination, clearing its stdout and stderr
+ * streams on-the-fly.
+ *
+ * @param hookProcessBuilder
+ * The process builder configured for this hook.
+ * @param outRedirect
+ * A print stream on which to redirect the hook's stdout. Can be
+ * <code>null</code>, in which case the hook's standard output
+ * will be lost.
+ * @param errRedirect
+ * A print stream on which to redirect the hook's stderr. Can be
+ * <code>null</code>, in which case the hook's standard error
+ * will be lost.
+ * @param stdinArgs
+ * A string to pass on to the standard input of the hook. Can be
+ * <code>null</code>.
+ * @return the exit value of this hook.
+ * @throws IOException
+ * if an I/O error occurs while executing this hook.
+ * @throws InterruptedException
+ * if the current thread is interrupted while waiting for the
+ * process to end.
+ * @since 3.7
+ */
+ protected int runProcess(ProcessBuilder hookProcessBuilder,
+ OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
+ throws IOException, InterruptedException {
+ final ExecutorService executor = Executors.newFixedThreadPool(2);
+ Process process = null;
+ // We'll record the first I/O exception that occurs, but keep on trying
+ // to dispose of our open streams and file handles
+ IOException ioException = null;
+ try {
+ process = hookProcessBuilder.start();
+ final Callable<Void> errorGobbler = new StreamGobbler(
+ process.getErrorStream(), errRedirect);
+ final Callable<Void> outputGobbler = new StreamGobbler(
+ process.getInputStream(), outRedirect);
+ executor.submit(errorGobbler);
+ executor.submit(outputGobbler);
+ if (stdinArgs != null) {
+ final PrintWriter stdinWriter = new PrintWriter(
+ process.getOutputStream());
+ stdinWriter.print(stdinArgs);
+ stdinWriter.flush();
+ // We are done with this hook's input. Explicitly close its
+ // stdin now to kick off any blocking read the hook might have.
+ stdinWriter.close();
+ }
+ return process.waitFor();
+ } catch (IOException e) {
+ ioException = e;
+ } finally {
+ shutdownAndAwaitTermination(executor);
+ if (process != null) {
+ try {
+ process.waitFor();
+ } catch (InterruptedException e) {
+ // Thrown by the outer try.
+ // Swallow this one to carry on our cleanup, and clear the
+ // interrupted flag (processes throw the exception without
+ // clearing the flag).
+ Thread.interrupted();
+ }
+ // A process doesn't clean its own resources even when destroyed
+ // Explicitly try and close all three streams, preserving the
+ // outer I/O exception if any.
+ try {
+ process.getErrorStream().close();
+ } catch (IOException e) {
+ ioException = ioException != null ? ioException : e;
+ }
+ try {
+ process.getInputStream().close();
+ } catch (IOException e) {
+ ioException = ioException != null ? ioException : e;
+ }
+ try {
+ process.getOutputStream().close();
+ } catch (IOException e) {
+ ioException = ioException != null ? ioException : e;
+ }
+ process.destroy();
+ }
+ }
+ // We can only be here if the outer try threw an IOException.
+ throw ioException;
+ }
+
+ /**
+ * Shuts down an {@link ExecutorService} in two phases, first by calling
+ * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and
+ * then calling {@link ExecutorService#shutdownNow() shutdownNow}, if
+ * necessary, to cancel any lingering tasks. Returns true if the pool has
+ * been properly shutdown, false otherwise.
+ * <p>
+ *
+ * @param pool
+ * the pool to shutdown
+ * @return <code>true</code> if the pool has been properly shutdown,
+ * <code>false</code> otherwise.
+ */
+ private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
+ boolean hasShutdown = true;
+ pool.shutdown(); // Disable new tasks from being submitted
+ try {
+ // Wait a while for existing tasks to terminate
+ if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
+ pool.shutdownNow(); // Cancel currently executing tasks
+ // Wait a while for tasks to respond to being canceled
+ if (!pool.awaitTermination(5, TimeUnit.SECONDS))
+ hasShutdown = false;
+ }
+ } catch (InterruptedException ie) {
+ // (Re-)Cancel if current thread also interrupted
+ pool.shutdownNow();
+ // Preserve interrupt status
+ Thread.currentThread().interrupt();
+ hasShutdown = false;
+ }
+ return hasShutdown;
+ }
+
+ /**
* Initialize a ProcesssBuilder to run a command using the system shell.
*
* @param cmd
@@ -802,4 +1097,50 @@ public abstract class FS {
public String normalize(String name) {
return name;
}
+
+ /**
+ * This runnable will consume an input stream's content into an output
+ * stream as soon as it gets available.
+ * <p>
+ * Typically used to empty processes' standard output and error, preventing
+ * them to choke.
+ * </p>
+ * <p>
+ * <b>Note</b> that a {@link StreamGobbler} will never close either of its
+ * streams.
+ * </p>
+ */
+ private static class StreamGobbler implements Callable<Void> {
+ private final BufferedReader reader;
+
+ private final BufferedWriter writer;
+
+ public StreamGobbler(InputStream stream, OutputStream output) {
+ this.reader = new BufferedReader(new InputStreamReader(stream));
+ if (output == null)
+ this.writer = null;
+ else
+ this.writer = new BufferedWriter(new OutputStreamWriter(output));
+ }
+
+ public Void call() throws IOException {
+ boolean writeFailure = false;
+
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ // Do not try to write again after a failure, but keep reading
+ // as long as possible to prevent the input stream from choking.
+ if (!writeFailure && writer != null) {
+ try {
+ writer.write(line);
+ writer.newLine();
+ writer.flush();
+ } catch (IOException e) {
+ writeFailure = true;
+ }
+ }
+ }
+ return null;
+ }
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
index b7de056ee4..ee29584239 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
@@ -44,11 +44,14 @@ package org.eclipse.jgit.util;
import java.io.File;
import java.io.IOException;
+import java.io.PrintStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.lib.Repository;
/**
* Base FS for POSIX based systems
@@ -121,4 +124,15 @@ public abstract class FS_POSIX extends FS {
proc.command(argv);
return proc;
}
+
+ /**
+ * @since 3.7
+ */
+ @Override
+ public ProcessResult runIfPresent(Repository repository, Hook hook,
+ String[] args, PrintStream outRedirect, PrintStream errRedirect,
+ String stdinArgs) throws JGitInternalException {
+ return internalRunIfPresent(repository, hook, args, outRedirect,
+ errRedirect, stdinArgs);
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
index 81f2001125..d0abd33275 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
@@ -44,12 +44,15 @@
package org.eclipse.jgit.util;
import java.io.File;
+import java.io.PrintStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.lib.Repository;
/**
* FS implementation for Cygwin on Windows
@@ -135,4 +138,24 @@ public class FS_Win32_Cygwin extends FS_Win32 {
proc.command(argv);
return proc;
}
+
+ /**
+ * @since 3.7
+ */
+ @Override
+ public String relativize(String base, String other) {
+ final String relativized = super.relativize(base, other);
+ return relativized.replace(File.separatorChar, '/');
+ }
+
+ /**
+ * @since 3.7
+ */
+ @Override
+ public ProcessResult runIfPresent(Repository repository, Hook hook,
+ String[] args, PrintStream outRedirect, PrintStream errRedirect,
+ String stdinArgs) throws JGitInternalException {
+ return internalRunIfPresent(repository, hook, args, outRedirect,
+ errRedirect, stdinArgs);
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
index 9e5b22180b..1e58245ea7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
@@ -51,6 +51,7 @@ import java.nio.channels.FileLock;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
+import java.util.regex.Pattern;
import org.eclipse.jgit.internal.JGitText;
@@ -387,4 +388,69 @@ public class FileUtils {
}
throw new IOException(JGitText.get().cannotCreateTempDir);
}
+
+ /**
+ * This will try and make a given path relative to another.
+ * <p>
+ * For example, if this is called with the two following paths :
+ *
+ * <pre>
+ * <code>base = "c:\\Users\\jdoe\\eclipse\\git\\project"</code>
+ * <code>other = "c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml"</code>
+ * </pre>
+ *
+ * This will return "..\\another_project\\pom.xml".
+ * </p>
+ * <p>
+ * This method uses {@link File#separator} to split the paths into segments.
+ * </p>
+ * <p>
+ * <b>Note</b> that this will return the empty String if <code>base</code>
+ * and <code>other</code> are equal.
+ * </p>
+ *
+ * @param base
+ * The path against which <code>other</code> should be
+ * relativized. This will be assumed to denote the path to a
+ * folder and not a file.
+ * @param other
+ * The path that will be made relative to <code>base</code>.
+ * @return A relative path that, when resolved against <code>base</code>,
+ * will yield the original <code>other</code>.
+ * @since 3.7
+ */
+ public static String relativize(String base, String other) {
+ if (base.equals(other))
+ return ""; //$NON-NLS-1$
+
+ final boolean ignoreCase = !FS.DETECTED.isCaseSensitive();
+ final String[] baseSegments = base.split(Pattern.quote(File.separator));
+ final String[] otherSegments = other.split(Pattern
+ .quote(File.separator));
+
+ int commonPrefix = 0;
+ while (commonPrefix < baseSegments.length
+ && commonPrefix < otherSegments.length) {
+ if (ignoreCase
+ && baseSegments[commonPrefix]
+ .equalsIgnoreCase(otherSegments[commonPrefix]))
+ commonPrefix++;
+ else if (!ignoreCase
+ && baseSegments[commonPrefix]
+ .equals(otherSegments[commonPrefix]))
+ commonPrefix++;
+ else
+ break;
+ }
+
+ final StringBuilder builder = new StringBuilder();
+ for (int i = commonPrefix; i < baseSegments.length; i++)
+ builder.append("..").append(File.separator); //$NON-NLS-1$
+ for (int i = commonPrefix; i < otherSegments.length; i++) {
+ builder.append(otherSegments[i]);
+ if (i < otherSegments.length - 1)
+ builder.append(File.separator);
+ }
+ return builder.toString();
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Hook.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Hook.java
new file mode 100644
index 0000000000..c24c9a3d0a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Hook.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2014 Obeo.
+ * 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.util;
+
+/**
+ * An enum describing the different hooks a user can implement to customize his
+ * repositories.
+ *
+ * @since 3.7
+ */
+public enum Hook {
+ /**
+ * Literal for the "pre-commit" git hook.
+ * <p>
+ * This hook is invoked by git commit, and can be bypassed with the
+ * "no-verify" option. It takes no parameter, and is invoked before
+ * obtaining the proposed commit log message and making a commit.
+ * </p>
+ * <p>
+ * A non-zero exit code from the called hook means that the commit should be
+ * aborted.
+ * </p>
+ */
+ PRE_COMMIT("pre-commit"), //$NON-NLS-1$
+
+ /**
+ * Literal for the "prepare-commit-msg" git hook.
+ * <p>
+ * This hook is invoked by git commit right after preparing the default
+ * message, and before any editing possibility is displayed to the user.
+ * </p>
+ * <p>
+ * A non-zero exit code from the called hook means that the commit should be
+ * aborted.
+ * </p>
+ */
+ PREPARE_COMMIT_MSG("prepare-commit-msg"), //$NON-NLS-1$
+
+ /**
+ * Literal for the "commit-msg" git hook.
+ * <p>
+ * This hook is invoked by git commit, and can be bypassed with the
+ * "no-verify" option. Its single parameter is the path to the file
+ * containing the prepared commit message (typically
+ * "&lt;gitdir>/COMMIT-EDITMSG").
+ * </p>
+ * <p>
+ * A non-zero exit code from the called hook means that the commit should be
+ * aborted.
+ * </p>
+ */
+ COMMIT_MSG("commit-msg"), //$NON-NLS-1$
+
+ /**
+ * Literal for the "post-commit" git hook.
+ * <p>
+ * This hook is invoked by git commit. It takes no parameter and is invoked
+ * after a commit has been made.
+ * </p>
+ * <p>
+ * The exit code of this hook has no significance.
+ * </p>
+ */
+ POST_COMMIT("post-commit"), //$NON-NLS-1$
+
+ /**
+ * Literal for the "post-rewrite" git hook.
+ * <p>
+ * This hook is invoked after commands that rewrite commits (currently, only
+ * "git rebase" and "git commit --amend"). It a single argument denoting the
+ * source of the call (one of <code>rebase</code> or <code>amend</code>). It
+ * then accepts a list of rewritten commits through stdin, in the form
+ * <code>&lt;old SHA-1> &lt;new SHA-1>LF</code>.
+ * </p>
+ * <p>
+ * The exit code of this hook has no significance.
+ * </p>
+ */
+ POST_REWRITE("post-rewrite"), //$NON-NLS-1$
+
+ /**
+ * Literal for the "pre-rebase" git hook.
+ * <p>
+ * </p>
+ * This hook is invoked right before the rebase operation runs. It accepts
+ * up to two parameters, the first being the upstream from which the branch
+ * to rebase has been forked. If the tip of the series of commits to rebase
+ * is HEAD, the other parameter is unset. Otherwise, that tip is passed as
+ * the second parameter of the script.
+ * <p>
+ * A non-zero exit code from the called hook means that the rebase should be
+ * aborted.
+ * </p>
+ */
+ PRE_REBASE("pre-rebase"); //$NON-NLS-1$
+
+ private final String name;
+
+ private Hook(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return The name of this hook.
+ */
+ public String getName() {
+ return name;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java
new file mode 100644
index 0000000000..f56bb1577e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014 Obeo.
+ * 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.util;
+
+/**
+ * Describes the result of running an external process.
+ *
+ * @since 3.7
+ */
+public class ProcessResult {
+ /**
+ * Status of a process' execution.
+ */
+ public static enum Status {
+ /**
+ * The script was found and launched properly. It may still have exited
+ * with a non-zero {@link #exitCode}.
+ */
+ OK,
+
+ /** The script was not found on disk and thus could not be launched. */
+ NOT_PRESENT,
+
+ /**
+ * The script was found but could not be launched since it was not
+ * supported by the current {@link FS}.
+ */
+ NOT_SUPPORTED;
+ }
+
+ /** The exit code of the process. */
+ private final int exitCode;
+
+ /** Status of the process' execution. */
+ private final Status status;
+
+ /**
+ * Instantiates a process result with the given status and an exit code of
+ * <code>-1</code>.
+ *
+ * @param status
+ * Status describing the execution of the external process.
+ */
+ public ProcessResult(Status status) {
+ this(-1, status);
+ }
+
+ /**
+ * @param exitCode
+ * Exit code of the process.
+ * @param status
+ * Status describing the execution of the external process.
+ */
+ public ProcessResult(int exitCode, Status status) {
+ this.exitCode = exitCode;
+ this.status = status;
+ }
+
+ /**
+ * @return The exit code of the process.
+ */
+ public int getExitCode() {
+ return exitCode;
+ }
+
+ /**
+ * @return The status of the process' execution.
+ */
+ public Status getStatus() {
+ return status;
+ }
+}