* stable-5.6: FS.runInShell(): handle quoted filters and hooksPath containing blanks Handle non-normalized index also for executable files Change-Id: I240377e87c073ee7a621a88e39fc319c59fa037a Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>tags/v5.8.0.202005061305-m2
@@ -16,6 +16,7 @@ import static org.junit.Assert.assertNull; | |||
import static org.junit.Assert.assertSame; | |||
import static org.junit.Assert.assertTrue; | |||
import static org.junit.Assert.fail; | |||
import static org.junit.Assume.assumeTrue; | |||
import java.io.File; | |||
import java.util.Date; | |||
@@ -600,40 +601,63 @@ public class CommitCommandTest extends RepositoryTestCase { | |||
} | |||
} | |||
@Test | |||
public void commitWithAutoCrlfAndNonNormalizedIndex() throws Exception { | |||
private void nonNormalizedIndexTest(boolean executable) throws Exception { | |||
String mode = executable ? "100755" : "100644"; | |||
try (Git git = new Git(db)) { | |||
// Commit a file with CR/LF into the index | |||
FileBasedConfig config = db.getConfig(); | |||
config.setString("core", null, "autocrlf", "false"); | |||
config.save(); | |||
writeTrashFile("file.txt", "line 1\r\nline 2\r\n"); | |||
File testFile = writeTrashFile("file.txt", "line 1\r\nline 2\r\n"); | |||
if (executable) { | |||
FS.DETECTED.setExecute(testFile, true); | |||
} | |||
git.add().addFilepattern("file.txt").call(); | |||
git.commit().setMessage("Initial").call(); | |||
assertEquals( | |||
"[file.txt, mode:100644, content:line 1\r\nline 2\r\n]", | |||
"[file.txt, mode:" + mode | |||
+ ", content:line 1\r\nline 2\r\n]", | |||
indexState(CONTENT)); | |||
config.setString("core", null, "autocrlf", "true"); | |||
config.save(); | |||
writeTrashFile("file.txt", "line 1\r\nline 1.5\r\nline 2\r\n"); | |||
writeTrashFile("file2.txt", "new\r\nfile\r\n"); | |||
testFile = writeTrashFile("file2.txt", "new\r\nfile\r\n"); | |||
if (executable) { | |||
FS.DETECTED.setExecute(testFile, true); | |||
} | |||
git.add().addFilepattern("file.txt").addFilepattern("file2.txt") | |||
.call(); | |||
git.commit().setMessage("Second").call(); | |||
assertEquals( | |||
"[file.txt, mode:100644, content:line 1\r\nline 1.5\r\nline 2\r\n]" | |||
+ "[file2.txt, mode:100644, content:new\nfile\n]", | |||
"[file.txt, mode:" + mode | |||
+ ", content:line 1\r\nline 1.5\r\nline 2\r\n]" | |||
+ "[file2.txt, mode:" + mode | |||
+ ", content:new\nfile\n]", | |||
indexState(CONTENT)); | |||
writeTrashFile("file2.txt", "new\r\nfile\r\ncontent\r\n"); | |||
git.add().addFilepattern("file2.txt").call(); | |||
git.commit().setMessage("Third").call(); | |||
assertEquals( | |||
"[file.txt, mode:100644, content:line 1\r\nline 1.5\r\nline 2\r\n]" | |||
+ "[file2.txt, mode:100644, content:new\nfile\ncontent\n]", | |||
"[file.txt, mode:" + mode | |||
+ ", content:line 1\r\nline 1.5\r\nline 2\r\n]" | |||
+ "[file2.txt, mode:" + mode | |||
+ ", content:new\nfile\ncontent\n]", | |||
indexState(CONTENT)); | |||
} | |||
} | |||
@Test | |||
public void commitWithAutoCrlfAndNonNormalizedIndex() throws Exception { | |||
nonNormalizedIndexTest(false); | |||
} | |||
@Test | |||
public void commitExecutableWithAutoCrlfAndNonNormalizedIndex() | |||
throws Exception { | |||
assumeTrue(FS.DETECTED.supportsExecute()); | |||
nonNormalizedIndexTest(true); | |||
} | |||
@Test | |||
public void testDeletionConflictWithAutoCrlf() throws Exception { | |||
try (Git git = new Git(db)) { |
@@ -12,6 +12,7 @@ package org.eclipse.jgit.api; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertTrue; | |||
import static org.junit.Assume.assumeTrue; | |||
import java.io.File; | |||
import java.io.IOException; | |||
@@ -21,6 +22,8 @@ import org.eclipse.jgit.api.errors.NoFilepatternException; | |||
import org.eclipse.jgit.errors.NoWorkTreeException; | |||
import org.eclipse.jgit.junit.RepositoryTestCase; | |||
import org.eclipse.jgit.lib.Sets; | |||
import org.eclipse.jgit.storage.file.FileBasedConfig; | |||
import org.eclipse.jgit.util.FS; | |||
import org.junit.Test; | |||
public class StatusCommandTest extends RepositoryTestCase { | |||
@@ -135,4 +138,26 @@ public class StatusCommandTest extends RepositoryTestCase { | |||
assertEquals(Sets.of("a", "D/b", "D/D/d"), stat.getModified()); | |||
} | |||
} | |||
@Test | |||
public void testExecutableWithNonNormalizedIndex() throws Exception { | |||
assumeTrue(FS.DETECTED.supportsExecute()); | |||
try (Git git = new Git(db)) { | |||
// Commit a file with CR/LF into the index | |||
FileBasedConfig config = db.getConfig(); | |||
config.setString("core", null, "autocrlf", "false"); | |||
config.save(); | |||
File testFile = writeTrashFile("file.txt", "line 1\r\nline 2\r\n"); | |||
FS.DETECTED.setExecute(testFile, true); | |||
git.add().addFilepattern("file.txt").call(); | |||
git.commit().setMessage("Initial").call(); | |||
assertEquals( | |||
"[file.txt, mode:100755, content:line 1\r\nline 2\r\n]", | |||
indexState(CONTENT)); | |||
config.setString("core", null, "autocrlf", "true"); | |||
config.save(); | |||
Status status = git.status().call(); | |||
assertTrue("Expected no differences", status.isClean()); | |||
} | |||
} | |||
} |
@@ -258,6 +258,38 @@ public class HookTest extends RepositoryTestCase { | |||
} | |||
} | |||
@Test | |||
public void testHookPathWithBlank() throws Exception { | |||
assumeSupportedPlatform(); | |||
File file = writeHookFile("../../a directory/" + PreCommitHook.NAME, | |||
"#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\n" | |||
+ "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\""); | |||
StoredConfig cfg = db.getConfig(); | |||
cfg.load(); | |||
cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null, | |||
ConfigConstants.CONFIG_KEY_HOOKS_PATH, | |||
file.getParentFile().getAbsolutePath()); | |||
cfg.save(); | |||
try (ByteArrayOutputStream out = new ByteArrayOutputStream(); | |||
ByteArrayOutputStream err = new ByteArrayOutputStream()) { | |||
ProcessResult res = FS.DETECTED.runHookIfPresent(db, | |||
PreCommitHook.NAME, new String[] { "arg1", "arg2" }, | |||
new PrintStream(out), new PrintStream(err), "stdin"); | |||
assertEquals("unexpected hook output", | |||
"test arg1 arg2\nstdin\n" | |||
+ db.getDirectory().getAbsolutePath() + '\n' | |||
+ db.getWorkTree().getAbsolutePath() + '\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 testFailedPreCommitHookBlockCommit() throws Exception { | |||
assumeSupportedPlatform(); |
@@ -1467,7 +1467,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { | |||
} | |||
// Read blob from index and check for CR/LF-delimited text. | |||
DirCacheEntry entry = dirCache.getDirCacheEntry(); | |||
if (FileMode.REGULAR_FILE.equals(entry.getFileMode())) { | |||
if ((entry.getRawMode() & FileMode.TYPE_MASK) == FileMode.TYPE_FILE) { | |||
ObjectId blobId = entry.getObjectId(); | |||
if (entry.getStage() > 0 | |||
&& entry.getStage() != DirCacheEntry.STAGE_2) { | |||
@@ -1484,7 +1484,10 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { | |||
break; | |||
} | |||
if (entry.getStage() == DirCacheEntry.STAGE_2) { | |||
blobId = entry.getObjectId(); | |||
if ((entry.getRawMode() | |||
& FileMode.TYPE_MASK) == FileMode.TYPE_FILE) { | |||
blobId = entry.getObjectId(); | |||
} | |||
break; | |||
} | |||
} |
@@ -1747,7 +1747,7 @@ public abstract class FS { | |||
return new ProcessResult(Status.NOT_PRESENT); | |||
} | |||
String cmd = hookFile.getAbsolutePath(); | |||
ProcessBuilder hookProcess = runInShell(cmd, args); | |||
ProcessBuilder hookProcess = runInShell(shellQuote(cmd), args); | |||
hookProcess.directory(runDirectory.getAbsoluteFile()); | |||
Map<String, String> environment = hookProcess.environment(); | |||
environment.put(Constants.GIT_DIR_KEY, | |||
@@ -1770,6 +1770,21 @@ public abstract class FS { | |||
} | |||
} | |||
/** | |||
* Quote a string (such as a file system path obtained from a Java | |||
* {@link File} or {@link Path} object) such that it can be passed as first | |||
* argument to {@link #runInShell(String, String[])}. | |||
* <p> | |||
* This default implementation returns the string unchanged. | |||
* </p> | |||
* | |||
* @param cmd | |||
* the String to quote | |||
* @return the quoted string | |||
*/ | |||
String shellQuote(String cmd) { | |||
return cmd; | |||
} | |||
/** | |||
* Tries to find a hook matching the given one in the given repository. |
@@ -228,7 +228,7 @@ public class FS_POSIX extends FS { | |||
List<String> argv = new ArrayList<>(4 + args.length); | |||
argv.add("sh"); //$NON-NLS-1$ | |||
argv.add("-c"); //$NON-NLS-1$ | |||
argv.add("$0 \"$@\""); //$NON-NLS-1$ | |||
argv.add(cmd + " \"$@\""); //$NON-NLS-1$ | |||
argv.add(cmd); | |||
argv.addAll(Arrays.asList(args)); | |||
ProcessBuilder proc = new ProcessBuilder(); | |||
@@ -236,6 +236,11 @@ public class FS_POSIX extends FS { | |||
return proc; | |||
} | |||
@Override | |||
String shellQuote(String cmd) { | |||
return QuotedString.BOURNE.quote(cmd); | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public ProcessResult runHookIfPresent(Repository repository, String hookName, |
@@ -116,14 +116,19 @@ public class FS_Win32_Cygwin extends FS_Win32 { | |||
List<String> argv = new ArrayList<>(4 + args.length); | |||
argv.add("sh.exe"); //$NON-NLS-1$ | |||
argv.add("-c"); //$NON-NLS-1$ | |||
argv.add("$0 \"$@\""); //$NON-NLS-1$ | |||
argv.add(cmd.replace(File.separatorChar, '/')); | |||
argv.add(cmd + " \"$@\""); //$NON-NLS-1$ | |||
argv.add(cmd); | |||
argv.addAll(Arrays.asList(args)); | |||
ProcessBuilder proc = new ProcessBuilder(); | |||
proc.command(argv); | |||
return proc; | |||
} | |||
@Override | |||
String shellQuote(String cmd) { | |||
return QuotedString.BOURNE.quote(cmd.replace(File.separatorChar, '/')); | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public String relativize(String base, String other) { |