Merge branch 'stable-3.7'
* stable-3.7: Add option --orphan for checkout Prepare post 3.7.0.201502031740-rc1 builds JGit v3.7.0.201502031740-rc1 Support for the pre-commit hook Fix FileUtils.testRelativize_mixedCase which failed on Mac OS X Add a hook test Introduce hook support into the FS implementations If a pack isn't found on disk remove it from pack list Conflicts: org.eclipse.jgit.java7.test/META-INF/MANIFEST.MF Change-Id: I936acd24d47b911fa30ab29856094e1b2c6ac3db Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
commit
21f667edba
|
@ -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.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 File normalize(File file) {
|
|||
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 @@
|
|||
|
||||
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 void createSymLink(File path, String target) throws IOException {
|
|||
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 void testCheckoutWithMissingWorkingTreeFile() throws Exception {
|
|||
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 @@ protected void run() throws Exception {
|
|||
command.setCreateBranch(createBranch);
|
||||
command.setName(name);
|
||||
command.setForce(force);
|
||||
command.setOrphan(orphan);
|
||||
}
|
||||
try {
|
||||
String oldBranch = db.getBranch();
|
||||
|
@ -107,10 +111,9 @@ protected void run() throws Exception {
|
|||
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 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 void testCreateSymlink() throws IOException {
|
|||
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 CherryPickResult call() throws GitAPIException, NoMessageException,
|
|||
.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.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.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 @@ protected CommitCommand(Repository repo) {
|
|||
* 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 RevCommit call() throws GitAPIException, NoHeadException,
|
|||
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 CommitCommand setReflogComment(String reflogComment) {
|
|||
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 @@ private RebaseResult processStep(RebaseTodoLine step, boolean shouldPick)
|
|||
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 @@ private RevCommit squashIntoPrevious(boolean sequenceContainsSquash,
|
|||
}
|
||||
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 static JGitText get() {
|
|||
/***/ 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 static JGitText get() {
|
|||
/***/ 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 static JGitText get() {
|
|||
/***/ 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 static JGitText get() {
|
|||
/***/ 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 @@ private void handlePackError(IOException e, PackFile p) {
|
|||
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 void createSymLink(File path, String target) throws IOException {
|
|||
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 File normalize(File file) {
|
|||
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 @@
|
|||
|
||||
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 ProcessBuilder runInShell(String cmd, String[] args) {
|
|||
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 ProcessBuilder runInShell(String cmd, String[] args) {
|
|||
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.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 static File createTempDir(String prefix, String suffix, File dir)
|
|||
}
|
||||
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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue