@adecore/merge 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/blocks.d.ts CHANGED
@@ -6,4 +6,3 @@ export interface MergeBlock {
6
6
  theirs: string[];
7
7
  }
8
8
  export declare function splitBlocks(base: readonly string[], ours: readonly string[], theirs: readonly string[]): MergeBlock[];
9
- export declare function openBlocks(blocks: readonly MergeBlock[]): number;
package/dist/blocks.js CHANGED
@@ -76,7 +76,3 @@ export function splitBlocks(base, ours, theirs) {
76
76
  push(base.length, ours.length, theirs.length);
77
77
  return blocks;
78
78
  }
79
- /* The stretches a person still has to decide on. */
80
- export function openBlocks(blocks) {
81
- return blocks.filter((block) => block.kind === 'conflict').length;
82
- }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { diffLines, type Change } from './diff.ts';
2
- export { openBlocks, splitBlocks, type MergeBlock, type MergeBlockKind } from './blocks.ts';
3
- export { autoLines, bothLines, draftOf, fingerprint, sideLines, wandLines, type MergeDraft, type MergeSide, type MergeSpan } from './resolve.ts';
2
+ export { splitBlocks, type MergeBlock, type MergeBlockKind } from './blocks.ts';
3
+ export { bothLines, draftOf, fingerprint, sideLines, wandLines, type MergeDraft, type MergeSide, type MergeSpan } from './resolve.ts';
4
4
  export { joinLines, shapeOf, splitLines, type TextShape } from './text.ts';
5
5
  export { missingSides, patchFits } from './patch.ts';
6
6
  export { parsePatch, parseHunkHeader, type HunkHeader, type Hunk, type ParsedPatch, type PatchLine } from './patch-parser.ts';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { diffLines } from './diff.js';
2
- export { openBlocks, splitBlocks } from './blocks.js';
3
- export { autoLines, bothLines, draftOf, fingerprint, sideLines, wandLines } from './resolve.js';
2
+ export { splitBlocks } from './blocks.js';
3
+ export { bothLines, draftOf, fingerprint, sideLines, wandLines } from './resolve.js';
4
4
  export { joinLines, shapeOf, splitLines } from './text.js';
5
5
  export { missingSides, patchFits } from './patch.js';
6
6
  export { parsePatch, parseHunkHeader } from './patch-parser.js';
package/dist/patch.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { parsePatch } from './patch-parser.js';
1
2
  /* Which side a patch says the file is missing on, read from its header so no line of the file can pass for it. */
2
3
  export function missingSides(patch) {
3
4
  const start = patch.search(/^@@/m);
@@ -5,8 +6,11 @@ export function missingSides(patch) {
5
6
  return { old: /^--- \/dev\/null$/m.test(header), new: /^\+\+\+ \/dev\/null$/m.test(header) };
6
7
  }
7
8
  const HUNK_HEADER = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
8
- /* Git's --ignore-all-space may show either side's spacing on a context line. */
9
+ /* Git's --ignore-all-space may show either side's spacing on a context line. A patch cut off before its hunks end fits nothing. */
9
10
  export function patchFits(patch, oldText, newText, ignoreWhitespace) {
11
+ if (parsePatch(patch).partial) {
12
+ return false;
13
+ }
10
14
  const oldLines = oldText.split('\n');
11
15
  const newLines = newText.split('\n');
12
16
  const same = ignoreWhitespace
package/dist/resolve.d.ts CHANGED
@@ -2,7 +2,6 @@ import type { MergeBlock } from './blocks.ts';
2
2
  export type MergeSide = 'ours' | 'theirs';
3
3
  export declare function sideLines(block: MergeBlock, side: MergeSide): string[];
4
4
  export declare function bothLines(block: MergeBlock, first: MergeSide): string[];
5
- export declare function autoLines(block: MergeBlock): string[] | null;
6
5
  export declare function wandLines(block: MergeBlock): string[] | null;
7
6
  export declare function fingerprint(block: MergeBlock): string;
8
7
  export interface MergeSpan {
package/dist/resolve.js CHANGED
@@ -6,7 +6,7 @@ export function bothLines(block, first) {
6
6
  return first === 'ours' ? [...block.ours, ...block.theirs] : [...block.theirs, ...block.ours];
7
7
  }
8
8
  /* What a block is worth without anyone choosing: the side that changed, or the change both made. */
9
- export function autoLines(block) {
9
+ function autoLines(block) {
10
10
  switch (block.kind) {
11
11
  case 'stable':
12
12
  case 'ours':
@@ -18,16 +18,6 @@ export function autoLines(block) {
18
18
  return null;
19
19
  }
20
20
  }
21
- function squashed(lines) {
22
- return lines
23
- .map((line) => {
24
- const body = line.trim();
25
- // Indentation is meaning in Python, YAML or a Makefile, so only a blank line loses its own.
26
- const indent = body === '' ? '' : line.slice(0, line.length - line.trimStart().length);
27
- return `${indent}${body.replace(/\s+/g, ' ')}`;
28
- })
29
- .join('\n');
30
- }
31
21
  /* Where `inner` starts in `outer` as one run of lines, or -1. */
32
22
  function runIn(outer, inner) {
33
23
  for (let start = 0; start + inner.length <= outer.length; start += 1) {
@@ -51,14 +41,14 @@ function covers(base, outer, inner) {
51
41
  }
52
42
  /*
53
43
  * What the wand makes of a conflict, or null for one that needs a person after all. It closes only
54
- * sides that differ in whitespace inside or after a line, and a side that already holds every line
55
- * of the other without putting back what the other deleted. Anything else is a choice it never guesses.
44
+ * identical sides and a side that already holds every line of the other without putting back what
45
+ * the other deleted. Whitespace keeps its meaning because this function has no language context.
56
46
  */
57
47
  export function wandLines(block) {
58
48
  if (block.kind !== 'conflict') {
59
49
  return autoLines(block);
60
50
  }
61
- if (squashed(block.ours) === squashed(block.theirs)) {
51
+ if (block.ours.length === block.theirs.length && block.ours.every((line, index) => line === block.theirs[index])) {
62
52
  return [...block.ours];
63
53
  }
64
54
  if (covers(block.base, block.ours, block.theirs)) {
@@ -94,7 +84,10 @@ export function draftOf(blocks, answered = new Map()) {
94
84
  const spans = [];
95
85
  for (const [index, block] of blocks.entries()) {
96
86
  const from = lines.length;
97
- lines.push(...(answered.get(index) ?? autoLines(block) ?? block.ours));
87
+ // One push per line: a block of a few hundred thousand lines is more arguments than V8 takes in one call.
88
+ for (const line of answered.get(index) ?? autoLines(block) ?? block.ours) {
89
+ lines.push(line);
90
+ }
98
91
  spans.push({ block: index, kind: block.kind, from, to: lines.length });
99
92
  }
100
93
  return { lines, spans };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/merge",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "Pure line diffs, three-way merge blocks and conflict resolution.",
5
5
  "license": "FSL-1.1-MIT",
6
6
  "author": "Bas Milius",
@@ -19,6 +19,7 @@
19
19
  "dist",
20
20
  "src",
21
21
  "!src/**/*.test.ts",
22
+ "!src/**/__snapshots__",
22
23
  "!src/fixtures"
23
24
  ],
24
25
  "exports": {
package/src/blocks.ts CHANGED
@@ -97,8 +97,3 @@ export function splitBlocks(base: readonly string[], ours: readonly string[], th
97
97
  push(base.length, ours.length, theirs.length);
98
98
  return blocks;
99
99
  }
100
-
101
- /* The stretches a person still has to decide on. */
102
- export function openBlocks(blocks: readonly MergeBlock[]): number {
103
- return blocks.filter((block) => block.kind === 'conflict').length;
104
- }
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { diffLines, type Change } from './diff.ts';
2
- export { openBlocks, splitBlocks, type MergeBlock, type MergeBlockKind } from './blocks.ts';
3
- export { autoLines, bothLines, draftOf, fingerprint, sideLines, wandLines, type MergeDraft, type MergeSide, type MergeSpan } from './resolve.ts';
2
+ export { splitBlocks, type MergeBlock, type MergeBlockKind } from './blocks.ts';
3
+ export { bothLines, draftOf, fingerprint, sideLines, wandLines, type MergeDraft, type MergeSide, type MergeSpan } from './resolve.ts';
4
4
  export { joinLines, shapeOf, splitLines, type TextShape } from './text.ts';
5
5
  export { missingSides, patchFits } from './patch.ts';
6
6
  export { parsePatch, parseHunkHeader, type HunkHeader, type Hunk, type ParsedPatch, type PatchLine } from './patch-parser.ts';
package/src/patch.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { parsePatch } from './patch-parser.ts';
2
+
1
3
  /* Which side a patch says the file is missing on, read from its header so no line of the file can pass for it. */
2
4
  export function missingSides(patch: string): { old: boolean; new: boolean } {
3
5
  const start = patch.search(/^@@/m);
@@ -7,8 +9,11 @@ export function missingSides(patch: string): { old: boolean; new: boolean } {
7
9
 
8
10
  const HUNK_HEADER = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
9
11
 
10
- /* Git's --ignore-all-space may show either side's spacing on a context line. */
12
+ /* Git's --ignore-all-space may show either side's spacing on a context line. A patch cut off before its hunks end fits nothing. */
11
13
  export function patchFits(patch: string, oldText: string, newText: string, ignoreWhitespace: boolean): boolean {
14
+ if (parsePatch(patch).partial) {
15
+ return false;
16
+ }
12
17
  const oldLines = oldText.split('\n');
13
18
  const newLines = newText.split('\n');
14
19
  const same = ignoreWhitespace
package/src/resolve.ts CHANGED
@@ -12,7 +12,7 @@ export function bothLines(block: MergeBlock, first: MergeSide): string[] {
12
12
  }
13
13
 
14
14
  /* What a block is worth without anyone choosing: the side that changed, or the change both made. */
15
- export function autoLines(block: MergeBlock): string[] | null {
15
+ function autoLines(block: MergeBlock): string[] | null {
16
16
  switch (block.kind) {
17
17
  case 'stable':
18
18
  case 'ours':
@@ -25,17 +25,6 @@ export function autoLines(block: MergeBlock): string[] | null {
25
25
  }
26
26
  }
27
27
 
28
- function squashed(lines: readonly string[]): string {
29
- return lines
30
- .map((line) => {
31
- const body = line.trim();
32
- // Indentation is meaning in Python, YAML or a Makefile, so only a blank line loses its own.
33
- const indent = body === '' ? '' : line.slice(0, line.length - line.trimStart().length);
34
- return `${indent}${body.replace(/\s+/g, ' ')}`;
35
- })
36
- .join('\n');
37
- }
38
-
39
28
  /* Where `inner` starts in `outer` as one run of lines, or -1. */
40
29
  function runIn(outer: readonly string[], inner: readonly string[]): number {
41
30
  for (let start = 0; start + inner.length <= outer.length; start += 1) {
@@ -61,14 +50,14 @@ function covers(base: readonly string[], outer: readonly string[], inner: readon
61
50
 
62
51
  /*
63
52
  * What the wand makes of a conflict, or null for one that needs a person after all. It closes only
64
- * sides that differ in whitespace inside or after a line, and a side that already holds every line
65
- * of the other without putting back what the other deleted. Anything else is a choice it never guesses.
53
+ * identical sides and a side that already holds every line of the other without putting back what
54
+ * the other deleted. Whitespace keeps its meaning because this function has no language context.
66
55
  */
67
56
  export function wandLines(block: MergeBlock): string[] | null {
68
57
  if (block.kind !== 'conflict') {
69
58
  return autoLines(block);
70
59
  }
71
- if (squashed(block.ours) === squashed(block.theirs)) {
60
+ if (block.ours.length === block.theirs.length && block.ours.every((line, index) => line === block.theirs[index])) {
72
61
  return [...block.ours];
73
62
  }
74
63
  if (covers(block.base, block.ours, block.theirs)) {
@@ -119,7 +108,10 @@ export function draftOf(blocks: readonly MergeBlock[], answered: ReadonlyMap<num
119
108
  const spans: MergeSpan[] = [];
120
109
  for (const [index, block] of blocks.entries()) {
121
110
  const from = lines.length;
122
- lines.push(...(answered.get(index) ?? autoLines(block) ?? block.ours));
111
+ // One push per line: a block of a few hundred thousand lines is more arguments than V8 takes in one call.
112
+ for (const line of answered.get(index) ?? autoLines(block) ?? block.ours) {
113
+ lines.push(line);
114
+ }
123
115
  spans.push({ block: index, kind: block.kind, from, to: lines.length });
124
116
  }
125
117
  return { lines, spans };