Merge "PatchApplier: Check for existence of src/dest files before any operation"
This commit is contained in:
commit
d174684273
|
@ -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");
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
switch (type) {
|
|
||||||
case ADD: {
|
|
||||||
File f = getFile(fh.getNewPath());
|
|
||||||
if (f != null) {
|
|
||||||
FileUtils.mkdirs(f.getParentFile(), true);
|
|
||||||
FileUtils.createNewFile(f);
|
|
||||||
}
|
|
||||||
apply(fh.getNewPath(), dirCache, dirCacheBuilder, f, fh, result);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MODIFY:
|
|
||||||
apply(fh.getOldPath(), dirCache, dirCacheBuilder,
|
|
||||||
getFile(fh.getOldPath()), fh, result);
|
|
||||||
break;
|
|
||||||
case DELETE:
|
|
||||||
if (!inCore()) {
|
|
||||||
File old = getFile(fh.getOldPath());
|
|
||||||
if (!old.delete())
|
|
||||||
throw new IOException(MessageFormat.format(
|
|
||||||
JGitText.get().cannotDeleteFile, old));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case RENAME: {
|
|
||||||
File src = getFile(fh.getOldPath());
|
File src = getFile(fh.getOldPath());
|
||||||
File dest = getFile(fh.getNewPath());
|
File dest = getFile(fh.getNewPath());
|
||||||
|
if (!verifyExistence(fh, src, dest, result)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (type) {
|
||||||
|
case ADD: {
|
||||||
|
if (dest != null) {
|
||||||
|
FileUtils.mkdirs(dest.getParentFile(), true);
|
||||||
|
FileUtils.createNewFile(dest);
|
||||||
|
}
|
||||||
|
apply(fh.getNewPath(), dirCache, dirCacheBuilder, dest, fh, result);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MODIFY: {
|
||||||
|
apply(fh.getOldPath(), dirCache, dirCacheBuilder, src, fh, result);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DELETE: {
|
||||||
|
if (!inCore()) {
|
||||||
|
if (!src.delete())
|
||||||
|
throw new IOException(MessageFormat.format(
|
||||||
|
JGitText.get().cannotDeleteFile, src));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RENAME: {
|
||||||
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!
|
||||||
|
|
Loading…
Reference in New Issue