summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ConflictOutOfBounds.patch10
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ConflictOutOfBounds_PostImage15
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ConflictOutOfBounds_PreImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/allowconflict.patch10
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/allowconflict_PostImage15
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/allowconflict_PreImage8
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchApplierTest.java55
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/patch/PatchApplier.java134
8 files changed, 234 insertions, 21 deletions
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ConflictOutOfBounds.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ConflictOutOfBounds.patch
new file mode 100644
index 0000000000..6e7448b95c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ConflictOutOfBounds.patch
@@ -0,0 +1,10 @@
+diff --git a/ConflictOutOfBounds b/ConflictOutOfBounds
+index 0000000..de98044
+--- a/ConflictOutOfBounds
++++ b/ConflictOutOfBounds
+@@ -25,4 +25,4 @@
+ line3
+-lineA
++lineB
+ line5
+ line6
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ConflictOutOfBounds_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ConflictOutOfBounds_PostImage
new file mode 100644
index 0000000000..4e5d5b2d88
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ConflictOutOfBounds_PostImage
@@ -0,0 +1,15 @@
+line1
+line2
+line3
+line4
+line5
+line6
+line7
+line8
+<<<<<<< HEAD
+=======
+line3
+lineB
+line5
+line6
+>>>>>>> PATCH
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ConflictOutOfBounds_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ConflictOutOfBounds_PreImage
new file mode 100644
index 0000000000..f62562a216
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ConflictOutOfBounds_PreImage
@@ -0,0 +1,8 @@
+line1
+line2
+line3
+line4
+line5
+line6
+line7
+line8
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/allowconflict.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/allowconflict.patch
new file mode 100644
index 0000000000..a99e636382
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/allowconflict.patch
@@ -0,0 +1,10 @@
+diff --git a/allowconflict b/allowconflict
+index 0000000..de98044
+--- a/allowconflict
++++ b/allowconflict
+@@ -3,4 +3,4 @@
+ line3
+-lineA
++lineB
+ line5
+ line6
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/allowconflict_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/allowconflict_PostImage
new file mode 100644
index 0000000000..a963b40dfe
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/allowconflict_PostImage
@@ -0,0 +1,15 @@
+line1
+line2
+<<<<<<< HEAD
+line3
+line4
+line5
+line6
+=======
+line3
+lineB
+line5
+line6
+>>>>>>> PATCH
+line7
+line8
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/allowconflict_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/allowconflict_PreImage
new file mode 100644
index 0000000000..f62562a216
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/allowconflict_PreImage
@@ -0,0 +1,8 @@
+line1
+line2
+line3
+line4
+line5
+line6
+line7
+line8
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchApplierTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchApplierTest.java
index 2aac15bbb6..e4b0ffbc21 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchApplierTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchApplierTest.java
@@ -27,6 +27,7 @@ import java.nio.file.Files;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.Git;
+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;
@@ -48,8 +49,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
-@Suite.SuiteClasses({
- PatchApplierTest.WithWorktree. class, //
+@Suite.SuiteClasses({ PatchApplierTest.WithWorktree.class, //
PatchApplierTest.InCore.class, //
})
public class PatchApplierTest {
@@ -128,6 +128,20 @@ public class PatchApplierTest {
}
}
+ protected Result applyPatchAllowConflicts() throws IOException {
+ InputStream patchStream = getTestResource(name + ".patch");
+ Patch patch = new Patch();
+ patch.parse(patchStream);
+ if (inCore) {
+ try (ObjectInserter oi = db.newObjectInserter()) {
+ return new PatchApplier(db, baseTip, oi).allowConflicts()
+ .applyPatch(patch);
+ }
+ }
+ return new PatchApplier(db).allowConflicts()
+ .applyPatch(patch);
+ }
+
protected static InputStream getTestResource(String patchFile) {
return PatchApplierTest.class.getClassLoader()
.getResourceAsStream("org/eclipse/jgit/diff/" + patchFile);
@@ -169,6 +183,13 @@ public class PatchApplierTest {
verifyContent(result, aName, exists);
}
+ void verifyChange(Result result, String aName, boolean exists,
+ int numConflicts) throws Exception {
+ assertEquals(numConflicts, result.getErrors().size());
+ assertEquals(1, result.getPaths().size());
+ verifyContent(result, aName, exists);
+ }
+
protected byte[] readBlob(ObjectId treeish, String path)
throws Exception {
try (TestRepository<?> tr = new TestRepository<>(db);
@@ -346,6 +367,36 @@ public class PatchApplierTest {
}
@Test
+ public void testConflictMarkers() throws Exception {
+ init("allowconflict", true, true);
+
+ Result result = applyPatchAllowConflicts();
+
+ assertEquals(result.getErrors().size(), 1);
+ PatchApplier.Result.Error error = result.getErrors().get(0);
+ error.hh = null; // We don't assert the hunk header as it is a
+ // complex object with lots of internal state.
+ assertEquals(error, new PatchApplier.Result.Error(
+ "cannot apply hunk", "allowconflict", null));
+ verifyChange(result, "allowconflict", true, 1);
+ }
+
+ @Test
+ public void testConflictMarkersOutOfBounds() throws Exception {
+ init("ConflictOutOfBounds", true, true);
+
+ Result result = applyPatchAllowConflicts();
+
+ assertEquals(result.getErrors().size(), 1);
+ PatchApplier.Result.Error error = result.getErrors().get(0);
+ error.hh = null; // We don't assert the hunk header as it is a
+ // complex object with lots of internal state.
+ assertEquals(error, new PatchApplier.Result.Error(
+ "cannot apply hunk", "ConflictOutOfBounds", null));
+ verifyChange(result, "ConflictOutOfBounds", true, 1);
+ }
+
+ @Test
public void testShiftUp() throws Exception {
init("ShiftUp");
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/PatchApplier.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/PatchApplier.java
index a327095c81..84c2ec43dc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/PatchApplier.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/PatchApplier.java
@@ -23,6 +23,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
@@ -33,9 +34,11 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.zip.InflaterInputStream;
+
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.errors.FilterFailedException;
import org.eclipse.jgit.api.errors.PatchFormatException;
@@ -101,11 +104,12 @@ import org.eclipse.jgit.util.sha1.SHA1;
* @since 6.4
*/
public class PatchApplier {
-
private static final byte[] NO_EOL = "\\ No newline at end of file" //$NON-NLS-1$
.getBytes(StandardCharsets.US_ASCII);
- /** The tree before applying the patch. Only non-null for inCore operation. */
+ /**
+ * The tree before applying the patch. Only non-null for inCore operation.
+ */
@Nullable
private final RevTree beforeTree;
@@ -115,10 +119,14 @@ public class PatchApplier {
private final ObjectReader reader;
+ private final Charset charset;
+
private WorkingTreeOptions workingTreeOptions;
private int inCoreSizeLimit;
+ private boolean allowConflicts;
+
/**
* @param repo
* repository to apply the patch in
@@ -128,7 +136,8 @@ public class PatchApplier {
inserter = repo.newObjectInserter();
reader = inserter.newReader();
beforeTree = null;
-
+ allowConflicts = false;
+ charset = StandardCharsets.UTF_8;
Config config = repo.getConfig();
workingTreeOptions = config.get(WorkingTreeOptions.KEY);
inCoreSizeLimit = config.getInt(ConfigConstants.CONFIG_MERGE_SECTION,
@@ -143,11 +152,14 @@ public class PatchApplier {
* @param oi
* to be used for modifying objects
*/
- public PatchApplier(Repository repo, RevTree beforeTree, ObjectInserter oi) {
+ public PatchApplier(Repository repo, RevTree beforeTree,
+ ObjectInserter oi) {
this.repo = repo;
this.beforeTree = beforeTree;
inserter = oi;
reader = oi.newReader();
+ allowConflicts = false;
+ charset = StandardCharsets.UTF_8;
}
/**
@@ -157,7 +169,6 @@ public class PatchApplier {
* @since 6.3
*/
public static class Result {
-
/**
* A wrapper for a patch applying error that affects a given file.
*
@@ -166,13 +177,14 @@ public class PatchApplier {
// TODO(ms): rename this class in next major release
@SuppressWarnings("JavaLangClash")
public static class Error {
+ String msg;
+
+ String oldFileName;
- private String msg;
- private String oldFileName;
- private @Nullable HunkHeader hh;
+ @Nullable
+ HunkHeader hh;
- private Error(String msg, String oldFileName,
- @Nullable HunkHeader hh) {
+ Error(String msg, String oldFileName, @Nullable HunkHeader hh) {
this.msg = msg;
this.oldFileName = oldFileName;
this.hh = hh;
@@ -181,13 +193,33 @@ public class PatchApplier {
@Override
public String toString() {
if (hh != null) {
- return MessageFormat.format(JGitText.get().patchApplyErrorWithHunk,
- oldFileName, hh, msg);
+ return MessageFormat.format(
+ JGitText.get().patchApplyErrorWithHunk, oldFileName,
+ hh, msg);
}
- return MessageFormat.format(JGitText.get().patchApplyErrorWithoutHunk,
- oldFileName, msg);
+ return MessageFormat.format(
+ JGitText.get().patchApplyErrorWithoutHunk, oldFileName,
+ msg);
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || !(o instanceof Error)) {
+ return false;
+ }
+ Error error = (Error) o;
+ return Objects.equals(msg, error.msg)
+ && Objects.equals(oldFileName, error.oldFileName)
+ && Objects.equals(hh, error.hh);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(msg, oldFileName, hh);
+ }
}
private ObjectId treeId;
@@ -357,6 +389,17 @@ public class PatchApplier {
return result;
}
+ /**
+ * Sets up the {@link PatchApplier} to apply patches even if they conflict.
+ *
+ * @return the {@link PatchApplier} to apply any patches
+ * @since 6.10
+ */
+ public PatchApplier allowConflicts() {
+ allowConflicts = true;
+ return this;
+ }
+
private File getFile(String path) {
return inCore() ? null : new File(repo.getWorkTree(), path);
}
@@ -439,6 +482,7 @@ public class PatchApplier {
return false;
}
}
+
private static final int FILE_TREE_INDEX = 1;
/**
@@ -539,7 +583,9 @@ public class PatchApplier {
convertCrLf);
resultStreamLoader = applyText(raw, fh, result);
}
- if (resultStreamLoader == null || !result.getErrors().isEmpty()) {
+ if (resultStreamLoader == null
+ || (!result.getErrors().isEmpty() && result.getErrors().stream()
+ .anyMatch(e -> !e.msg.equals("cannot apply hunk")))) { //$NON-NLS-1$
return;
}
@@ -961,9 +1007,51 @@ public class PatchApplier {
}
}
if (!applies) {
- result.addError(JGitText.get().applyTextPatchCannotApplyHunk,
- fh.getOldPath(), hh);
- return null;
+ if (!allowConflicts) {
+ result.addError(
+ JGitText.get().applyTextPatchCannotApplyHunk,
+ fh.getOldPath(), hh);
+ return null;
+ }
+ // Insert conflict markers. This is best-guess because the
+ // file might have changed completely. But at least we give
+ // the user a graceful state that they can resolve manually.
+ // An alternative to this is using the 3-way merger. This
+ // only works if the pre-image SHA is contained in the repo.
+ // If that was the case, cherry-picking the original commit
+ // should be preferred to apply a patch.
+ result.addError("cannot apply hunk", fh.getOldPath(), hh); //$NON-NLS-1$
+ newLines.add(Math.min(applyAt++, newLines.size()),
+ asBytes("<<<<<<< HEAD")); //$NON-NLS-1$
+ applyAt += hh.getOldImage().lineCount;
+ newLines.add(Math.min(applyAt++, newLines.size()),
+ asBytes("=======")); //$NON-NLS-1$
+
+ int sz = hunkLines.size();
+ for (int j = 1; j < sz; j++) {
+ ByteBuffer hunkLine = hunkLines.get(j);
+ if (!hunkLine.hasRemaining()) {
+ // Completely empty line; accept as empty context
+ // line
+ applyAt++;
+ lastWasRemoval = false;
+ continue;
+ }
+ switch (hunkLine.array()[hunkLine.position()]) {
+ case ' ':
+ case '+':
+ newLines.add(Math.min(applyAt++, newLines.size()),
+ slice(hunkLine, 1));
+ break;
+ case '-':
+ case '\\':
+ default:
+ break;
+ }
+ }
+ newLines.add(Math.min(applyAt++, newLines.size()),
+ asBytes(">>>>>>> PATCH")); //$NON-NLS-1$
+ continue;
}
// Hunk applies at applyAt. Apply it, and update afterLastHunk and
// lineNumberShift
@@ -1010,7 +1098,11 @@ public class PatchApplier {
} else if (!rt.isMissingNewlineAtEnd()) {
newLines.add(null);
}
+ return toContentStreamLoader(newLines);
+ }
+ private static ContentStreamLoader toContentStreamLoader(
+ List<ByteBuffer> newLines) throws IOException {
// We could check if old == new, but the short-circuiting complicates
// logic for inCore patching, so just write the new thing regardless.
TemporaryBuffer buffer = new TemporaryBuffer.LocalFile(null);
@@ -1034,6 +1126,10 @@ public class PatchApplier {
}
}
+ private ByteBuffer asBytes(String str) {
+ return ByteBuffer.wrap(str.getBytes(charset));
+ }
+
@SuppressWarnings("ByteBufferBackingArray")
private boolean canApplyAt(List<ByteBuffer> hunkLines,
List<ByteBuffer> newLines, int line) {
@@ -1123,4 +1219,4 @@ public class PatchApplier {
in.close();
}
}
-}
+} \ No newline at end of file