PatchApplier: Check for existence of src/dest files before any operation

Change-Id: Ia3ec0ce1af65114b48669157a934f70f1e22fd37
Bug: Google b/271474227
This commit is contained in:
Nitzan Gur-Furman 2023-03-30 12:12:46 +02:00
parent bf9b704d19
commit 903645835b
4 changed files with 131 additions and 23 deletions

View File

@ -93,12 +93,16 @@ protected void init(String aName, boolean preExists, boolean postExists)
} }
protected void initPreImage(String aName) throws Exception { protected void initPreImage(String aName) throws Exception {
File f = new File(db.getWorkTree(), aName);
preImage = IO preImage = IO
.readWholeStream(getTestResource(aName + "_PreImage"), 0) .readWholeStream(getTestResource(aName + "_PreImage"), 0)
.array(); .array();
addFile(aName, preImage);
}
protected void addFile(String aName, byte[] b) throws Exception {
File f = new File(db.getWorkTree(), aName);
Files.write(f.toPath(), b);
try (Git git = new Git(db)) { try (Git git = new Git(db)) {
Files.write(f.toPath(), preImage);
git.add().addFilepattern(aName).call(); git.add().addFilepattern(aName).call();
} }
} }
@ -372,6 +376,68 @@ public void testShiftDown2() throws Exception {
verifyChange(result, "ShiftDown2"); verifyChange(result, "ShiftDown2");
} }
@Test
public void testAddAlreadyExistingFile() throws Exception {
addFile("M1", "existing content".getBytes());
init("M1", false, false);
Result result = applyPatch();
assertEquals(1, result.getErrors().size());
assertEquals(0, result.getPaths().size());
}
@Test
public void testDeleteNonexistentFile() throws Exception {
init("NonASCIIDel", false, false);
Result result = applyPatch();
assertEquals(1, result.getErrors().size());
assertEquals(0, result.getPaths().size());
}
@Test
public void testModifyNonexistentFile() throws Exception {
init("ShiftDown", false, true);
Result result = applyPatch();
assertEquals(1, result.getErrors().size());
assertEquals(0, result.getPaths().size());
}
@Test
public void testRenameNonexistentFile() throws Exception {
init("RenameNoHunks", false, true);
Result result = applyPatch();
assertEquals(1, result.getErrors().size());
assertEquals(0, result.getPaths().size());
}
@Test
public void testCopyNonexistentFile() throws Exception {
init("CopyWithHunks", false, true);
Result result = applyPatch();
assertEquals(1, result.getErrors().size());
assertEquals(0, result.getPaths().size());
}
@Test
public void testCopyOnTopAlreadyExistingFile() throws Exception {
addFile("CopyResult", "existing content".getBytes());
init("CopyWithHunks", true, false);
Result result = applyPatch();
assertEquals(1, result.getErrors().size());
assertEquals(0, result.getPaths().size());
}
@Test @Test
public void testDoesNotAffectUnrelatedFiles() throws Exception { public void testDoesNotAffectUnrelatedFiles() throws Exception {
initPreImage("Unaffected"); initPreImage("Unaffected");

View File

@ -20,6 +20,9 @@ applyBinaryPatchTypeNotSupported=Couldn't apply binary patch of type {0}
applyTextPatchCannotApplyHunk=Hunk cannot be applied applyTextPatchCannotApplyHunk=Hunk cannot be applied
applyTextPatchSingleClearingHunk=Expected a single hunk for clearing all content applyTextPatchSingleClearingHunk=Expected a single hunk for clearing all content
applyBinaryResultOidWrong=Result of binary patch for file {0} has wrong OID applyBinaryResultOidWrong=Result of binary patch for file {0} has wrong OID
applyPatchWithoutSourceOnAlreadyExistingSource=Cannot perform {0} action on an existing file
applyPatchWithCreationOverAlreadyExistingDestination=Cannot perform {0} action which overrides an existing file
applyPatchWithSourceOnNonExistentSource=Cannot perform {0} action on a non-existent file
applyTextPatchUnorderedHunkApplications=Current hunk must be applied after the last hunk applyTextPatchUnorderedHunkApplications=Current hunk must be applied after the last hunk
applyTextPatchUnorderedHunks=Got unordered hunks applyTextPatchUnorderedHunks=Got unordered hunks
applyingCommit=Applying {0} applyingCommit=Applying {0}

View File

@ -46,6 +46,9 @@ public static JGitText get() {
/***/ public String applyBinaryOidTooShort; /***/ public String applyBinaryOidTooShort;
/***/ public String applyBinaryPatchTypeNotSupported; /***/ public String applyBinaryPatchTypeNotSupported;
/***/ public String applyBinaryResultOidWrong; /***/ public String applyBinaryResultOidWrong;
/***/ public String applyPatchWithoutSourceOnAlreadyExistingSource;
/***/ public String applyPatchWithCreationOverAlreadyExistingDestination;
/***/ public String applyPatchWithSourceOnNonExistentSource;
/***/ public String applyTextPatchCannotApplyHunk; /***/ public String applyTextPatchCannotApplyHunk;
/***/ public String applyTextPatchSingleClearingHunk; /***/ public String applyTextPatchSingleClearingHunk;
/***/ public String applyTextPatchUnorderedHunkApplications; /***/ public String applyTextPatchUnorderedHunkApplications;

View File

@ -9,6 +9,11 @@
*/ */
package org.eclipse.jgit.patch; package org.eclipse.jgit.patch;
import static org.eclipse.jgit.diff.DiffEntry.ChangeType.ADD;
import static org.eclipse.jgit.diff.DiffEntry.ChangeType.COPY;
import static org.eclipse.jgit.diff.DiffEntry.ChangeType.DELETE;
import static org.eclipse.jgit.diff.DiffEntry.ChangeType.MODIFY;
import static org.eclipse.jgit.diff.DiffEntry.ChangeType.RENAME;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -257,32 +262,33 @@ public Result applyPatch(Patch p) throws IOException {
Set<String> modifiedPaths = new HashSet<>(); Set<String> modifiedPaths = new HashSet<>();
for (FileHeader fh : p.getFiles()) { for (FileHeader fh : p.getFiles()) {
ChangeType type = fh.getChangeType(); ChangeType type = fh.getChangeType();
File src = getFile(fh.getOldPath());
File dest = getFile(fh.getNewPath());
if (!verifyExistence(fh, src, dest, result)) {
continue;
}
switch (type) { switch (type) {
case ADD: { case ADD: {
File f = getFile(fh.getNewPath()); if (dest != null) {
if (f != null) { FileUtils.mkdirs(dest.getParentFile(), true);
FileUtils.mkdirs(f.getParentFile(), true); FileUtils.createNewFile(dest);
FileUtils.createNewFile(f);
} }
apply(fh.getNewPath(), dirCache, dirCacheBuilder, f, fh, result); apply(fh.getNewPath(), dirCache, dirCacheBuilder, dest, fh, result);
} }
break; break;
case MODIFY: case MODIFY: {
apply(fh.getOldPath(), dirCache, dirCacheBuilder, apply(fh.getOldPath(), dirCache, dirCacheBuilder, src, fh, result);
getFile(fh.getOldPath()), fh, result);
break; break;
case DELETE: }
case DELETE: {
if (!inCore()) { if (!inCore()) {
File old = getFile(fh.getOldPath()); if (!src.delete())
if (!old.delete())
throw new IOException(MessageFormat.format( throw new IOException(MessageFormat.format(
JGitText.get().cannotDeleteFile, old)); JGitText.get().cannotDeleteFile, src));
} }
break; break;
}
case RENAME: { case RENAME: {
File src = getFile(fh.getOldPath());
File dest = getFile(fh.getNewPath());
if (!inCore()) { if (!inCore()) {
/* /*
* this is odd: we rename the file on the FS, but * this is odd: we rename the file on the FS, but
@ -299,9 +305,7 @@ public Result applyPatch(Patch p) throws IOException {
break; break;
} }
case COPY: { case COPY: {
File dest = getFile(fh.getNewPath());
if (!inCore()) { if (!inCore()) {
File src = getFile(fh.getOldPath());
FileUtils.mkdirs(dest.getParentFile(), true); FileUtils.mkdirs(dest.getParentFile(), true);
Files.copy(src.toPath(), dest.toPath()); Files.copy(src.toPath(), dest.toPath());
} }
@ -309,10 +313,10 @@ public Result applyPatch(Patch p) throws IOException {
break; break;
} }
} }
if (fh.getChangeType() != ChangeType.DELETE) if (fh.getChangeType() != DELETE)
modifiedPaths.add(fh.getNewPath()); modifiedPaths.add(fh.getNewPath());
if (fh.getChangeType() != ChangeType.COPY if (fh.getChangeType() != COPY
&& fh.getChangeType() != ChangeType.ADD) && fh.getChangeType() != ADD)
modifiedPaths.add(fh.getOldPath()); modifiedPaths.add(fh.getOldPath());
} }
@ -368,6 +372,38 @@ private TreeWalk getTreeWalkForFile(String path, DirCache cache)
return walk; return walk;
} }
private boolean fileExists(String path, @Nullable File f)
throws IOException {
if (f != null) {
return f.exists();
}
return inCore() && TreeWalk.forPath(repo, path, beforeTree) != null;
}
private boolean verifyExistence(FileHeader fh, File src, File dest,
Result result) throws IOException {
boolean isValid = true;
boolean srcShouldExist = List.of(MODIFY, DELETE, RENAME, COPY)
.contains(fh.getChangeType());
boolean destShouldNotExist = List.of(ADD, RENAME, COPY)
.contains(fh.getChangeType());
if (srcShouldExist != fileExists(fh.getOldPath(), src)) {
result.addError(MessageFormat.format(srcShouldExist
? JGitText.get().applyPatchWithSourceOnNonExistentSource
: JGitText
.get().applyPatchWithoutSourceOnAlreadyExistingSource,
fh.getPatchType()), fh.getOldPath(), null);
isValid = false;
}
if (destShouldNotExist && fileExists(fh.getNewPath(), dest)) {
result.addError(MessageFormat.format(JGitText
.get().applyPatchWithCreationOverAlreadyExistingDestination,
fh.getPatchType()), fh.getNewPath(), null);
isValid = false;
}
return isValid;
}
private static final int FILE_TREE_INDEX = 1; private static final int FILE_TREE_INDEX = 1;
/** /**
@ -681,7 +717,7 @@ private boolean checkOid(ObjectId baseId, ObjectId id, ChangeType type, File f,
boolean hashOk = false; boolean hashOk = false;
if (id != null) { if (id != null) {
hashOk = baseId.equals(id); hashOk = baseId.equals(id);
if (!hashOk && ChangeType.ADD.equals(type) if (!hashOk && ADD.equals(type)
&& ObjectId.zeroId().equals(baseId)) { && ObjectId.zeroId().equals(baseId)) {
// We create a new file. The OID of an empty file is not the // We create a new file. The OID of an empty file is not the
// zero id! // zero id!