Skip to content

Commit

Permalink
fix(blueprint): Detect binary files during three way merges (#410)
Browse files Browse the repository at this point in the history
The three way merge is text based and won't produce meaningful results
when run on binary files. Use a heuristic similar to git of checking the
first 3000 bytes of the input files for null bytes. If there are any null
bytes, consider the file to be binary and fall back to returning the proposed
file.
  • Loading branch information
aggagen authored Oct 13, 2023
1 parent a604a84 commit e2c02e4
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 2 deletions.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,29 @@ export class MergeStrategies {
* A strategy that performs a three way merge between the existing, proposed, and common
* ancestor files. The resolved file may contain conflict markers if the files can not be
* cleanly merged.
*
* The provided files' contents must be UTF-8 encoded. The strategy attempts to detect
* if the input files are binary and will return the proposedFile if any of the files
* are binary.
*/
public static threeWayMerge: StrategyFunction = function threeWayMerge(
commonAncestorFile: ContextFile | undefined,
existingFile: ContextFile | undefined,
proposedFile: ContextFile | undefined,
) {
// The three way merge algorithm below is text based and won't produce a meaningful result
// when merging binary files. Default to the proposed file when we detect one of the files is
// binary:
if (isBinaryFile(proposedFile) || isBinaryFile(existingFile) || isBinaryFile(commonAncestorFile)) {
return proposedFile;
}

if (!existingFile && proposedFile && commonAncestorFile && proposedFile.buffer.equals(commonAncestorFile.buffer)) {
return undefined;
}

if (!proposedFile && existingFile && commonAncestorFile && existingFile.buffer.equals(commonAncestorFile.buffer)) {
return undefined;
}

if (!existingFile && !proposedFile) {
return undefined;
}
Expand Down Expand Up @@ -97,3 +106,24 @@ export class MergeStrategies {
};
};
}

const BINARY_FILE_HEURISTIC_MAX_LENGTH = 8000;

/**
* Returns whether the given ContextFile is binary or not using a heuristic based
* approach of checking for null bytes in the beginning of the file contents.
*/
function isBinaryFile(file?: ContextFile): boolean {
if (!file) {
return false;
}

const searchLength = Math.min(file.buffer.length, BINARY_FILE_HEURISTIC_MAX_LENGTH);
for (let i = 0; i < searchLength; i++) {
if (file.buffer[i] === 0) {
return true;
}
}

return false;
}

0 comments on commit e2c02e4

Please sign in to comment.