diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java index 4cc3ca0a5..8084505c1 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java @@ -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)) { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java index e0d9cbc19..7e0de82d8 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java @@ -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()); + } + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java index 254878ae0..33ed360ef 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java @@ -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(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java index 2d406bd5b..994af2607 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -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; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java index 216bf2c33..988953b00 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java @@ -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 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[])}. + *

+ * This default implementation returns the string unchanged. + *

+ * + * @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. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java index a0822001c..c9d2770b1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java @@ -228,7 +228,7 @@ public class FS_POSIX extends FS { List 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, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java index 82b2818a4..d53bff78e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java @@ -116,14 +116,19 @@ public class FS_Win32_Cygwin extends FS_Win32 { List 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) {