* 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
@@ -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", |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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> |
@@ -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. |
@@ -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, |
@@ -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)); | |||
} | |||
} |
@@ -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} |
@@ -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()) |
@@ -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 | |||
@@ -119,11 +125,20 @@ 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; | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; |
@@ -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 |
@@ -386,6 +386,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. | |||
* |
@@ -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 { | |||
@@ -613,6 +626,288 @@ public abstract class FS { | |||
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. | |||
* | |||
@@ -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; | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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 | |||
* "<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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |