Added support of patch creation from files without a newline at the end

The simplest solution was to only include a newline at the end of the patch file if the source file ended with a newline. This is used to inform the merge code as to whether to add the newline to the end of the resulting file. Without this, the checksums do not match (and therefore previously the complete file would have been included as a result).
This commit is contained in:
CalDescent 2021-12-23 16:59:49 +00:00
parent 361dc79ede
commit 2d853e5a2f
3 changed files with 45 additions and 8 deletions

View File

@ -10,6 +10,7 @@ import org.apache.logging.log4j.Logger;
import org.qortal.crypto.Crypto;
import org.qortal.repository.DataException;
import org.qortal.settings.Settings;
import org.qortal.utils.FilesystemUtils;
import java.io.BufferedWriter;
import java.io.File;
@ -72,6 +73,9 @@ public class UnifiedDiffPatch {
List<String> original = FileUtils.readLines(before.toFile(), StandardCharsets.UTF_8);
List<String> revised = FileUtils.readLines(after.toFile(), StandardCharsets.UTF_8);
// Check if the original file ends with a newline
boolean endsWithNewline = FilesystemUtils.fileEndsWithNewline(before);
// Generate diff information
Patch<String> diff = DiffUtils.diff(original, revised);
@ -83,10 +87,14 @@ public class UnifiedDiffPatch {
// Write the diff to the destination directory
FileWriter fileWriter = new FileWriter(destination.toString(), true);
BufferedWriter writer = new BufferedWriter(fileWriter);
for (String line : unifiedDiff) {
for (int i=0; i<unifiedDiff.size(); i++) {
String line = unifiedDiff.get(i);
writer.append(line);
// Add a newline if this isn't the last line, or the original ended with a newline
if (i < unifiedDiff.size()-1 || endsWithNewline) {
writer.newLine();
}
}
writer.flush();
writer.close();
}
@ -173,6 +181,9 @@ public class UnifiedDiffPatch {
List<String> originalContents = FileUtils.readLines(originalPath.toFile(), StandardCharsets.UTF_8);
List<String> patchContents = FileUtils.readLines(patchPath.toFile(), StandardCharsets.UTF_8);
// Check if the patch file (and therefore the original file) ends with a newline
boolean endsWithNewline = FilesystemUtils.fileEndsWithNewline(patchPath);
// At first, parse the unified diff file and get the patch
Patch<String> patch = UnifiedDiffUtils.parseUnifiedDiff(patchContents);
@ -183,10 +194,16 @@ public class UnifiedDiffPatch {
// Write the patched file to the merge directory
FileWriter fileWriter = new FileWriter(mergePath.toString(), true);
BufferedWriter writer = new BufferedWriter(fileWriter);
for (String line : patchedContents) {
for (int i=0; i<patchedContents.size(); i++) {
String line = patchedContents.get(i);
LOGGER.info("Applying line: %s", line);
writer.append(line);
// Add a newline if this isn't the last line, or the original ended with a newline
if (i < patchedContents.size()-1 || endsWithNewline) {
LOGGER.info("Applying newline");
writer.newLine();
}
}
writer.flush();
writer.close();

View File

@ -6,6 +6,8 @@ import org.qortal.settings.Settings;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
public class FilesystemUtils {
@ -239,4 +241,23 @@ public class FilesystemUtils {
return data;
}
public static byte[] readFromFile(String filePath, long position, int size) throws IOException {
RandomAccessFile file = new RandomAccessFile(filePath, "r");
file.seek(position);
byte[] bytes = new byte[size];
file.read(bytes);
file.close();
return bytes;
}
public static String readUtf8StringFromFile(String filePath, long position, int size) throws IOException {
return new String(FilesystemUtils.readFromFile(filePath, position, size), StandardCharsets.UTF_8);
}
public static boolean fileEndsWithNewline(Path path) throws IOException {
long length = Files.size(path);
String lastCharacter = FilesystemUtils.readUtf8StringFromFile(path.toString(), length-1, 1);
return (lastCharacter.equals("\n") || lastCharacter.equals("\r"));
}
}

View File

@ -349,13 +349,12 @@ public class ArbitraryDataMergeTests extends Common {
assertTrue(Files.exists(patchFilePath));
byte[] patchDigest = Crypto.digest(patchFilePath.toFile());
// The patch file should be identical to file2, because we don't currently
// support arbitrary diff patches on files without trailing newlines
assertArrayEquals(patchDigest, file2Digest);
// Make sure that the patch file is different from file1
assertFalse(Arrays.equals(patchDigest, file1Digest));
// Make sure that the patch file is different from file2
assertFalse(Arrays.equals(patchDigest, file2Digest));
// Now merge the patch with the original path
ArbitraryDataCombiner combiner = new ArbitraryDataCombiner(tempDir1, patchPath, signature);
combiner.setShouldValidateHashes(true);