* 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>tags/v4.0.0.201503231230-m1
Bundle-Vendor: %provider_name | Bundle-Vendor: %provider_name | ||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.7 | Bundle-RequiredExecutionEnvironment: JavaSE-1.7 | ||||
Import-Package: org.eclipse.jgit.api;version="[4.0.0,4.1.0)", | 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.diff;version="[4.0.0,4.1.0)", | ||||
org.eclipse.jgit.dircache;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", | org.eclipse.jgit.internal.storage.file;version="4.0.0", |
/* | |||||
* 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); | |||||
} | |||||
} |
import java.nio.file.attribute.PosixFilePermission; | import java.nio.file.attribute.PosixFilePermission; | ||||
import java.util.Set; | import java.util.Set; | ||||
import org.eclipse.jgit.lib.Constants; | |||||
import org.eclipse.jgit.lib.Repository; | |||||
/** | /** | ||||
* FS implementation for Java7 on unix like systems | * FS implementation for Java7 on unix like systems | ||||
*/ | */ | ||||
public String normalize(String name) { | public String normalize(String name) { | ||||
return FileUtil.normalize(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; | |||||
} | |||||
} | } |
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | 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 | * FS for Java7 on Windows with Cygwin | ||||
public Attributes getAttributes(File path) { | public Attributes getAttributes(File path) { | ||||
return FileUtil.getFileAttributesBasic(this, 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; | |||||
} | |||||
} | } |
assertEquals("Hello world a", read(fileA)); | 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: | * Steps: | ||||
* <ol> | * <ol> |
usage_useNameInsteadOfOriginToTrackUpstream=use <name> instead of 'origin' to track upstream | usage_useNameInsteadOfOriginToTrackUpstream=use <name> instead of 'origin' to track upstream | ||||
usage_checkoutBranchAfterClone=checkout named branch instead of remotes's HEAD | usage_checkoutBranchAfterClone=checkout named branch instead of remotes's HEAD | ||||
usage_viewCommitHistory=View commit history | 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. |
@Option(name = "--force", aliases = { "-f" }, usage = "usage_forceCheckout") | @Option(name = "--force", aliases = { "-f" }, usage = "usage_forceCheckout") | ||||
private boolean force = false; | 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") | @Argument(required = true, index = 0, metaVar = "metaVar_name", usage = "usage_checkout") | ||||
private String name; | private String name; | ||||
command.setCreateBranch(createBranch); | command.setCreateBranch(createBranch); | ||||
command.setName(name); | command.setName(name); | ||||
command.setForce(force); | command.setForce(force); | ||||
command.setOrphan(orphan); | |||||
} | } | ||||
try { | try { | ||||
String oldBranch = db.getBranch(); | String oldBranch = db.getBranch(); | ||||
name)); | name)); | ||||
return; | return; | ||||
} | } | ||||
if (createBranch) | |||||
if (createBranch || orphan) | |||||
outw.println(MessageFormat.format( | outw.println(MessageFormat.format( | ||||
CLIText.get().switchedToNewBranch, | |||||
Repository.shortenRefName(ref.getName()))); | |||||
CLIText.get().switchedToNewBranch, name)); | |||||
else | else | ||||
outw.println(MessageFormat.format( | outw.println(MessageFormat.format( | ||||
CLIText.get().switchedToBranch, | CLIText.get().switchedToBranch, |
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.util.regex.Matcher; | |||||
import org.eclipse.jgit.junit.JGitTestUtil; | import org.eclipse.jgit.junit.JGitTestUtil; | ||||
import org.junit.After; | import org.junit.After; | ||||
String target = fs.readSymLink(new File(trash, "x")); | String target = fs.readSymLink(new File(trash, "x")); | ||||
assertEquals("y", target); | 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)); | |||||
} | |||||
} | } |
commitMessageNotSpecified=commit message not specified | commitMessageNotSpecified=commit message not specified | ||||
commitOnRepoWithoutHEADCurrentlyNotSupported=Commit on repo without HEAD currently not supported | commitOnRepoWithoutHEADCurrentlyNotSupported=Commit on repo without HEAD currently not supported | ||||
commitAmendOnInitialNotPossible=Amending is not possible on initial commit. | commitAmendOnInitialNotPossible=Amending is not possible on initial commit. | ||||
commitRejectedByHook=Commit rejected by "{0}" hook.\n{1} | |||||
compressingObjects=Compressing objects | compressingObjects=Compressing objects | ||||
connectionFailed=connection failed | connectionFailed=connection failed | ||||
connectionTimeOut=Connection time out: {0} | connectionTimeOut=Connection time out: {0} | ||||
errorOccurredDuringUnpackingOnTheRemoteEnd=error occurred during unpacking on the remote end: {0} | errorOccurredDuringUnpackingOnTheRemoteEnd=error occurred during unpacking on the remote end: {0} | ||||
errorReadingInfoRefs=error reading info/refs | errorReadingInfoRefs=error reading info/refs | ||||
errorSymlinksNotSupported=Symlinks are not supported with this OS/JRE | 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 | exceptionCaughtDuringExecutionOfAddCommand=Exception caught during execution of add command | ||||
exceptionCaughtDuringExecutionOfArchiveCommand=Exception caught during execution of archive command | exceptionCaughtDuringExecutionOfArchiveCommand=Exception caught during execution of archive command | ||||
exceptionCaughtDuringExecutionOfCherryPickCommand=Exception caught during execution of cherry-pick command. {0} | exceptionCaughtDuringExecutionOfCherryPickCommand=Exception caught during execution of cherry-pick command. {0} | ||||
exceptionCaughtDuringExecutionOfRevertCommand=Exception caught during execution of revert command. {0} | exceptionCaughtDuringExecutionOfRevertCommand=Exception caught during execution of revert command. {0} | ||||
exceptionCaughtDuringExecutionOfRmCommand=Exception caught during execution of rm command | exceptionCaughtDuringExecutionOfRmCommand=Exception caught during execution of rm command | ||||
exceptionCaughtDuringExecutionOfTagCommand=Exception caught during execution of tag 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 | exceptionOccurredDuringAddingOfOptionToALogCommand=Exception occurred during adding of {0} as option to a Log command | ||||
exceptionOccurredDuringReadingOfGIT_DIR=Exception occurred during reading of $GIT_DIR/{0}. {1} | 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 | exceptionWhileReadingPack=ERROR: Exception caught while accessing pack file {0}, the pack file might be corrupt | ||||
packRefs=Pack refs | packRefs=Pack refs | ||||
packSizeNotSetYet=Pack size not yet set since it has not yet been received | packSizeNotSetYet=Pack size not yet set since it has not yet been received | ||||
packTooLargeForIndexVersion1=Pack too large for index version 1 | 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}) | 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 | panicCantRenameIndexFile=Panic: index file {0} must be renamed to replace {1}; until then repository is corrupt | ||||
patchApplyException=Cannot apply: {0} | patchApplyException=Cannot apply: {0} |
.setMessage(srcCommit.getFullMessage()) | .setMessage(srcCommit.getFullMessage()) | ||||
.setReflogComment(reflogPrefix + " " //$NON-NLS-1$ | .setReflogComment(reflogPrefix + " " //$NON-NLS-1$ | ||||
+ srcCommit.getShortMessage()) | + srcCommit.getShortMessage()) | ||||
.setAuthor(srcCommit.getAuthorIdent()).call(); | |||||
.setAuthor(srcCommit.getAuthorIdent()) | |||||
.setNoVerify(true).call(); | |||||
cherryPickedRefs.add(src); | cherryPickedRefs.add(src); | ||||
} else { | } else { | ||||
if (merger.failed()) | if (merger.failed()) |
*/ | */ | ||||
package org.eclipse.jgit.api; | package org.eclipse.jgit.api; | ||||
import java.io.ByteArrayOutputStream; | |||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.io.PrintStream; | |||||
import java.text.MessageFormat; | import java.text.MessageFormat; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Collections; | import java.util.Collections; | ||||
import org.eclipse.jgit.api.errors.NoFilepatternException; | import org.eclipse.jgit.api.errors.NoFilepatternException; | ||||
import org.eclipse.jgit.api.errors.NoHeadException; | import org.eclipse.jgit.api.errors.NoHeadException; | ||||
import org.eclipse.jgit.api.errors.NoMessageException; | 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.UnmergedPathsException; | ||||
import org.eclipse.jgit.api.errors.WrongRepositoryStateException; | import org.eclipse.jgit.api.errors.WrongRepositoryStateException; | ||||
import org.eclipse.jgit.dircache.DirCache; | import org.eclipse.jgit.dircache.DirCache; | ||||
import org.eclipse.jgit.treewalk.FileTreeIterator; | import org.eclipse.jgit.treewalk.FileTreeIterator; | ||||
import org.eclipse.jgit.treewalk.TreeWalk; | import org.eclipse.jgit.treewalk.TreeWalk; | ||||
import org.eclipse.jgit.util.ChangeIdUtil; | 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 | * A class used to execute a {@code Commit} command. It has setters for all | ||||
private String reflogComment; | 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 | * @param repo | ||||
*/ | */ | ||||
protected CommitCommand(Repository repo) { | protected CommitCommand(Repository repo) { | ||||
super(repo); | super(repo); | ||||
hookOutRedirect = System.out; | |||||
} | } | ||||
/** | /** | ||||
* else | * else | ||||
* @throws WrongRepositoryStateException | * @throws WrongRepositoryStateException | ||||
* when repository is not in the right state for committing | * 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, | public RevCommit call() throws GitAPIException, NoHeadException, | ||||
NoMessageException, UnmergedPathsException, | NoMessageException, UnmergedPathsException, | ||||
ConcurrentRefUpdateException, | |||||
WrongRepositoryStateException { | |||||
ConcurrentRefUpdateException, WrongRepositoryStateException, | |||||
RejectCommitException { | |||||
checkCallable(); | checkCallable(); | ||||
Collections.sort(only); | Collections.sort(only); | ||||
throw new WrongRepositoryStateException(MessageFormat.format( | throw new WrongRepositoryStateException(MessageFormat.format( | ||||
JGitText.get().cannotCommitOnARepoWithState, | JGitText.get().cannotCommitOnARepoWithState, | ||||
state.name())); | 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); | processOptions(state, rw); | ||||
if (all && !repo.isBare() && repo.getWorkTree() != null) { | if (all && !repo.isBare() && repo.getWorkTree() != null) { | ||||
return this; | 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; | |||||
} | |||||
} | } |
String newMessage = interactiveHandler | String newMessage = interactiveHandler | ||||
.modifyCommitMessage(oldMessage); | .modifyCommitMessage(oldMessage); | ||||
newHead = new Git(repo).commit().setMessage(newMessage) | newHead = new Git(repo).commit().setMessage(newMessage) | ||||
.setAmend(true).call(); | |||||
.setAmend(true).setNoVerify(true).call(); | |||||
return null; | return null; | ||||
case EDIT: | case EDIT: | ||||
rebaseState.createFile(AMEND, commitToPick.name()); | rebaseState.createFile(AMEND, commitToPick.name()); | ||||
} | } | ||||
retNewHead = new Git(repo).commit() | retNewHead = new Git(repo).commit() | ||||
.setMessage(stripCommentLines(commitMessage)) | .setMessage(stripCommentLines(commitMessage)) | ||||
.setAmend(true).call(); | |||||
.setAmend(true).setNoVerify(true).call(); | |||||
rebaseState.getFile(MESSAGE_SQUASH).delete(); | rebaseState.getFile(MESSAGE_SQUASH).delete(); | ||||
rebaseState.getFile(MESSAGE_FIXUP).delete(); | rebaseState.getFile(MESSAGE_FIXUP).delete(); | ||||
} else { | } else { | ||||
// Next step is either Squash or Fixup | // 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; | return retNewHead; | ||||
} | } |
/* | |||||
* 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); | |||||
} | |||||
} |
/***/ public String commitMessageNotSpecified; | /***/ public String commitMessageNotSpecified; | ||||
/***/ public String commitOnRepoWithoutHEADCurrentlyNotSupported; | /***/ public String commitOnRepoWithoutHEADCurrentlyNotSupported; | ||||
/***/ public String commitAmendOnInitialNotPossible; | /***/ public String commitAmendOnInitialNotPossible; | ||||
/***/ public String commitRejectedByHook; | |||||
/***/ public String compressingObjects; | /***/ public String compressingObjects; | ||||
/***/ public String connectionFailed; | /***/ public String connectionFailed; | ||||
/***/ public String connectionTimeOut; | /***/ public String connectionTimeOut; | ||||
/***/ public String errorOccurredDuringUnpackingOnTheRemoteEnd; | /***/ public String errorOccurredDuringUnpackingOnTheRemoteEnd; | ||||
/***/ public String errorReadingInfoRefs; | /***/ public String errorReadingInfoRefs; | ||||
/***/ public String errorSymlinksNotSupported; | /***/ public String errorSymlinksNotSupported; | ||||
/***/ public String exceptionCaughtDuringExecutionOfHook; | |||||
/***/ public String exceptionCaughtDuringExecutionOfAddCommand; | /***/ public String exceptionCaughtDuringExecutionOfAddCommand; | ||||
/***/ public String exceptionCaughtDuringExecutionOfArchiveCommand; | /***/ public String exceptionCaughtDuringExecutionOfArchiveCommand; | ||||
/***/ public String exceptionCaughtDuringExecutionOfCherryPickCommand; | /***/ public String exceptionCaughtDuringExecutionOfCherryPickCommand; | ||||
/***/ public String exceptionCaughtDuringExecutionOfRevertCommand; | /***/ public String exceptionCaughtDuringExecutionOfRevertCommand; | ||||
/***/ public String exceptionCaughtDuringExecutionOfRmCommand; | /***/ public String exceptionCaughtDuringExecutionOfRmCommand; | ||||
/***/ public String exceptionCaughtDuringExecutionOfTagCommand; | /***/ public String exceptionCaughtDuringExecutionOfTagCommand; | ||||
/***/ public String exceptionHookExecutionInterrupted; | |||||
/***/ public String exceptionOccurredDuringAddingOfOptionToALogCommand; | /***/ public String exceptionOccurredDuringAddingOfOptionToALogCommand; | ||||
/***/ public String exceptionOccurredDuringReadingOfGIT_DIR; | /***/ public String exceptionOccurredDuringReadingOfGIT_DIR; | ||||
/***/ public String exceptionWhileReadingPack; | /***/ public String exceptionWhileReadingPack; | ||||
/***/ public String packRefs; | /***/ public String packRefs; | ||||
/***/ public String packSizeNotSetYet; | /***/ public String packSizeNotSetYet; | ||||
/***/ public String packTooLargeForIndexVersion1; | /***/ public String packTooLargeForIndexVersion1; | ||||
/***/ public String packWasDeleted; | |||||
/***/ public String packWriterStatistics; | /***/ public String packWriterStatistics; | ||||
/***/ public String panicCantRenameIndexFile; | /***/ public String panicCantRenameIndexFile; | ||||
/***/ public String patchApplyException; | /***/ public String patchApplyException; |
tmpl = JGitText.get().corruptPack; | tmpl = JGitText.get().corruptPack; | ||||
// Assume the pack is corrupted, and remove it from the list. | // Assume the pack is corrupted, and remove it from the list. | ||||
removePack(p); | removePack(p); | ||||
} else if (e instanceof FileNotFoundException) { | |||||
tmpl = JGitText.get().packWasDeleted; | |||||
removePack(p); | |||||
} else { | } else { | ||||
tmpl = JGitText.get().exceptionWhileReadingPack; | tmpl = JGitText.get().exceptionWhileReadingPack; | ||||
// Don't remove the pack from the list, as the error may be | // Don't remove the pack from the list, as the error may be |
*/ | */ | ||||
public static final String MODULES = "modules"; | 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. | * Create a new digest function for objects. | ||||
* | * |
package org.eclipse.jgit.util; | package org.eclipse.jgit.util; | ||||
import java.io.BufferedReader; | import java.io.BufferedReader; | ||||
import java.io.BufferedWriter; | |||||
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.io.InputStreamReader; | 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.AccessController; | ||||
import java.security.PrivilegedAction; | import java.security.PrivilegedAction; | ||||
import java.text.MessageFormat; | import java.text.MessageFormat; | ||||
import java.util.Arrays; | 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 java.util.concurrent.atomic.AtomicBoolean; | ||||
import org.eclipse.jgit.api.errors.JGitInternalException; | |||||
import org.eclipse.jgit.errors.SymlinksNotSupportedException; | import org.eclipse.jgit.errors.SymlinksNotSupportedException; | ||||
import org.eclipse.jgit.internal.JGitText; | 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. */ | /** Abstraction to support various file system operations not in Java. */ | ||||
public abstract class FS { | public abstract class FS { | ||||
JGitText.get().errorSymlinksNotSupported); | JGitText.get().errorSymlinksNotSupported); | ||||
} | } | ||||
/** | |||||
* 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. | * Initialize a ProcesssBuilder to run a command using the system shell. | ||||
* | * | ||||
public String normalize(String name) { | public String normalize(String name) { | ||||
return 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; | |||||
} | |||||
} | |||||
} | } |
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.PrintStream; | |||||
import java.nio.charset.Charset; | import java.nio.charset.Charset; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.List; | import java.util.List; | ||||
import org.eclipse.jgit.api.errors.JGitInternalException; | |||||
import org.eclipse.jgit.lib.Repository; | |||||
/** | /** | ||||
* Base FS for POSIX based systems | * Base FS for POSIX based systems | ||||
proc.command(argv); | proc.command(argv); | ||||
return proc; | 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); | |||||
} | |||||
} | } |
package org.eclipse.jgit.util; | package org.eclipse.jgit.util; | ||||
import java.io.File; | import java.io.File; | ||||
import java.io.PrintStream; | |||||
import java.security.AccessController; | import java.security.AccessController; | ||||
import java.security.PrivilegedAction; | import java.security.PrivilegedAction; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.List; | import java.util.List; | ||||
import org.eclipse.jgit.api.errors.JGitInternalException; | |||||
import org.eclipse.jgit.lib.Repository; | |||||
/** | /** | ||||
* FS implementation for Cygwin on Windows | * FS implementation for Cygwin on Windows | ||||
proc.command(argv); | proc.command(argv); | ||||
return proc; | 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); | |||||
} | |||||
} | } |
import java.text.MessageFormat; | import java.text.MessageFormat; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.regex.Pattern; | |||||
import org.eclipse.jgit.internal.JGitText; | import org.eclipse.jgit.internal.JGitText; | ||||
} | } | ||||
throw new IOException(JGitText.get().cannotCreateTempDir); | 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(); | |||||
} | |||||
} | } |
/* | |||||
* 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 | |||||
* "<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><old SHA-1> <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; | |||||
} | |||||
} |
/* | |||||
* 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; | |||||
} | |||||
} |