aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf.patch9
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2.patch9
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2_PostImage3
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2_PreImage3
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3.patch8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3_PostImage3
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3_PreImage0
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf4.patch9
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf4_PostImage3
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf_PostImage3
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf_PreImage3
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest.patch9
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest_PostImage3
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest_PreImage3
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java191
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java279
16 files changed, 513 insertions, 25 deletions
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf.patch
new file mode 100644
index 0000000000..01eb0b9510
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf.patch
@@ -0,0 +1,9 @@
+diff --git a/crlf b/crlf
+index 9206ee6..95dd193 100644
+--- a/crlf
++++ b/crlf
+@@ -1,3 +1,3 @@
+ foo
+-fie
++bar
+ fum
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2.patch
new file mode 100644
index 0000000000..5a6210489c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2.patch
@@ -0,0 +1,9 @@
+diff --git a/crlf2 b/crlf2
+index 05c1c78..91e246d 100644
+--- a/crlf2
++++ b/crlf2
+@@ -1,3 +1,3 @@
+ foo
+-fie
++bar
+ fum
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2_PostImage
new file mode 100644
index 0000000000..91e246d064
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2_PostImage
@@ -0,0 +1,3 @@
+foo
+bar
+fum
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2_PreImage
new file mode 100644
index 0000000000..05c1c782e5
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2_PreImage
@@ -0,0 +1,3 @@
+foo
+fie
+fum
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3.patch
new file mode 100644
index 0000000000..b155148b3e
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3.patch
@@ -0,0 +1,8 @@
+diff --git a/crlf3 b/crlf3
+index e69de29..9206ee6 100644
+--- a/crlf3
++++ b/crlf3
+@@ -0,0 +1,3 @@
++foo
++fie
++fum
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3_PostImage
new file mode 100644
index 0000000000..05c1c782e5
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3_PostImage
@@ -0,0 +1,3 @@
+foo
+fie
+fum
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3_PreImage
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3_PreImage
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf4.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf4.patch
new file mode 100644
index 0000000000..0cf606390a
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf4.patch
@@ -0,0 +1,9 @@
+diff --git a/crlf4 b/crlf4
+new file mode 100644
+index 0000000..9206ee6
+--- /dev/null
++++ b/crlf4
+@@ -0,0 +1,3 @@
++foo
++fie
++fum
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf4_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf4_PostImage
new file mode 100644
index 0000000000..05c1c782e5
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf4_PostImage
@@ -0,0 +1,3 @@
+foo
+fie
+fum
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf_PostImage
new file mode 100644
index 0000000000..91e246d064
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf_PostImage
@@ -0,0 +1,3 @@
+foo
+bar
+fum
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf_PreImage
new file mode 100644
index 0000000000..05c1c782e5
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf_PreImage
@@ -0,0 +1,3 @@
+foo
+fie
+fum
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest.patch
new file mode 100644
index 0000000000..ab4d4265a0
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest.patch
@@ -0,0 +1,9 @@
+diff --git a/smudgetest b/smudgetest
+index a24d41e..762c4d0 100644
+--- a/smudgetest
++++ b/smudgetest
+@@ -1,3 +1,3 @@
+ PERLE
+-HEBLE
++sprich
+ speak
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest_PostImage
new file mode 100644
index 0000000000..ad630893df
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest_PostImage
@@ -0,0 +1,3 @@
+PARLA
+sprich
+speak
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest_PreImage
new file mode 100644
index 0000000000..9bbd8c763e
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest_PreImage
@@ -0,0 +1,3 @@
+PARLA
+HABLA
+speak
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java
index 055eba7184..335a64d70b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011, 2020 IBM Corporation and others
+ * Copyright (C) 2011, 2021 IBM Corporation and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -18,11 +18,17 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import org.eclipse.jgit.api.errors.PatchApplyException;
import org.eclipse.jgit.api.errors.PatchFormatException;
+import org.eclipse.jgit.attributes.FilterCommand;
+import org.eclipse.jgit.attributes.FilterCommandFactory;
+import org.eclipse.jgit.attributes.FilterCommandRegistry;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.junit.Test;
public class ApplyCommandTest extends RepositoryTestCase {
@@ -58,6 +64,189 @@ public class ApplyCommandTest extends RepositoryTestCase {
}
@Test
+ public void testCrLf() throws Exception {
+ try {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
+ ApplyResult result = init("crlf", true, true);
+ assertEquals(1, result.getUpdatedFiles().size());
+ assertEquals(new File(db.getWorkTree(), "crlf"),
+ result.getUpdatedFiles().get(0));
+ checkFile(new File(db.getWorkTree(), "crlf"),
+ b.getString(0, b.size(), false));
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ @Test
+ public void testCrLfOff() throws Exception {
+ try {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
+ ApplyResult result = init("crlf", true, true);
+ assertEquals(1, result.getUpdatedFiles().size());
+ assertEquals(new File(db.getWorkTree(), "crlf"),
+ result.getUpdatedFiles().get(0));
+ checkFile(new File(db.getWorkTree(), "crlf"),
+ b.getString(0, b.size(), false));
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ @Test
+ public void testCrLfEmptyCommitted() throws Exception {
+ try {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
+ ApplyResult result = init("crlf3", true, true);
+ assertEquals(1, result.getUpdatedFiles().size());
+ assertEquals(new File(db.getWorkTree(), "crlf3"),
+ result.getUpdatedFiles().get(0));
+ checkFile(new File(db.getWorkTree(), "crlf3"),
+ b.getString(0, b.size(), false));
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ @Test
+ public void testCrLfNewFile() throws Exception {
+ try {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
+ ApplyResult result = init("crlf4", false, true);
+ assertEquals(1, result.getUpdatedFiles().size());
+ assertEquals(new File(db.getWorkTree(), "crlf4"),
+ result.getUpdatedFiles().get(0));
+ checkFile(new File(db.getWorkTree(), "crlf4"),
+ b.getString(0, b.size(), false));
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ @Test
+ public void testPatchWithCrLf() throws Exception {
+ try {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
+ ApplyResult result = init("crlf2", true, true);
+ assertEquals(1, result.getUpdatedFiles().size());
+ assertEquals(new File(db.getWorkTree(), "crlf2"),
+ result.getUpdatedFiles().get(0));
+ checkFile(new File(db.getWorkTree(), "crlf2"),
+ b.getString(0, b.size(), false));
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ @Test
+ public void testPatchWithCrLf2() throws Exception {
+ String name = "crlf2";
+ try (Git git = new Git(db)) {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
+ a = new RawText(readFile(name + "_PreImage"));
+ write(new File(db.getWorkTree(), name),
+ a.getString(0, a.size(), false));
+
+ git.add().addFilepattern(name).call();
+ git.commit().setMessage("PreImage").call();
+
+ b = new RawText(readFile(name + "_PostImage"));
+
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
+ ApplyResult result = git.apply()
+ .setPatch(getTestResource(name + ".patch")).call();
+ assertEquals(1, result.getUpdatedFiles().size());
+ assertEquals(new File(db.getWorkTree(), name),
+ result.getUpdatedFiles().get(0));
+ checkFile(new File(db.getWorkTree(), name),
+ b.getString(0, b.size(), false));
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ // Clean/smudge filter for testFiltering. The smudgetest test resources were
+ // created with C git using a clean filter sed -e "s/A/E/g" and the smudge
+ // filter sed -e "s/E/A/g". To keep the test independent of the presence of
+ // sed, implement this with a built-in filter.
+ private static class ReplaceFilter extends FilterCommand {
+
+ private final char toReplace;
+
+ private final char replacement;
+
+ ReplaceFilter(InputStream in, OutputStream out, char toReplace,
+ char replacement) {
+ super(in, out);
+ this.toReplace = toReplace;
+ this.replacement = replacement;
+ }
+
+ @Override
+ public int run() throws IOException {
+ int b = in.read();
+ if (b < 0) {
+ in.close();
+ out.close();
+ return -1;
+ }
+ if ((b & 0xFF) == toReplace) {
+ b = replacement;
+ }
+ out.write(b);
+ return 1;
+ }
+ }
+
+ @Test
+ public void testFiltering() throws Exception {
+ // Set up filter
+ FilterCommandFactory clean = (repo, in, out) -> {
+ return new ReplaceFilter(in, out, 'A', 'E');
+ };
+ FilterCommandFactory smudge = (repo, in, out) -> {
+ return new ReplaceFilter(in, out, 'E', 'A');
+ };
+ FilterCommandRegistry.register("jgit://builtin/a2e/clean", clean);
+ FilterCommandRegistry.register("jgit://builtin/a2e/smudge", smudge);
+ try (Git git = new Git(db)) {
+ Config config = db.getConfig();
+ config.setString(ConfigConstants.CONFIG_FILTER_SECTION, "a2e",
+ "clean", "jgit://builtin/a2e/clean");
+ config.setString(ConfigConstants.CONFIG_FILTER_SECTION, "a2e",
+ "smudge", "jgit://builtin/a2e/smudge");
+ write(new File(db.getWorkTree(), ".gitattributes"),
+ "smudgetest filter=a2e");
+ git.add().addFilepattern(".gitattributes").call();
+ git.commit().setMessage("Attributes").call();
+ ApplyResult result = init("smudgetest", true, true);
+ assertEquals(1, result.getUpdatedFiles().size());
+ assertEquals(new File(db.getWorkTree(), "smudgetest"),
+ result.getUpdatedFiles().get(0));
+ checkFile(new File(db.getWorkTree(), "smudgetest"),
+ b.getString(0, b.size(), false));
+
+ } finally {
+ // Tear down filter
+ FilterCommandRegistry.unregister("jgit://builtin/a2e/clean");
+ FilterCommandRegistry.unregister("jgit://builtin/a2e/smudge");
+ }
+ }
+
+ @Test
public void testAddA1() throws Exception {
ApplyResult result = init("A1", false, true);
assertEquals(1, result.getUpdatedFiles().size());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
index e228e8276a..5d975ea389 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011, 2020 IBM Corporation and others
+ * Copyright (C) 2011, 2021 IBM Corporation and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -9,10 +9,16 @@
*/
package org.eclipse.jgit.api;
+import java.io.BufferedWriter;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
import java.io.Writer;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
@@ -20,18 +26,45 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import org.eclipse.jgit.api.errors.FilterFailedException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.PatchApplyException;
import org.eclipse.jgit.api.errors.PatchFormatException;
+import org.eclipse.jgit.attributes.FilterCommand;
+import org.eclipse.jgit.attributes.FilterCommandRegistry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.diff.RawText;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheCheckout;
+import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
+import org.eclipse.jgit.dircache.DirCacheIterator;
+import org.eclipse.jgit.errors.LargeObjectException;
+import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectStream;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.patch.FileHeader;
import org.eclipse.jgit.patch.HunkHeader;
import org.eclipse.jgit.patch.Patch;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
+import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
+import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter;
+import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
+import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.StringUtils;
+import org.eclipse.jgit.util.TemporaryBuffer;
+import org.eclipse.jgit.util.FS.ExecutionResult;
+import org.eclipse.jgit.util.TemporaryBuffer.LocalFile;
+import org.eclipse.jgit.util.io.EolStreamTypeUtil;
/**
* Apply a patch to files and/or to the index.
@@ -45,7 +78,7 @@ public class ApplyCommand extends GitCommand<ApplyResult> {
private InputStream in;
/**
- * Constructs the command if the patch is to be applied to the index.
+ * Constructs the command.
*
* @param repo
*/
@@ -79,6 +112,7 @@ public class ApplyCommand extends GitCommand<ApplyResult> {
public ApplyResult call() throws GitAPIException, PatchFormatException,
PatchApplyException {
checkCallable();
+ setCallable(false);
ApplyResult r = new ApplyResult();
try {
final Patch p = new Patch();
@@ -87,19 +121,22 @@ public class ApplyCommand extends GitCommand<ApplyResult> {
} finally {
in.close();
}
- if (!p.getErrors().isEmpty())
+ if (!p.getErrors().isEmpty()) {
throw new PatchFormatException(p.getErrors());
+ }
+ Repository repository = getRepository();
+ DirCache cache = repository.readDirCache();
for (FileHeader fh : p.getFiles()) {
ChangeType type = fh.getChangeType();
File f = null;
switch (type) {
case ADD:
f = getFile(fh.getNewPath(), true);
- apply(f, fh);
+ apply(repository, fh.getNewPath(), cache, f, fh);
break;
case MODIFY:
f = getFile(fh.getOldPath(), false);
- apply(f, fh);
+ apply(repository, fh.getOldPath(), cache, f, fh);
break;
case DELETE:
f = getFile(fh.getOldPath(), false);
@@ -118,14 +155,14 @@ public class ApplyCommand extends GitCommand<ApplyResult> {
throw new PatchApplyException(MessageFormat.format(
JGitText.get().renameFileFailed, f, dest), e);
}
- apply(dest, fh);
+ apply(repository, fh.getOldPath(), cache, dest, fh);
break;
case COPY:
f = getFile(fh.getOldPath(), false);
File target = getFile(fh.getNewPath(), false);
FileUtils.mkdirs(target.getParentFile(), true);
Files.copy(f.toPath(), target.toPath());
- apply(target, fh);
+ apply(repository, fh.getOldPath(), cache, target, fh);
}
r.addUpdatedFile(f);
}
@@ -133,14 +170,13 @@ public class ApplyCommand extends GitCommand<ApplyResult> {
throw new PatchApplyException(MessageFormat.format(
JGitText.get().patchApplyException, e.getMessage()), e);
}
- setCallable(false);
return r;
}
private File getFile(String path, boolean create)
throws PatchApplyException {
File f = new File(getRepository().getWorkTree(), path);
- if (create)
+ if (create) {
try {
File parent = f.getParentFile();
FileUtils.mkdirs(parent, true);
@@ -149,21 +185,201 @@ public class ApplyCommand extends GitCommand<ApplyResult> {
throw new PatchApplyException(MessageFormat.format(
JGitText.get().createNewFileFailed, f), e);
}
+ }
return f;
}
+ private void apply(Repository repository, String path, DirCache cache,
+ File f, FileHeader fh) throws IOException, PatchApplyException {
+ boolean convertCrLf = needsCrLfConversion(f, fh);
+ // Use a TreeWalk with a DirCacheIterator to pick up the correct
+ // clean/smudge filters. CR-LF handling is completely determined by
+ // whether the file or the patch have CR-LF line endings.
+ try (TreeWalk walk = new TreeWalk(repository)) {
+ walk.setOperationType(OperationType.CHECKIN_OP);
+ FileTreeIterator files = new FileTreeIterator(repository);
+ int fileIdx = walk.addTree(files);
+ int cacheIdx = walk.addTree(new DirCacheIterator(cache));
+ files.setDirCacheIterator(walk, cacheIdx);
+ walk.setFilter(AndTreeFilter.create(
+ PathFilterGroup.createFromStrings(path),
+ new NotIgnoredFilter(fileIdx)));
+ walk.setRecursive(true);
+ if (walk.next()) {
+ // If the file on disk has no newline characters, convertCrLf
+ // will be false. In that case we want to honor the normal git
+ // settings.
+ EolStreamType streamType = convertCrLf ? EolStreamType.TEXT_CRLF
+ : walk.getEolStreamType(OperationType.CHECKOUT_OP);
+ String command = walk.getFilterCommand(
+ Constants.ATTR_FILTER_TYPE_SMUDGE);
+ CheckoutMetadata checkOut = new CheckoutMetadata(streamType, command);
+ FileTreeIterator file = walk.getTree(fileIdx,
+ FileTreeIterator.class);
+ if (file != null) {
+ command = walk
+ .getFilterCommand(Constants.ATTR_FILTER_TYPE_CLEAN);
+ RawText raw;
+ // Can't use file.openEntryStream() as it would do CR-LF
+ // conversion as usual, not as wanted by us.
+ try (InputStream input = filterClean(repository, path,
+ new FileInputStream(f), convertCrLf, command)) {
+ raw = new RawText(IO.readWholeStream(input, 0).array());
+ }
+ apply(repository, path, raw, f, fh, checkOut);
+ return;
+ }
+ }
+ }
+ // File ignored?
+ RawText raw;
+ CheckoutMetadata checkOut;
+ if (convertCrLf) {
+ try (InputStream input = EolStreamTypeUtil.wrapInputStream(
+ new FileInputStream(f), EolStreamType.TEXT_LF)) {
+ raw = new RawText(IO.readWholeStream(input, 0).array());
+ }
+ checkOut = new CheckoutMetadata(EolStreamType.TEXT_CRLF, null);
+ } else {
+ raw = new RawText(f);
+ checkOut = new CheckoutMetadata(EolStreamType.DIRECT, null);
+ }
+ apply(repository, path, raw, f, fh, checkOut);
+ }
+
+ private boolean needsCrLfConversion(File f, FileHeader fileHeader)
+ throws IOException {
+ if (!hasCrLf(fileHeader)) {
+ try (InputStream input = new FileInputStream(f)) {
+ return RawText.isCrLfText(input);
+ }
+ }
+ return false;
+ }
+
+ private static boolean hasCrLf(FileHeader fileHeader) {
+ if (fileHeader == null) {
+ return false;
+ }
+ for (HunkHeader header : fileHeader.getHunks()) {
+ byte[] buf = header.getBuffer();
+ int hunkEnd = header.getEndOffset();
+ int lineStart = header.getStartOffset();
+ while (lineStart < hunkEnd) {
+ int nextLineStart = RawParseUtils.nextLF(buf, lineStart);
+ if (nextLineStart > hunkEnd) {
+ nextLineStart = hunkEnd;
+ }
+ if (nextLineStart <= lineStart) {
+ break;
+ }
+ if (nextLineStart - lineStart > 1) {
+ char first = (char) (buf[lineStart] & 0xFF);
+ if (first == ' ' || first == '-') {
+ // It's an old line. Does it end in CR-LF?
+ if (buf[nextLineStart - 2] == '\r') {
+ return true;
+ }
+ }
+ }
+ lineStart = nextLineStart;
+ }
+ }
+ return false;
+ }
+
+ private InputStream filterClean(Repository repository, String path,
+ InputStream fromFile, boolean convertCrLf, String filterCommand)
+ throws IOException {
+ InputStream input = fromFile;
+ if (convertCrLf) {
+ input = EolStreamTypeUtil.wrapInputStream(input,
+ EolStreamType.TEXT_LF);
+ }
+ if (StringUtils.isEmptyOrNull(filterCommand)) {
+ return input;
+ }
+ if (FilterCommandRegistry.isRegistered(filterCommand)) {
+ LocalFile buffer = new TemporaryBuffer.LocalFile(null);
+ FilterCommand command = FilterCommandRegistry.createFilterCommand(
+ filterCommand, repository, input, buffer);
+ while (command.run() != -1) {
+ // loop as long as command.run() tells there is work to do
+ }
+ return buffer.openInputStreamWithAutoDestroy();
+ }
+ FS fs = repository.getFS();
+ ProcessBuilder filterProcessBuilder = fs.runInShell(filterCommand,
+ new String[0]);
+ filterProcessBuilder.directory(repository.getWorkTree());
+ filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
+ repository.getDirectory().getAbsolutePath());
+ ExecutionResult result;
+ try {
+ result = fs.execute(filterProcessBuilder, in);
+ } catch (IOException | InterruptedException e) {
+ throw new IOException(
+ new FilterFailedException(e, filterCommand, path));
+ }
+ int rc = result.getRc();
+ if (rc != 0) {
+ throw new IOException(new FilterFailedException(rc, filterCommand,
+ path, result.getStdout().toByteArray(4096), RawParseUtils
+ .decode(result.getStderr().toByteArray(4096))));
+ }
+ return result.getStdout().openInputStreamWithAutoDestroy();
+ }
+
/**
- * @param f
- * @param fh
- * @throws IOException
- * @throws PatchApplyException
+ * We write the patch result to a {@link TemporaryBuffer} and then use
+ * {@link DirCacheCheckout}.getContent() to run the result through the CR-LF
+ * and smudge filters. DirCacheCheckout needs an ObjectLoader, not a
+ * TemporaryBuffer, so this class bridges between the two, making the
+ * TemporaryBuffer look like an ordinary git blob to DirCacheCheckout.
*/
- private void apply(File f, FileHeader fh)
+ private static class BufferLoader extends ObjectLoader {
+
+ private TemporaryBuffer data;
+
+ BufferLoader(TemporaryBuffer data) {
+ this.data = data;
+ }
+
+ @Override
+ public int getType() {
+ return Constants.OBJ_BLOB;
+ }
+
+ @Override
+ public long getSize() {
+ return data.length();
+ }
+
+ @Override
+ public boolean isLarge() {
+ return true;
+ }
+
+ @Override
+ public byte[] getCachedBytes() throws LargeObjectException {
+ throw new LargeObjectException();
+ }
+
+ @Override
+ public ObjectStream openStream()
+ throws MissingObjectException, IOException {
+ return new ObjectStream.Filter(getType(), getSize(),
+ data.openInputStream());
+ }
+ }
+
+ private void apply(Repository repository, String path, RawText rt, File f,
+ FileHeader fh, CheckoutMetadata checkOut)
throws IOException, PatchApplyException {
- RawText rt = new RawText(f);
List<String> oldLines = new ArrayList<>(rt.size());
- for (int i = 0; i < rt.size(); i++)
+ for (int i = 0; i < rt.size(); i++) {
oldLines.add(rt.getString(i));
+ }
List<String> newLines = new ArrayList<>(oldLines);
int afterLastHunk = 0;
int lineNumberShift = 0;
@@ -279,17 +495,32 @@ public class ApplyCommand extends GitCommand<ApplyResult> {
if (!isChanged(oldLines, newLines)) {
return; // Don't touch the file
}
- try (Writer fw = Files.newBufferedWriter(f.toPath())) {
- for (Iterator<String> l = newLines.iterator(); l.hasNext();) {
- fw.write(l.next());
- if (l.hasNext()) {
- // Don't bother handling line endings - if it was Windows,
- // the \r is still there!
- fw.write('\n');
+
+ // TODO: forcing UTF-8 is a bit strange and may lead to re-coding if the
+ // input was some other encoding, but it's what previous versions of
+ // this code used. (Even earlier the code used the default encoding,
+ // which has the same problem.) Perhaps using bytes instead of Strings
+ // for the lines would be better.
+ TemporaryBuffer buffer = new TemporaryBuffer.LocalFile(null);
+ try {
+ try (Writer w = new BufferedWriter(
+ new OutputStreamWriter(buffer, StandardCharsets.UTF_8))) {
+ for (Iterator<String> l = newLines.iterator(); l.hasNext();) {
+ w.write(l.next());
+ if (l.hasNext()) {
+ w.write('\n');
+ }
}
}
+ try (OutputStream output = new FileOutputStream(f)) {
+ DirCacheCheckout.getContent(repository, path, checkOut,
+ new BufferLoader(buffer), null, output);
+ }
+ } finally {
+ buffer.destroy();
}
- getRepository().getFS().setExecute(f, fh.getNewMode() == FileMode.EXECUTABLE_FILE);
+ repository.getFS().setExecute(f,
+ fh.getNewMode() == FileMode.EXECUTABLE_FILE);
}
private boolean canApplyAt(List<String> hunkLines, List<String> newLines,