Browse Source

ApplyCommand: use context lines to determine hunk location

If a hunk does not apply at the position stated in the hunk header
try to determine its position using the old lines (context and
deleted lines).

This is still a far cry from a full git apply: it doesn't do binary
patches, it doesn't handle git's whitespace options, and it's perhaps
not the fastest on big patches. C git hashes the lines and uses these
hashes to speed up matching hunks (and to do its whitespace magic).

Bug: 562348
Change-Id: Id0796bba059d84e648769d5896f497fde0b787dd
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
master
Thomas Wolf 5 years ago committed by Matthias Sohn
parent
commit
ed481f96b8
  1. 14
      org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftDown.patch
  2. 24
      org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftDown2.patch
  3. 75
      org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftDown2_PostImage
  4. 68
      org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftDown2_PreImage
  5. 71
      org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftDown_PostImage
  6. 68
      org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftDown_PreImage
  7. 14
      org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftUp.patch
  8. 23
      org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftUp2.patch
  9. 38
      org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftUp2_PostImage
  10. 32
      org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftUp2_PreImage
  11. 35
      org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftUp_PostImage
  12. 32
      org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftUp_PreImage
  13. 42
      org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java
  14. 166
      org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java

14
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftDown.patch

@ -0,0 +1,14 @@
diff --git a/ShiftDown b/ShiftDown
index 8b9727b..25dc192 100644
--- a/ShiftDown
+++ b/ShiftDown
@@ -16,6 +16,9 @@
something("A.b", "bar");
}
+ public void methodC() {
+ something("A.c", "bar");
+ }
}
public class B {

24
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftDown2.patch

@ -0,0 +1,24 @@
diff --git a/ShiftDown2 b/ShiftDown2
index 8b9727b..63353aa 100644
--- a/ShiftDown2
+++ b/ShiftDown2
@@ -16,6 +16,9 @@
something("A.b", "bar");
}
+ public void methodC() {
+ something("A.c", "bar");
+ }
}
public class B {
@@ -28,5 +31,9 @@
something("B.b", "bar");
}
+ public void methodC() {
+ something("B.c", "bar");
+ }
+
}
}

75
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftDown2_PostImage

@ -0,0 +1,75 @@
package org.eclipse.jgit.test.apply;
public class TestClass {
private void something(String prefix, String msg) {
System.out.println(prefix + ": " + msg);
}
public class D {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
}
public class E {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
}
public class F {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
}
public class A {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
public void methodC() {
something("A.c", "bar");
}
}
public class B {
public void methodA() {
something("B.a", "foo");
}
public void methodB() {
something("B.b", "bar");
}
public void methodC() {
something("B.c", "bar");
}
}
}

68
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftDown2_PreImage

@ -0,0 +1,68 @@
package org.eclipse.jgit.test.apply;
public class TestClass {
private void something(String prefix, String msg) {
System.out.println(prefix + ": " + msg);
}
public class D {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
}
public class E {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
}
public class F {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
}
public class A {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
}
public class B {
public void methodA() {
something("B.a", "foo");
}
public void methodB() {
something("B.b", "bar");
}
}
}

71
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftDown_PostImage

@ -0,0 +1,71 @@
package org.eclipse.jgit.test.apply;
public class TestClass {
private void something(String prefix, String msg) {
System.out.println(prefix + ": " + msg);
}
public class D {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
}
public class E {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
}
public class F {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
}
public class A {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
public void methodC() {
something("A.c", "bar");
}
}
public class B {
public void methodA() {
something("B.a", "foo");
}
public void methodB() {
something("B.b", "bar");
}
}
}

68
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftDown_PreImage

@ -0,0 +1,68 @@
package org.eclipse.jgit.test.apply;
public class TestClass {
private void something(String prefix, String msg) {
System.out.println(prefix + ": " + msg);
}
public class D {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
}
public class E {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
}
public class F {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
}
public class A {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
}
public class B {
public void methodA() {
something("B.a", "foo");
}
public void methodB() {
something("B.b", "bar");
}
}
}

14
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftUp.patch

@ -0,0 +1,14 @@
diff --git a/ShiftUp b/ShiftUp
index e1ee19c..5c6e9bc 100644
--- a/ShiftUp
+++ b/ShiftUp
@@ -52,6 +52,9 @@
something("A.b", "bar");
}
+ public void methodC() {
+ something("A.c", "bar");
+ }
}
public class B {

23
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftUp2.patch

@ -0,0 +1,23 @@
diff --git a/ShiftUp2 b/ShiftUp2
index e1ee19c..f010144 100644
--- a/ShiftUp2
+++ b/ShiftUp2
@@ -52,6 +52,9 @@
something("A.b", "bar");
}
+ public void methodC() {
+ something("A.c", "bar");
+ }
}
public class B {
@@ -64,5 +67,8 @@
something("B.b", "bar");
}
+ public void methodC() {
+ something("B.c", "bar");
+ }
}
}

38
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftUp2_PostImage

@ -0,0 +1,38 @@
package org.eclipse.jgit.test.apply;
public class TestClass {
private void something(String prefix, String msg) {
System.out.println(prefix + ": " + msg);
}
public class A {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
public void methodC() {
something("A.c", "bar");
}
}
public class B {
public void methodA() {
something("B.a", "foo");
}
public void methodB() {
something("B.b", "bar");
}
public void methodC() {
something("B.c", "bar");
}
}
}

32
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftUp2_PreImage

@ -0,0 +1,32 @@
package org.eclipse.jgit.test.apply;
public class TestClass {
private void something(String prefix, String msg) {
System.out.println(prefix + ": " + msg);
}
public class A {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
}
public class B {
public void methodA() {
something("B.a", "foo");
}
public void methodB() {
something("B.b", "bar");
}
}
}

35
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftUp_PostImage

@ -0,0 +1,35 @@
package org.eclipse.jgit.test.apply;
public class TestClass {
private void something(String prefix, String msg) {
System.out.println(prefix + ": " + msg);
}
public class A {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
public void methodC() {
something("A.c", "bar");
}
}
public class B {
public void methodA() {
something("B.a", "foo");
}
public void methodB() {
something("B.b", "bar");
}
}
}

32
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ShiftUp_PreImage

@ -0,0 +1,32 @@
package org.eclipse.jgit.test.apply;
public class TestClass {
private void something(String prefix, String msg) {
System.out.println(prefix + ": " + msg);
}
public class A {
public void methodA() {
something("A.a", "foo");
}
public void methodB() {
something("A.b", "bar");
}
}
public class B {
public void methodA() {
something("B.a", "foo");
}
public void methodB() {
something("B.b", "bar");
}
}
}

42
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2011, 2012, IBM Corporation and others. and others * Copyright (C) 2011, 2020 IBM Corporation and others
* *
* This program and the accompanying materials are made available under the * This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at * terms of the Eclipse Distribution License v. 1.0 which is available at
@ -280,6 +280,46 @@ public class ApplyCommandTest extends RepositoryTestCase {
b.getString(0, b.size(), false)); b.getString(0, b.size(), false));
} }
@Test
public void testShiftUp() throws Exception {
ApplyResult result = init("ShiftUp");
assertEquals(1, result.getUpdatedFiles().size());
assertEquals(new File(db.getWorkTree(), "ShiftUp"),
result.getUpdatedFiles().get(0));
checkFile(new File(db.getWorkTree(), "ShiftUp"),
b.getString(0, b.size(), false));
}
@Test
public void testShiftUp2() throws Exception {
ApplyResult result = init("ShiftUp2");
assertEquals(1, result.getUpdatedFiles().size());
assertEquals(new File(db.getWorkTree(), "ShiftUp2"),
result.getUpdatedFiles().get(0));
checkFile(new File(db.getWorkTree(), "ShiftUp2"),
b.getString(0, b.size(), false));
}
@Test
public void testShiftDown() throws Exception {
ApplyResult result = init("ShiftDown");
assertEquals(1, result.getUpdatedFiles().size());
assertEquals(new File(db.getWorkTree(), "ShiftDown"),
result.getUpdatedFiles().get(0));
checkFile(new File(db.getWorkTree(), "ShiftDown"),
b.getString(0, b.size(), false));
}
@Test
public void testShiftDown2() throws Exception {
ApplyResult result = init("ShiftDown2");
assertEquals(1, result.getUpdatedFiles().size());
assertEquals(new File(db.getWorkTree(), "ShiftDown2"),
result.getUpdatedFiles().get(0));
checkFile(new File(db.getWorkTree(), "ShiftDown2"),
b.getString(0, b.size(), false));
}
private static byte[] readFile(String patchFile) throws IOException { private static byte[] readFile(String patchFile) throws IOException {
final InputStream in = getTestResource(patchFile); final InputStream in = getTestResource(patchFile);
if (in == null) { if (in == null) {

166
org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2011, 2012, IBM Corporation and others. and others * Copyright (C) 2011, 2020 IBM Corporation and others
* *
* This program and the accompanying materials are made available under the * This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at * terms of the Eclipse Distribution License v. 1.0 which is available at
@ -9,18 +9,15 @@
*/ */
package org.eclipse.jgit.api; package org.eclipse.jgit.api;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer; import java.io.Writer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.GitAPIException;
@ -168,71 +165,156 @@ public class ApplyCommand extends GitCommand<ApplyResult> {
for (int i = 0; i < rt.size(); i++) for (int i = 0; i < rt.size(); i++)
oldLines.add(rt.getString(i)); oldLines.add(rt.getString(i));
List<String> newLines = new ArrayList<>(oldLines); List<String> newLines = new ArrayList<>(oldLines);
int afterLastHunk = 0;
int lineNumberShift = 0;
int lastHunkNewLine = -1;
for (HunkHeader hh : fh.getHunks()) { for (HunkHeader hh : fh.getHunks()) {
// We assume hunks to be ordered
if (hh.getNewStartLine() <= lastHunkNewLine) {
throw new PatchApplyException(MessageFormat
.format(JGitText.get().patchApplyException, hh));
}
lastHunkNewLine = hh.getNewStartLine();
byte[] b = new byte[hh.getEndOffset() - hh.getStartOffset()]; byte[] b = new byte[hh.getEndOffset() - hh.getStartOffset()];
System.arraycopy(hh.getBuffer(), hh.getStartOffset(), b, 0, System.arraycopy(hh.getBuffer(), hh.getStartOffset(), b, 0,
b.length); b.length);
RawText hrt = new RawText(b); RawText hrt = new RawText(b);
List<String> hunkLines = new ArrayList<>(hrt.size()); List<String> hunkLines = new ArrayList<>(hrt.size());
for (int i = 0; i < hrt.size(); i++) for (int i = 0; i < hrt.size(); i++) {
hunkLines.add(hrt.getString(i)); hunkLines.add(hrt.getString(i));
int pos = 0; }
for (int j = 1; j < hunkLines.size(); j++) {
if (hh.getNewStartLine() == 0) {
// Must be the single hunk for clearing all content
if (fh.getHunks().size() == 1
&& canApplyAt(hunkLines, newLines, 0)) {
newLines.clear();
break;
}
throw new PatchApplyException(MessageFormat
.format(JGitText.get().patchApplyException, hh));
}
// Hunk lines as reported by the hunk may be off, so don't rely on
// them.
int applyAt = hh.getNewStartLine() - 1 + lineNumberShift;
// But they definitely should not go backwards.
if (applyAt < afterLastHunk && lineNumberShift < 0) {
applyAt = hh.getNewStartLine() - 1;
lineNumberShift = 0;
}
if (applyAt < afterLastHunk) {
throw new PatchApplyException(MessageFormat
.format(JGitText.get().patchApplyException, hh));
}
boolean applies = false;
int oldLinesInHunk = hh.getLinesContext()
+ hh.getOldImage().getLinesDeleted();
if (oldLinesInHunk <= 1) {
// Don't shift hunks without context lines. Just try the
// position corrected by the current lineNumberShift, and if
// that fails, the position recorded in the hunk header.
applies = canApplyAt(hunkLines, newLines, applyAt);
if (!applies && lineNumberShift != 0) {
applyAt = hh.getNewStartLine() - 1;
applies = applyAt >= afterLastHunk
&& canApplyAt(hunkLines, newLines, applyAt);
}
} else {
int maxShift = applyAt - afterLastHunk;
for (int shift = 0; shift <= maxShift; shift++) {
if (canApplyAt(hunkLines, newLines, applyAt - shift)) {
applies = true;
applyAt -= shift;
break;
}
}
if (!applies) {
// Try shifting the hunk downwards
applyAt = hh.getNewStartLine() - 1 + lineNumberShift;
maxShift = newLines.size() - applyAt - oldLinesInHunk;
for (int shift = 1; shift <= maxShift; shift++) {
if (canApplyAt(hunkLines, newLines, applyAt + shift)) {
applies = true;
applyAt += shift;
break;
}
}
}
}
if (!applies) {
throw new PatchApplyException(MessageFormat
.format(JGitText.get().patchApplyException, hh));
}
// Hunk applies at applyAt. Apply it, and update afterLastHunk and
// lineNumberShift
lineNumberShift = applyAt - hh.getNewStartLine() + 1;
int sz = hunkLines.size();
for (int j = 1; j < sz; j++) {
String hunkLine = hunkLines.get(j); String hunkLine = hunkLines.get(j);
switch (hunkLine.charAt(0)) { switch (hunkLine.charAt(0)) {
case ' ': case ' ':
if (!newLines.get(hh.getNewStartLine() - 1 + pos).equals( applyAt++;
hunkLine.substring(1))) {
throw new PatchApplyException(MessageFormat.format(
JGitText.get().patchApplyException, hh));
}
pos++;
break; break;
case '-': case '-':
if (hh.getNewStartLine() == 0) { newLines.remove(applyAt);
newLines.clear();
} else {
if (!newLines.get(hh.getNewStartLine() - 1 + pos)
.equals(hunkLine.substring(1))) {
throw new PatchApplyException(MessageFormat.format(
JGitText.get().patchApplyException, hh));
}
newLines.remove(hh.getNewStartLine() - 1 + pos);
}
break; break;
case '+': case '+':
newLines.add(hh.getNewStartLine() - 1 + pos, newLines.add(applyAt++, hunkLine.substring(1));
hunkLine.substring(1)); break;
pos++; default:
break; break;
} }
} }
afterLastHunk = applyAt;
} }
if (!isNoNewlineAtEndOfFile(fh)) if (!isNoNewlineAtEndOfFile(fh)) {
newLines.add(""); //$NON-NLS-1$ newLines.add(""); //$NON-NLS-1$
if (!rt.isMissingNewlineAtEnd()) }
if (!rt.isMissingNewlineAtEnd()) {
oldLines.add(""); //$NON-NLS-1$ oldLines.add(""); //$NON-NLS-1$
if (!isChanged(oldLines, newLines))
return; // don't touch the file
StringBuilder sb = new StringBuilder();
for (String l : newLines) {
// don't bother handling line endings - if it was windows, the \r is
// still there!
sb.append(l).append('\n');
} }
if (sb.length() > 0) { if (!isChanged(oldLines, newLines)) {
sb.deleteCharAt(sb.length() - 1); return; // Don't touch the file
} }
try (Writer fw = new OutputStreamWriter(new FileOutputStream(f), try (Writer fw = Files.newBufferedWriter(f.toPath())) {
UTF_8)) { for (Iterator<String> l = newLines.iterator(); l.hasNext();) {
fw.write(sb.toString()); 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');
}
}
} }
getRepository().getFS().setExecute(f, fh.getNewMode() == FileMode.EXECUTABLE_FILE); getRepository().getFS().setExecute(f, fh.getNewMode() == FileMode.EXECUTABLE_FILE);
} }
private boolean canApplyAt(List<String> hunkLines, List<String> newLines,
int line) {
int sz = hunkLines.size();
int limit = newLines.size();
int pos = line;
for (int j = 1; j < sz; j++) {
String hunkLine = hunkLines.get(j);
switch (hunkLine.charAt(0)) {
case ' ':
case '-':
if (pos >= limit
|| !newLines.get(pos).equals(hunkLine.substring(1))) {
return false;
}
pos++;
break;
default:
break;
}
}
return true;
}
private static boolean isChanged(List<String> ol, List<String> nl) { private static boolean isChanged(List<String> ol, List<String> nl) {
if (ol.size() != nl.size()) if (ol.size() != nl.size())
return true; return true;

Loading…
Cancel
Save