mirror of
https://github.com/Kong/insomnia
synced 2024-11-12 17:26:32 +00:00
131 lines
2.9 KiB
JavaScript
131 lines
2.9 KiB
JavaScript
|
// @flow
|
||
|
import crypto from 'crypto';
|
||
|
|
||
|
type InsertOperation = {|
|
||
|
type: 'INSERT',
|
||
|
content: string,
|
||
|
|};
|
||
|
|
||
|
type CopyOperation = {|
|
||
|
type: 'COPY',
|
||
|
start: number,
|
||
|
len: number,
|
||
|
|};
|
||
|
|
||
|
export type Operation = InsertOperation | CopyOperation;
|
||
|
|
||
|
type Block = {|
|
||
|
start: number,
|
||
|
len: number,
|
||
|
hash: string,
|
||
|
|};
|
||
|
|
||
|
export function diff(source: string, target: string, blockSize: number): Array<Operation> {
|
||
|
const operations: Array<Operation> = [];
|
||
|
|
||
|
const sourceBlockMap = getBlockMap(source, blockSize);
|
||
|
|
||
|
// Iterate over source blocks in order and match them to target
|
||
|
let lastTargetMatch = 0;
|
||
|
for (let targetPosition = 0; targetPosition < target.length; ) {
|
||
|
const targetBlock = getBlock(target, targetPosition, blockSize);
|
||
|
const sourceBlocks = sourceBlockMap[targetBlock.hash] || [];
|
||
|
|
||
|
if (sourceBlocks.length === 0) {
|
||
|
targetPosition++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// TODO: Try all blocks
|
||
|
const sourceBlock = sourceBlocks[0];
|
||
|
|
||
|
// Try to match as far as possible
|
||
|
let sourceIndex = sourceBlock.start + sourceBlock.len;
|
||
|
let targetIndex = targetBlock.start + targetBlock.len;
|
||
|
|
||
|
while (targetIndex < target.length && sourceIndex < source.length) {
|
||
|
if (source[sourceIndex] === target[targetIndex]) {
|
||
|
targetIndex++;
|
||
|
sourceIndex++;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
while (
|
||
|
source[sourceIndex++] === target[targetIndex] &&
|
||
|
targetIndex < target.length &&
|
||
|
sourceIndex < source.length
|
||
|
) {
|
||
|
targetIndex++;
|
||
|
}
|
||
|
|
||
|
sourceIndex--;
|
||
|
|
||
|
// Found unknown bytes, INSERT them
|
||
|
if (targetBlock.start > lastTargetMatch) {
|
||
|
operations.push({
|
||
|
type: 'INSERT',
|
||
|
content: target.slice(lastTargetMatch, targetBlock.start),
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Source block found in target
|
||
|
operations.push({
|
||
|
type: 'COPY',
|
||
|
start: sourceBlock.start,
|
||
|
len: sourceIndex - sourceBlock.start,
|
||
|
});
|
||
|
|
||
|
targetPosition = lastTargetMatch = targetIndex;
|
||
|
}
|
||
|
|
||
|
// Add the target suffix if there's still some left
|
||
|
if (lastTargetMatch < target.length) {
|
||
|
operations.push({
|
||
|
type: 'INSERT',
|
||
|
content: target.slice(lastTargetMatch),
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return operations;
|
||
|
}
|
||
|
|
||
|
function getBlock(value: string, start: number, blockSize: number): Block {
|
||
|
if (start >= value.length) {
|
||
|
throw new Error('Invalid block index');
|
||
|
}
|
||
|
|
||
|
const blockSlice = value.slice(start, start + blockSize);
|
||
|
return {
|
||
|
start,
|
||
|
len: blockSlice.length,
|
||
|
hash: crypto
|
||
|
.createHash('sha1')
|
||
|
.update(blockSlice)
|
||
|
.digest('hex'),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function getBlockMap(value: string, blockSize: number): { [string]: Block } {
|
||
|
const map = {};
|
||
|
|
||
|
for (let i = 0; i < value.length; ) {
|
||
|
const block = getBlock(value, i, blockSize);
|
||
|
|
||
|
if (map[block.hash]) {
|
||
|
map[block.hash].push(block);
|
||
|
} else {
|
||
|
map[block.hash] = [block];
|
||
|
}
|
||
|
|
||
|
i += block.len;
|
||
|
}
|
||
|
|
||
|
return map;
|
||
|
}
|
||
|
|
||
|
export const __internal = {
|
||
|
getBlockMap,
|
||
|
};
|