Browse Source

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>
stable-4.0
Matthias Sohn 10 years ago
parent
commit
21f667edba
  1. 1
      org.eclipse.jgit.java7.test/META-INF/MANIFEST.MF
  2. 136
      org.eclipse.jgit.java7.test/src/org/eclipse/jgit/util/HookTest.java
  3. 16
      org.eclipse.jgit.java7/src/org/eclipse/jgit/util/FS_POSIX_Java7.java
  4. 18
      org.eclipse.jgit.java7/src/org/eclipse/jgit/util/FS_Win32_Java7Cygwin.java
  5. 12
      org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
  6. 1
      org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
  7. 9
      org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
  8. 68
      org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java
  9. 4
      org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
  10. 3
      org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
  11. 71
      org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
  12. 9
      org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
  13. 61
      org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RejectCommitException.java
  14. 4
      org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
  15. 3
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
  16. 7
      org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
  17. 341
      org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
  18. 14
      org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
  19. 23
      org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
  20. 66
      org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
  21. 149
      org.eclipse.jgit/src/org/eclipse/jgit/util/Hook.java
  22. 112
      org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java

1
org.eclipse.jgit.java7.test/META-INF/MANIFEST.MF

@ -6,6 +6,7 @@ Bundle-Version: 4.0.0.qualifier
Bundle-Vendor: %provider_name Bundle-Vendor: %provider_name
Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Import-Package: org.eclipse.jgit.api;version="[4.0.0,4.1.0)", Import-Package: org.eclipse.jgit.api;version="[4.0.0,4.1.0)",
org.eclipse.jgit.api.errors;version="[4.0.0,4.1.0)",
org.eclipse.jgit.diff;version="[4.0.0,4.1.0)", org.eclipse.jgit.diff;version="[4.0.0,4.1.0)",
org.eclipse.jgit.dircache;version="[4.0.0,4.1.0)", org.eclipse.jgit.dircache;version="[4.0.0,4.1.0)",
org.eclipse.jgit.internal.storage.file;version="4.0.0", org.eclipse.jgit.internal.storage.file;version="4.0.0",

136
org.eclipse.jgit.java7.test/src/org/eclipse/jgit/util/HookTest.java

@ -0,0 +1,136 @@
/*
* Copyright (C) 2014 Matthias Sohn <matthias.sohn@sap.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.RejectCommitException;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.junit.Assume;
import org.junit.Test;
public class HookTest extends RepositoryTestCase {
@Test
public void testFindHook() throws Exception {
assumeSupportedPlatform();
Hook h = Hook.PRE_COMMIT;
assertNull("no hook should be installed", FS.DETECTED.findHook(db, h));
File hookFile = writeHookFile(h.getName(),
"#!/bin/bash\necho \"test $1 $2\"");
assertEquals("exected to find pre-commit hook", hookFile,
FS.DETECTED.findHook(db, h));
}
@Test
public void testRunHook() throws Exception {
assumeSupportedPlatform();
Hook h = Hook.PRE_COMMIT;
writeHookFile(
h.getName(),
"#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\necho 1>&2 \"stderr\"");
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream err = new ByteArrayOutputStream();
ProcessResult res = FS.DETECTED.runIfPresent(db, h, new String[] {
"arg1", "arg2" },
new PrintStream(out), new PrintStream(err), "stdin");
assertEquals("unexpected hook output", "test arg1 arg2\nstdin\n",
out.toString("UTF-8"));
assertEquals("unexpected output on stderr stream", "stderr\n",
err.toString("UTF-8"));
assertEquals("unexpected exit code", 0, res.getExitCode());
assertEquals("unexpected process status", ProcessResult.Status.OK,
res.getStatus());
}
@Test
public void testPreCommitHook() throws Exception {
assumeSupportedPlatform();
Hook h = Hook.PRE_COMMIT;
writeHookFile(h.getName(),
"#!/bin/sh\necho \"test\"\n\necho 1>&2 \"stderr\"\nexit 1");
Git git = Git.wrap(db);
String path = "a.txt";
writeTrashFile(path, "content");
git.add().addFilepattern(path).call();
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
git.commit().setMessage("commit")
.setHookOutputStream(new PrintStream(out)).call();
fail("expected pre-commit hook to abort commit");
} catch (RejectCommitException e) {
assertEquals("unexpected error message from pre-commit hook",
"Commit rejected by \"pre-commit\" hook.\nstderr\n",
e.getMessage());
assertEquals("unexpected output from pre-commit hook", "test\n",
out.toString());
} catch (Throwable e) {
fail("unexpected exception thrown by pre-commit hook: " + e);
}
}
private File writeHookFile(final String name, final String data)
throws IOException {
File path = new File(db.getWorkTree() + "/.git/hooks/", name);
JGitTestUtil.write(path, data);
FS.DETECTED.setExecute(path, true);
return path;
}
private void assumeSupportedPlatform() {
Assume.assumeTrue(FS.DETECTED instanceof FS_POSIX
|| FS.DETECTED instanceof FS_Win32_Java7Cygwin);
}
}

16
org.eclipse.jgit.java7/src/org/eclipse/jgit/util/FS_POSIX_Java7.java

@ -53,6 +53,9 @@ import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermission;
import java.util.Set; import java.util.Set;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
/** /**
* FS implementation for Java7 on unix like systems * FS implementation for Java7 on unix like systems
*/ */
@ -344,4 +347,17 @@ public class FS_POSIX_Java7 extends FS_POSIX {
public String normalize(String name) { public String normalize(String name) {
return FileUtil.normalize(name); return FileUtil.normalize(name);
} }
/**
* @since 3.7
*/
@Override
public File findHook(Repository repository, Hook hook) {
final File gitdir = repository.getDirectory();
final Path hookPath = gitdir.toPath().resolve(Constants.HOOKS)
.resolve(hook.getName());
if (Files.isExecutable(hookPath))
return hookPath.toFile();
return null;
}
} }

18
org.eclipse.jgit.java7/src/org/eclipse/jgit/util/FS_Win32_Java7Cygwin.java

@ -45,6 +45,11 @@ package org.eclipse.jgit.util;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
/** /**
* FS for Java7 on Windows with Cygwin * FS for Java7 on Windows with Cygwin
@ -135,4 +140,17 @@ public class FS_Win32_Java7Cygwin extends FS_Win32_Cygwin {
public Attributes getAttributes(File path) { public Attributes getAttributes(File path) {
return FileUtil.getFileAttributesBasic(this, path); return FileUtil.getFileAttributesBasic(this, path);
} }
/**
* @since 3.7
*/
@Override
public File findHook(Repository repository, Hook hook) {
final File gitdir = repository.getDirectory();
final Path hookPath = gitdir.toPath().resolve(Constants.HOOKS)
.resolve(hook.getName());
if (Files.isExecutable(hookPath))
return hookPath.toFile();
return null;
}
} }

12
org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java

@ -188,6 +188,18 @@ public class CheckoutTest extends CLIRepositoryTestCase {
assertEquals("Hello world a", read(fileA)); assertEquals("Hello world a", read(fileA));
} }
@Test
public void testCheckoutOrphan() throws Exception {
Git git = new Git(db);
git.commit().setMessage("initial commit").call();
assertEquals("Switched to a new branch 'new_branch'",
execute("git checkout --orphan new_branch"));
assertEquals("refs/heads/new_branch", db.getRef("HEAD").getTarget().getName());
RevCommit commit = git.commit().setMessage("orphan commit").call();
assertEquals(0, commit.getParentCount());
}
/** /**
* Steps: * Steps:
* <ol> * <ol>

1
org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties

@ -345,3 +345,4 @@ usage_updateRemoteRefsFromAnotherRepository=Update remote refs from another repo
usage_useNameInsteadOfOriginToTrackUpstream=use <name> instead of 'origin' to track upstream usage_useNameInsteadOfOriginToTrackUpstream=use <name> instead of 'origin' to track upstream
usage_checkoutBranchAfterClone=checkout named branch instead of remotes's HEAD usage_checkoutBranchAfterClone=checkout named branch instead of remotes's HEAD
usage_viewCommitHistory=View commit history usage_viewCommitHistory=View commit history
usage_orphan=Create a new orphan branch. The first commit made on this new branch will have no parents amd it will be the root of a new history totally disconnected from other branches and commits.

9
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java

@ -71,6 +71,9 @@ class Checkout extends TextBuiltin {
@Option(name = "--force", aliases = { "-f" }, usage = "usage_forceCheckout") @Option(name = "--force", aliases = { "-f" }, usage = "usage_forceCheckout")
private boolean force = false; private boolean force = false;
@Option(name = "--orphan", usage = "usage_orphan")
private boolean orphan = false;
@Argument(required = true, index = 0, metaVar = "metaVar_name", usage = "usage_checkout") @Argument(required = true, index = 0, metaVar = "metaVar_name", usage = "usage_checkout")
private String name; private String name;
@ -95,6 +98,7 @@ class Checkout extends TextBuiltin {
command.setCreateBranch(createBranch); command.setCreateBranch(createBranch);
command.setName(name); command.setName(name);
command.setForce(force); command.setForce(force);
command.setOrphan(orphan);
} }
try { try {
String oldBranch = db.getBranch(); String oldBranch = db.getBranch();
@ -107,10 +111,9 @@ class Checkout extends TextBuiltin {
name)); name));
return; return;
} }
if (createBranch) if (createBranch || orphan)
outw.println(MessageFormat.format( outw.println(MessageFormat.format(
CLIText.get().switchedToNewBranch, CLIText.get().switchedToNewBranch, name));
Repository.shortenRefName(ref.getName())));
else else
outw.println(MessageFormat.format( outw.println(MessageFormat.format(
CLIText.get().switchedToBranch, CLIText.get().switchedToBranch,

68
org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java

@ -50,6 +50,7 @@ import static org.junit.Assert.fail;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.regex.Matcher;
import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.JGitTestUtil;
import org.junit.After; import org.junit.After;
@ -434,4 +435,71 @@ public class FileUtilTest {
String target = fs.readSymLink(new File(trash, "x")); String target = fs.readSymLink(new File(trash, "x"));
assertEquals("y", target); assertEquals("y", target);
} }
@Test
public void testRelativize_doc() {
// This is the javadoc example
String base = toOSPathString("c:\\Users\\jdoe\\eclipse\\git\\project");
String other = toOSPathString("c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml");
String expected = toOSPathString("..\\another_project\\pom.xml");
String actual = FileUtils.relativize(base, other);
assertEquals(expected, actual);
}
@Test
public void testRelativize_mixedCase() {
SystemReader systemReader = SystemReader.getInstance();
String base = toOSPathString("C:\\git\\jgit");
String other = toOSPathString("C:\\Git\\test\\d\\f.txt");
String expectedCaseInsensitive = toOSPathString("..\\test\\d\\f.txt");
String expectedCaseSensitive = toOSPathString("..\\..\\Git\\test\\d\\f.txt");
if (systemReader.isWindows()) {
String actual = FileUtils.relativize(base, other);
assertEquals(expectedCaseInsensitive, actual);
} else if (systemReader.isMacOS()) {
String actual = FileUtils.relativize(base, other);
assertEquals(expectedCaseInsensitive, actual);
} else {
String actual = FileUtils.relativize(base, other);
assertEquals(expectedCaseSensitive, actual);
}
}
@Test
public void testRelativize_scheme() {
String base = toOSPathString("file:/home/eclipse/runtime-New_configuration/project_1/file.java");
String other = toOSPathString("file:/home/eclipse/runtime-New_configuration/project");
// 'file.java' is treated as a folder
String expected = toOSPathString("../../project");
String actual = FileUtils.relativize(base, other);
assertEquals(expected, actual);
}
@Test
public void testRelativize_equalPaths() {
String base = toOSPathString("file:/home/eclipse/runtime-New_configuration/project_1");
String other = toOSPathString("file:/home/eclipse/runtime-New_configuration/project_1");
String expected = "";
String actual = FileUtils.relativize(base, other);
assertEquals(expected, actual);
}
@Test
public void testRelativize_whitespaces() {
String base = toOSPathString("/home/eclipse 3.4/runtime New_configuration/project_1");
String other = toOSPathString("/home/eclipse 3.4/runtime New_configuration/project_1/file");
String expected = "file";
String actual = FileUtils.relativize(base, other);
assertEquals(expected, actual);
}
private String toOSPathString(String path) {
return path.replaceAll("/|\\\\",
Matcher.quoteReplacement(File.separator));
}
} }

4
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties

@ -104,6 +104,7 @@ commitAlreadyExists=exists {0}
commitMessageNotSpecified=commit message not specified commitMessageNotSpecified=commit message not specified
commitOnRepoWithoutHEADCurrentlyNotSupported=Commit on repo without HEAD currently not supported commitOnRepoWithoutHEADCurrentlyNotSupported=Commit on repo without HEAD currently not supported
commitAmendOnInitialNotPossible=Amending is not possible on initial commit. commitAmendOnInitialNotPossible=Amending is not possible on initial commit.
commitRejectedByHook=Commit rejected by "{0}" hook.\n{1}
compressingObjects=Compressing objects compressingObjects=Compressing objects
connectionFailed=connection failed connectionFailed=connection failed
connectionTimeOut=Connection time out: {0} connectionTimeOut=Connection time out: {0}
@ -193,6 +194,7 @@ errorListing=Error listing {0}
errorOccurredDuringUnpackingOnTheRemoteEnd=error occurred during unpacking on the remote end: {0} errorOccurredDuringUnpackingOnTheRemoteEnd=error occurred during unpacking on the remote end: {0}
errorReadingInfoRefs=error reading info/refs errorReadingInfoRefs=error reading info/refs
errorSymlinksNotSupported=Symlinks are not supported with this OS/JRE errorSymlinksNotSupported=Symlinks are not supported with this OS/JRE
exceptionCaughtDuringExecutionOfHook=Exception caught during execution of "{0}" hook.
exceptionCaughtDuringExecutionOfAddCommand=Exception caught during execution of add command exceptionCaughtDuringExecutionOfAddCommand=Exception caught during execution of add command
exceptionCaughtDuringExecutionOfArchiveCommand=Exception caught during execution of archive command exceptionCaughtDuringExecutionOfArchiveCommand=Exception caught during execution of archive command
exceptionCaughtDuringExecutionOfCherryPickCommand=Exception caught during execution of cherry-pick command. {0} exceptionCaughtDuringExecutionOfCherryPickCommand=Exception caught during execution of cherry-pick command. {0}
@ -206,6 +208,7 @@ exceptionCaughtDuringExecutionOfResetCommand=Exception caught during execution o
exceptionCaughtDuringExecutionOfRevertCommand=Exception caught during execution of revert command. {0} exceptionCaughtDuringExecutionOfRevertCommand=Exception caught during execution of revert command. {0}
exceptionCaughtDuringExecutionOfRmCommand=Exception caught during execution of rm command exceptionCaughtDuringExecutionOfRmCommand=Exception caught during execution of rm command
exceptionCaughtDuringExecutionOfTagCommand=Exception caught during execution of tag command exceptionCaughtDuringExecutionOfTagCommand=Exception caught during execution of tag command
exceptionHookExecutionInterrupted=Execution of "{0}" hook interrupted.
exceptionOccurredDuringAddingOfOptionToALogCommand=Exception occurred during adding of {0} as option to a Log command exceptionOccurredDuringAddingOfOptionToALogCommand=Exception occurred during adding of {0} as option to a Log command
exceptionOccurredDuringReadingOfGIT_DIR=Exception occurred during reading of $GIT_DIR/{0}. {1} exceptionOccurredDuringReadingOfGIT_DIR=Exception occurred during reading of $GIT_DIR/{0}. {1}
exceptionWhileReadingPack=ERROR: Exception caught while accessing pack file {0}, the pack file might be corrupt exceptionWhileReadingPack=ERROR: Exception caught while accessing pack file {0}, the pack file might be corrupt
@ -392,6 +395,7 @@ packObjectCountMismatch=Pack object count mismatch: pack {0} index {1}: {2}
packRefs=Pack refs packRefs=Pack refs
packSizeNotSetYet=Pack size not yet set since it has not yet been received packSizeNotSetYet=Pack size not yet set since it has not yet been received
packTooLargeForIndexVersion1=Pack too large for index version 1 packTooLargeForIndexVersion1=Pack too large for index version 1
packWasDeleted=Pack file {0} was deleted, removing it from pack list
packWriterStatistics=Total {0,number,#0} (delta {1,number,#0}), reused {2,number,#0} (delta {3,number,#0}) packWriterStatistics=Total {0,number,#0} (delta {1,number,#0}), reused {2,number,#0} (delta {3,number,#0})
panicCantRenameIndexFile=Panic: index file {0} must be renamed to replace {1}; until then repository is corrupt panicCantRenameIndexFile=Panic: index file {0} must be renamed to replace {1}; until then repository is corrupt
patchApplyException=Cannot apply: {0} patchApplyException=Cannot apply: {0}

3
org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java

@ -169,7 +169,8 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> {
.setMessage(srcCommit.getFullMessage()) .setMessage(srcCommit.getFullMessage())
.setReflogComment(reflogPrefix + " " //$NON-NLS-1$ .setReflogComment(reflogPrefix + " " //$NON-NLS-1$
+ srcCommit.getShortMessage()) + srcCommit.getShortMessage())
.setAuthor(srcCommit.getAuthorIdent()).call(); .setAuthor(srcCommit.getAuthorIdent())
.setNoVerify(true).call();
cherryPickedRefs.add(src); cherryPickedRefs.add(src);
} else { } else {
if (merger.failed()) if (merger.failed())

71
org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java

@ -42,8 +42,10 @@
*/ */
package org.eclipse.jgit.api; package org.eclipse.jgit.api;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.PrintStream;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -56,6 +58,7 @@ import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.NoFilepatternException; import org.eclipse.jgit.api.errors.NoFilepatternException;
import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.NoMessageException; import org.eclipse.jgit.api.errors.NoMessageException;
import org.eclipse.jgit.api.errors.RejectCommitException;
import org.eclipse.jgit.api.errors.UnmergedPathsException; import org.eclipse.jgit.api.errors.UnmergedPathsException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCache;
@ -84,6 +87,9 @@ import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.ChangeIdUtil; import org.eclipse.jgit.util.ChangeIdUtil;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.Hook;
import org.eclipse.jgit.util.ProcessResult;
/** /**
* A class used to execute a {@code Commit} command. It has setters for all * A class used to execute a {@code Commit} command. It has setters for all
@ -119,11 +125,20 @@ public class CommitCommand extends GitCommand<RevCommit> {
private String reflogComment; private String reflogComment;
/**
* Setting this option bypasses the {@link Hook#PRE_COMMIT pre-commit} and
* {@link Hook#COMMIT_MSG commit-msg} hooks.
*/
private boolean noVerify;
private PrintStream hookOutRedirect;
/** /**
* @param repo * @param repo
*/ */
protected CommitCommand(Repository repo) { protected CommitCommand(Repository repo) {
super(repo); super(repo);
hookOutRedirect = System.out;
} }
/** /**
@ -144,11 +159,14 @@ public class CommitCommand extends GitCommand<RevCommit> {
* else * else
* @throws WrongRepositoryStateException * @throws WrongRepositoryStateException
* when repository is not in the right state for committing * when repository is not in the right state for committing
* @throws RejectCommitException
* if there are either pre-commit or commit-msg hooks present in
* the repository and at least one of them rejects the commit.
*/ */
public RevCommit call() throws GitAPIException, NoHeadException, public RevCommit call() throws GitAPIException, NoHeadException,
NoMessageException, UnmergedPathsException, NoMessageException, UnmergedPathsException,
ConcurrentRefUpdateException, ConcurrentRefUpdateException, WrongRepositoryStateException,
WrongRepositoryStateException { RejectCommitException {
checkCallable(); checkCallable();
Collections.sort(only); Collections.sort(only);
@ -160,6 +178,23 @@ public class CommitCommand extends GitCommand<RevCommit> {
throw new WrongRepositoryStateException(MessageFormat.format( throw new WrongRepositoryStateException(MessageFormat.format(
JGitText.get().cannotCommitOnARepoWithState, JGitText.get().cannotCommitOnARepoWithState,
state.name())); state.name()));
if (!noVerify) {
final ByteArrayOutputStream errorByteArray = new ByteArrayOutputStream();
final PrintStream hookErrRedirect = new PrintStream(
errorByteArray);
ProcessResult preCommitHookResult = FS.DETECTED.runIfPresent(
repo, Hook.PRE_COMMIT, new String[0], hookOutRedirect,
hookErrRedirect, null);
if (preCommitHookResult.getStatus() == ProcessResult.Status.OK
&& preCommitHookResult.getExitCode() != 0) {
String errorMessage = MessageFormat.format(
JGitText.get().commitRejectedByHook, Hook.PRE_COMMIT.getName(),
errorByteArray.toString());
throw new RejectCommitException(errorMessage);
}
}
processOptions(state, rw); processOptions(state, rw);
if (all && !repo.isBare() && repo.getWorkTree() != null) { if (all && !repo.isBare() && repo.getWorkTree() != null) {
@ -733,4 +768,36 @@ public class CommitCommand extends GitCommand<RevCommit> {
return this; return this;
} }
/**
* Sets the {@link #noVerify} option on this commit command.
* <p>
* Both the {@link Hook#PRE_COMMIT pre-commit} and {@link Hook#COMMIT_MSG
* commit-msg} hooks can block a commit by their return value; setting this
* option to <code>true</code> will bypass these two hooks.
* </p>
*
* @param noVerify
* Whether this commit should be verified by the pre-commit and
* commit-msg hooks.
* @return {@code this}
* @since 3.7
*/
public CommitCommand setNoVerify(boolean noVerify) {
this.noVerify = noVerify;
return this;
}
/**
* Set the output stream for hook scripts executed by this command. If not
* set it defaults to {@code System.out}.
*
* @param hookStdOut
* the output stream for hook scripts executed by this command
* @return {@code this}
* @since 3.7
*/
public CommitCommand setHookOutputStream(PrintStream hookStdOut) {
this.hookOutRedirect = hookStdOut;
return this;
}
} }

9
org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java

@ -462,7 +462,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
String newMessage = interactiveHandler String newMessage = interactiveHandler
.modifyCommitMessage(oldMessage); .modifyCommitMessage(oldMessage);
newHead = new Git(repo).commit().setMessage(newMessage) newHead = new Git(repo).commit().setMessage(newMessage)
.setAmend(true).call(); .setAmend(true).setNoVerify(true).call();
return null; return null;
case EDIT: case EDIT:
rebaseState.createFile(AMEND, commitToPick.name()); rebaseState.createFile(AMEND, commitToPick.name());
@ -768,15 +768,14 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
} }
retNewHead = new Git(repo).commit() retNewHead = new Git(repo).commit()
.setMessage(stripCommentLines(commitMessage)) .setMessage(stripCommentLines(commitMessage))
.setAmend(true).call(); .setAmend(true).setNoVerify(true).call();
rebaseState.getFile(MESSAGE_SQUASH).delete(); rebaseState.getFile(MESSAGE_SQUASH).delete();
rebaseState.getFile(MESSAGE_FIXUP).delete(); rebaseState.getFile(MESSAGE_FIXUP).delete();
} else { } else {
// Next step is either Squash or Fixup // Next step is either Squash or Fixup
retNewHead = new Git(repo).commit() retNewHead = new Git(repo).commit().setMessage(commitMessage)
.setMessage(commitMessage).setAmend(true) .setAmend(true).setNoVerify(true).call();
.call();
} }
return retNewHead; return retNewHead;
} }

61
org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RejectCommitException.java

@ -0,0 +1,61 @@
/*
* Copyright (C) 2015 Obeo.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.api.errors;
/**
* Exception thrown when a commit is rejected by a hook (either
* {@link org.eclipse.jgit.util.Hook#PRE_COMMIT pre-commit} or
* {@link org.eclipse.jgit.util.Hook#COMMIT_MSG commit-msg}).
*
* @since 3.7
*/
public class RejectCommitException extends GitAPIException {
private static final long serialVersionUID = 1L;
/**
* @param message
*/
public RejectCommitException(String message) {
super(message);
}
}

4
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java

@ -163,6 +163,7 @@ public class JGitText extends TranslationBundle {
/***/ public String commitMessageNotSpecified; /***/ public String commitMessageNotSpecified;
/***/ public String commitOnRepoWithoutHEADCurrentlyNotSupported; /***/ public String commitOnRepoWithoutHEADCurrentlyNotSupported;
/***/ public String commitAmendOnInitialNotPossible; /***/ public String commitAmendOnInitialNotPossible;
/***/ public String commitRejectedByHook;
/***/ public String compressingObjects; /***/ public String compressingObjects;
/***/ public String connectionFailed; /***/ public String connectionFailed;
/***/ public String connectionTimeOut; /***/ public String connectionTimeOut;
@ -252,6 +253,7 @@ public class JGitText extends TranslationBundle {
/***/ public String errorOccurredDuringUnpackingOnTheRemoteEnd; /***/ public String errorOccurredDuringUnpackingOnTheRemoteEnd;
/***/ public String errorReadingInfoRefs; /***/ public String errorReadingInfoRefs;
/***/ public String errorSymlinksNotSupported; /***/ public String errorSymlinksNotSupported;
/***/ public String exceptionCaughtDuringExecutionOfHook;
/***/ public String exceptionCaughtDuringExecutionOfAddCommand; /***/ public String exceptionCaughtDuringExecutionOfAddCommand;
/***/ public String exceptionCaughtDuringExecutionOfArchiveCommand; /***/ public String exceptionCaughtDuringExecutionOfArchiveCommand;
/***/ public String exceptionCaughtDuringExecutionOfCherryPickCommand; /***/ public String exceptionCaughtDuringExecutionOfCherryPickCommand;
@ -265,6 +267,7 @@ public class JGitText extends TranslationBundle {
/***/ public String exceptionCaughtDuringExecutionOfRevertCommand; /***/ public String exceptionCaughtDuringExecutionOfRevertCommand;
/***/ public String exceptionCaughtDuringExecutionOfRmCommand; /***/ public String exceptionCaughtDuringExecutionOfRmCommand;
/***/ public String exceptionCaughtDuringExecutionOfTagCommand; /***/ public String exceptionCaughtDuringExecutionOfTagCommand;
/***/ public String exceptionHookExecutionInterrupted;
/***/ public String exceptionOccurredDuringAddingOfOptionToALogCommand; /***/ public String exceptionOccurredDuringAddingOfOptionToALogCommand;
/***/ public String exceptionOccurredDuringReadingOfGIT_DIR; /***/ public String exceptionOccurredDuringReadingOfGIT_DIR;
/***/ public String exceptionWhileReadingPack; /***/ public String exceptionWhileReadingPack;
@ -451,6 +454,7 @@ public class JGitText extends TranslationBundle {
/***/ public String packRefs; /***/ public String packRefs;
/***/ public String packSizeNotSetYet; /***/ public String packSizeNotSetYet;
/***/ public String packTooLargeForIndexVersion1; /***/ public String packTooLargeForIndexVersion1;
/***/ public String packWasDeleted;
/***/ public String packWriterStatistics; /***/ public String packWriterStatistics;
/***/ public String panicCantRenameIndexFile; /***/ public String panicCantRenameIndexFile;
/***/ public String patchApplyException; /***/ public String patchApplyException;

3
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java

@ -557,6 +557,9 @@ public class ObjectDirectory extends FileObjectDatabase {
tmpl = JGitText.get().corruptPack; tmpl = JGitText.get().corruptPack;
// Assume the pack is corrupted, and remove it from the list. // Assume the pack is corrupted, and remove it from the list.
removePack(p); removePack(p);
} else if (e instanceof FileNotFoundException) {
tmpl = JGitText.get().packWasDeleted;
removePack(p);
} else { } else {
tmpl = JGitText.get().exceptionWhileReadingPack; tmpl = JGitText.get().exceptionWhileReadingPack;
// Don't remove the pack from the list, as the error may be // Don't remove the pack from the list, as the error may be

7
org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java

@ -386,6 +386,13 @@ public final class Constants {
*/ */
public static final String MODULES = "modules"; public static final String MODULES = "modules";
/**
* Name of the folder (inside gitDir) where the hooks are stored.
*
* @since 3.7
*/
public static final String HOOKS = "hooks";
/** /**
* Create a new digest function for objects. * Create a new digest function for objects.
* *

341
org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java

@ -44,18 +44,31 @@
package org.eclipse.jgit.util; package org.eclipse.jgit.util;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.SymlinksNotSupportedException; import org.eclipse.jgit.errors.SymlinksNotSupportedException;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.ProcessResult.Status;
/** Abstraction to support various file system operations not in Java. */ /** Abstraction to support various file system operations not in Java. */
public abstract class FS { public abstract class FS {
@ -613,6 +626,288 @@ public abstract class FS {
JGitText.get().errorSymlinksNotSupported); JGitText.get().errorSymlinksNotSupported);
} }
/**
* See {@link FileUtils#relativize(String, String)}.
*
* @param base
* The path against which <code>other</code> should be
* relativized.
* @param other
* The path that will be made relative to <code>base</code>.
* @return A relative path that, when resolved against <code>base</code>,
* will yield the original <code>other</code>.
* @see FileUtils#relativize(String, String)
* @since 3.7
*/
public String relativize(String base, String other) {
return FileUtils.relativize(base, other);
}
/**
* Checks whether the given hook is defined for the given repository, then
* runs it with the given arguments.
* <p>
* The hook's standard output and error streams will be redirected to
* <code>System.out</code> and <code>System.err</code> respectively. The
* hook will have no stdin.
* </p>
*
* @param repository
* The repository for which a hook should be run.
* @param hook
* The hook to be executed.
* @param args
* Arguments to pass to this hook. Cannot be <code>null</code>,
* but can be an empty array.
* @return The ProcessResult describing this hook's execution.
* @throws JGitInternalException
* if we fail to run the hook somehow. Causes may include an
* interrupted process or I/O errors.
* @since 3.7
*/
public ProcessResult runIfPresent(Repository repository, final Hook hook,
String[] args) throws JGitInternalException {
return runIfPresent(repository, hook, args, System.out, System.err,
null);
}
/**
* Checks whether the given hook is defined for the given repository, then
* runs it with the given arguments.
*
* @param repository
* The repository for which a hook should be run.
* @param hook
* The hook to be executed.
* @param args
* Arguments to pass to this hook. Cannot be <code>null</code>,
* but can be an empty array.
* @param outRedirect
* A print stream on which to redirect the hook's stdout. Can be
* <code>null</code>, in which case the hook's standard output
* will be lost.
* @param errRedirect
* A print stream on which to redirect the hook's stderr. Can be
* <code>null</code>, in which case the hook's standard error
* will be lost.
* @param stdinArgs
* A string to pass on to the standard input of the hook. May be
* <code>null</code>.
* @return The ProcessResult describing this hook's execution.
* @throws JGitInternalException
* if we fail to run the hook somehow. Causes may include an
* interrupted process or I/O errors.
* @since 3.7
*/
public ProcessResult runIfPresent(Repository repository, final Hook hook,
String[] args, PrintStream outRedirect, PrintStream errRedirect,
String stdinArgs) throws JGitInternalException {
return new ProcessResult(Status.NOT_SUPPORTED);
}
/**
* See
* {@link #runIfPresent(Repository, Hook, String[], PrintStream, PrintStream, String)}
* . Should only be called by FS supporting shell scripts execution.
*
* @param repository
* The repository for which a hook should be run.
* @param hook
* The hook to be executed.
* @param args
* Arguments to pass to this hook. Cannot be <code>null</code>,
* but can be an empty array.
* @param outRedirect
* A print stream on which to redirect the hook's stdout. Can be
* <code>null</code>, in which case the hook's standard output
* will be lost.
* @param errRedirect
* A print stream on which to redirect the hook's stderr. Can be
* <code>null</code>, in which case the hook's standard error
* will be lost.
* @param stdinArgs
* A string to pass on to the standard input of the hook. May be
* <code>null</code>.
* @return The ProcessResult describing this hook's execution.
* @throws JGitInternalException
* if we fail to run the hook somehow. Causes may include an
* interrupted process or I/O errors.
* @since 3.7
*/
protected ProcessResult internalRunIfPresent(Repository repository,
final Hook hook, String[] args, PrintStream outRedirect,
PrintStream errRedirect, String stdinArgs)
throws JGitInternalException {
final File hookFile = findHook(repository, hook);
if (hookFile == null)
return new ProcessResult(Status.NOT_PRESENT);
final String hookPath = hookFile.getAbsolutePath();
final File runDirectory;
if (repository.isBare())
runDirectory = repository.getDirectory();
else
runDirectory = repository.getWorkTree();
final String cmd = relativize(runDirectory.getAbsolutePath(),
hookPath);
ProcessBuilder hookProcess = runInShell(cmd, args);
hookProcess.directory(runDirectory);
try {
return new ProcessResult(runProcess(hookProcess, outRedirect,
errRedirect, stdinArgs), Status.OK);
} catch (IOException e) {
throw new JGitInternalException(MessageFormat.format(
JGitText.get().exceptionCaughtDuringExecutionOfHook,
hook.getName()), e);
} catch (InterruptedException e) {
throw new JGitInternalException(MessageFormat.format(
JGitText.get().exceptionHookExecutionInterrupted,
hook.getName()), e);
}
}
/**
* Tries to find a hook matching the given one in the given repository.
*
* @param repository
* The repository within which to find a hook.
* @param hook
* The hook we're trying to find.
* @return The {@link File} containing this particular hook if it exists in
* the given repository, <code>null</code> otherwise.
* @since 3.7
*/
public File findHook(Repository repository, final Hook hook) {
final File hookFile = new File(new File(repository.getDirectory(),
Constants.HOOKS), hook.getName());
return hookFile.isFile() ? hookFile : null;
}
/**
* Runs the given process until termination, clearing its stdout and stderr
* streams on-the-fly.
*
* @param hookProcessBuilder
* The process builder configured for this hook.
* @param outRedirect
* A print stream on which to redirect the hook's stdout. Can be
* <code>null</code>, in which case the hook's standard output
* will be lost.
* @param errRedirect
* A print stream on which to redirect the hook's stderr. Can be
* <code>null</code>, in which case the hook's standard error
* will be lost.
* @param stdinArgs
* A string to pass on to the standard input of the hook. Can be
* <code>null</code>.
* @return the exit value of this hook.
* @throws IOException
* if an I/O error occurs while executing this hook.
* @throws InterruptedException
* if the current thread is interrupted while waiting for the
* process to end.
* @since 3.7
*/
protected int runProcess(ProcessBuilder hookProcessBuilder,
OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
throws IOException, InterruptedException {
final ExecutorService executor = Executors.newFixedThreadPool(2);
Process process = null;
// We'll record the first I/O exception that occurs, but keep on trying
// to dispose of our open streams and file handles
IOException ioException = null;
try {
process = hookProcessBuilder.start();
final Callable<Void> errorGobbler = new StreamGobbler(
process.getErrorStream(), errRedirect);
final Callable<Void> outputGobbler = new StreamGobbler(
process.getInputStream(), outRedirect);
executor.submit(errorGobbler);
executor.submit(outputGobbler);
if (stdinArgs != null) {
final PrintWriter stdinWriter = new PrintWriter(
process.getOutputStream());
stdinWriter.print(stdinArgs);
stdinWriter.flush();
// We are done with this hook's input. Explicitly close its
// stdin now to kick off any blocking read the hook might have.
stdinWriter.close();
}
return process.waitFor();
} catch (IOException e) {
ioException = e;
} finally {
shutdownAndAwaitTermination(executor);
if (process != null) {
try {
process.waitFor();
} catch (InterruptedException e) {
// Thrown by the outer try.
// Swallow this one to carry on our cleanup, and clear the
// interrupted flag (processes throw the exception without
// clearing the flag).
Thread.interrupted();
}
// A process doesn't clean its own resources even when destroyed
// Explicitly try and close all three streams, preserving the
// outer I/O exception if any.
try {
process.getErrorStream().close();
} catch (IOException e) {
ioException = ioException != null ? ioException : e;
}
try {
process.getInputStream().close();
} catch (IOException e) {
ioException = ioException != null ? ioException : e;
}
try {
process.getOutputStream().close();
} catch (IOException e) {
ioException = ioException != null ? ioException : e;
}
process.destroy();
}
}
// We can only be here if the outer try threw an IOException.
throw ioException;
}
/**
* Shuts down an {@link ExecutorService} in two phases, first by calling
* {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and
* then calling {@link ExecutorService#shutdownNow() shutdownNow}, if
* necessary, to cancel any lingering tasks. Returns true if the pool has
* been properly shutdown, false otherwise.
* <p>
*
* @param pool
* the pool to shutdown
* @return <code>true</code> if the pool has been properly shutdown,
* <code>false</code> otherwise.
*/
private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
boolean hasShutdown = true;
pool.shutdown(); // Disable new tasks from being submitted
try {
// Wait a while for existing tasks to terminate
if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
pool.shutdownNow(); // Cancel currently executing tasks
// Wait a while for tasks to respond to being canceled
if (!pool.awaitTermination(5, TimeUnit.SECONDS))
hasShutdown = false;
}
} catch (InterruptedException ie) {
// (Re-)Cancel if current thread also interrupted
pool.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
hasShutdown = false;
}
return hasShutdown;
}
/** /**
* Initialize a ProcesssBuilder to run a command using the system shell. * Initialize a ProcesssBuilder to run a command using the system shell.
* *
@ -802,4 +1097,50 @@ public abstract class FS {
public String normalize(String name) { public String normalize(String name) {
return name; return name;
} }
/**
* This runnable will consume an input stream's content into an output
* stream as soon as it gets available.
* <p>
* Typically used to empty processes' standard output and error, preventing
* them to choke.
* </p>
* <p>
* <b>Note</b> that a {@link StreamGobbler} will never close either of its
* streams.
* </p>
*/
private static class StreamGobbler implements Callable<Void> {
private final BufferedReader reader;
private final BufferedWriter writer;
public StreamGobbler(InputStream stream, OutputStream output) {
this.reader = new BufferedReader(new InputStreamReader(stream));
if (output == null)
this.writer = null;
else
this.writer = new BufferedWriter(new OutputStreamWriter(output));
}
public Void call() throws IOException {
boolean writeFailure = false;
String line = null;
while ((line = reader.readLine()) != null) {
// Do not try to write again after a failure, but keep reading
// as long as possible to prevent the input stream from choking.
if (!writeFailure && writer != null) {
try {
writer.write(line);
writer.newLine();
writer.flush();
} catch (IOException e) {
writeFailure = true;
}
}
}
return null;
}
}
} }

14
org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java

@ -44,11 +44,14 @@ package org.eclipse.jgit.util;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.lib.Repository;
/** /**
* Base FS for POSIX based systems * Base FS for POSIX based systems
@ -121,4 +124,15 @@ public abstract class FS_POSIX extends FS {
proc.command(argv); proc.command(argv);
return proc; return proc;
} }
/**
* @since 3.7
*/
@Override
public ProcessResult runIfPresent(Repository repository, Hook hook,
String[] args, PrintStream outRedirect, PrintStream errRedirect,
String stdinArgs) throws JGitInternalException {
return internalRunIfPresent(repository, hook, args, outRedirect,
errRedirect, stdinArgs);
}
} }

23
org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java

@ -44,12 +44,15 @@
package org.eclipse.jgit.util; package org.eclipse.jgit.util;
import java.io.File; import java.io.File;
import java.io.PrintStream;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.lib.Repository;
/** /**
* FS implementation for Cygwin on Windows * FS implementation for Cygwin on Windows
@ -135,4 +138,24 @@ public class FS_Win32_Cygwin extends FS_Win32 {
proc.command(argv); proc.command(argv);
return proc; return proc;
} }
/**
* @since 3.7
*/
@Override
public String relativize(String base, String other) {
final String relativized = super.relativize(base, other);
return relativized.replace(File.separatorChar, '/');
}
/**
* @since 3.7
*/
@Override
public ProcessResult runIfPresent(Repository repository, Hook hook,
String[] args, PrintStream outRedirect, PrintStream errRedirect,
String stdinArgs) throws JGitInternalException {
return internalRunIfPresent(repository, hook, args, outRedirect,
errRedirect, stdinArgs);
}
} }

66
org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java

@ -51,6 +51,7 @@ import java.nio.channels.FileLock;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.regex.Pattern;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
@ -387,4 +388,69 @@ public class FileUtils {
} }
throw new IOException(JGitText.get().cannotCreateTempDir); throw new IOException(JGitText.get().cannotCreateTempDir);
} }
/**
* This will try and make a given path relative to another.
* <p>
* For example, if this is called with the two following paths :
*
* <pre>
* <code>base = "c:\\Users\\jdoe\\eclipse\\git\\project"</code>
* <code>other = "c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml"</code>
* </pre>
*
* This will return "..\\another_project\\pom.xml".
* </p>
* <p>
* This method uses {@link File#separator} to split the paths into segments.
* </p>
* <p>
* <b>Note</b> that this will return the empty String if <code>base</code>
* and <code>other</code> are equal.
* </p>
*
* @param base
* The path against which <code>other</code> should be
* relativized. This will be assumed to denote the path to a
* folder and not a file.
* @param other
* The path that will be made relative to <code>base</code>.
* @return A relative path that, when resolved against <code>base</code>,
* will yield the original <code>other</code>.
* @since 3.7
*/
public static String relativize(String base, String other) {
if (base.equals(other))
return ""; //$NON-NLS-1$
final boolean ignoreCase = !FS.DETECTED.isCaseSensitive();
final String[] baseSegments = base.split(Pattern.quote(File.separator));
final String[] otherSegments = other.split(Pattern
.quote(File.separator));
int commonPrefix = 0;
while (commonPrefix < baseSegments.length
&& commonPrefix < otherSegments.length) {
if (ignoreCase
&& baseSegments[commonPrefix]
.equalsIgnoreCase(otherSegments[commonPrefix]))
commonPrefix++;
else if (!ignoreCase
&& baseSegments[commonPrefix]
.equals(otherSegments[commonPrefix]))
commonPrefix++;
else
break;
}
final StringBuilder builder = new StringBuilder();
for (int i = commonPrefix; i < baseSegments.length; i++)
builder.append("..").append(File.separator); //$NON-NLS-1$
for (int i = commonPrefix; i < otherSegments.length; i++) {
builder.append(otherSegments[i]);
if (i < otherSegments.length - 1)
builder.append(File.separator);
}
return builder.toString();
}
} }

149
org.eclipse.jgit/src/org/eclipse/jgit/util/Hook.java

@ -0,0 +1,149 @@
/*
* Copyright (C) 2014 Obeo.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.util;
/**
* An enum describing the different hooks a user can implement to customize his
* repositories.
*
* @since 3.7
*/
public enum Hook {
/**
* Literal for the "pre-commit" git hook.
* <p>
* This hook is invoked by git commit, and can be bypassed with the
* "no-verify" option. It takes no parameter, and is invoked before
* obtaining the proposed commit log message and making a commit.
* </p>
* <p>
* A non-zero exit code from the called hook means that the commit should be
* aborted.
* </p>
*/
PRE_COMMIT("pre-commit"), //$NON-NLS-1$
/**
* Literal for the "prepare-commit-msg" git hook.
* <p>
* This hook is invoked by git commit right after preparing the default
* message, and before any editing possibility is displayed to the user.
* </p>
* <p>
* A non-zero exit code from the called hook means that the commit should be
* aborted.
* </p>
*/
PREPARE_COMMIT_MSG("prepare-commit-msg"), //$NON-NLS-1$
/**
* Literal for the "commit-msg" git hook.
* <p>
* This hook is invoked by git commit, and can be bypassed with the
* "no-verify" option. Its single parameter is the path to the file
* containing the prepared commit message (typically
* "&lt;gitdir>/COMMIT-EDITMSG").
* </p>
* <p>
* A non-zero exit code from the called hook means that the commit should be
* aborted.
* </p>
*/
COMMIT_MSG("commit-msg"), //$NON-NLS-1$
/**
* Literal for the "post-commit" git hook.
* <p>
* This hook is invoked by git commit. It takes no parameter and is invoked
* after a commit has been made.
* </p>
* <p>
* The exit code of this hook has no significance.
* </p>
*/
POST_COMMIT("post-commit"), //$NON-NLS-1$
/**
* Literal for the "post-rewrite" git hook.
* <p>
* This hook is invoked after commands that rewrite commits (currently, only
* "git rebase" and "git commit --amend"). It a single argument denoting the
* source of the call (one of <code>rebase</code> or <code>amend</code>). It
* then accepts a list of rewritten commits through stdin, in the form
* <code>&lt;old SHA-1> &lt;new SHA-1>LF</code>.
* </p>
* <p>
* The exit code of this hook has no significance.
* </p>
*/
POST_REWRITE("post-rewrite"), //$NON-NLS-1$
/**
* Literal for the "pre-rebase" git hook.
* <p>
* </p>
* This hook is invoked right before the rebase operation runs. It accepts
* up to two parameters, the first being the upstream from which the branch
* to rebase has been forked. If the tip of the series of commits to rebase
* is HEAD, the other parameter is unset. Otherwise, that tip is passed as
* the second parameter of the script.
* <p>
* A non-zero exit code from the called hook means that the rebase should be
* aborted.
* </p>
*/
PRE_REBASE("pre-rebase"); //$NON-NLS-1$
private final String name;
private Hook(String name) {
this.name = name;
}
/**
* @return The name of this hook.
*/
public String getName() {
return name;
}
}

112
org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java

@ -0,0 +1,112 @@
/*
* Copyright (C) 2014 Obeo.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.util;
/**
* Describes the result of running an external process.
*
* @since 3.7
*/
public class ProcessResult {
/**
* Status of a process' execution.
*/
public static enum Status {
/**
* The script was found and launched properly. It may still have exited
* with a non-zero {@link #exitCode}.
*/
OK,
/** The script was not found on disk and thus could not be launched. */
NOT_PRESENT,
/**
* The script was found but could not be launched since it was not
* supported by the current {@link FS}.
*/
NOT_SUPPORTED;
}
/** The exit code of the process. */
private final int exitCode;
/** Status of the process' execution. */
private final Status status;
/**
* Instantiates a process result with the given status and an exit code of
* <code>-1</code>.
*
* @param status
* Status describing the execution of the external process.
*/
public ProcessResult(Status status) {
this(-1, status);
}
/**
* @param exitCode
* Exit code of the process.
* @param status
* Status describing the execution of the external process.
*/
public ProcessResult(int exitCode, Status status) {
this.exitCode = exitCode;
this.status = status;
}
/**
* @return The exit code of the process.
*/
public int getExitCode() {
return exitCode;
}
/**
* @return The status of the process' execution.
*/
public Status getStatus() {
return status;
}
}
Loading…
Cancel
Save