canopycms 0.12.0 → 0.13.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/CHANGELOG.md +326 -48
- package/dist/api/admin.d.ts +4 -4
- package/dist/api/branch-review.d.ts +20 -2
- package/dist/api/branch-review.js +153 -32
- package/dist/api/branch-status.d.ts +23 -2
- package/dist/api/branch-status.js +16 -2
- package/dist/api/branch-update-from-base.d.ts +23 -0
- package/dist/api/branch-update-from-base.js +227 -0
- package/dist/api/branch.d.ts +10 -0
- package/dist/api/branch.js +28 -2
- package/dist/api/client.d.ts +15 -2
- package/dist/api/client.js +24 -2
- package/dist/api/comments.d.ts +2 -2
- package/dist/api/content.d.ts +38 -1
- package/dist/api/content.js +41 -5
- package/dist/api/merge-conflicts.d.ts +235 -0
- package/dist/api/merge-conflicts.js +709 -0
- package/dist/api/routes.js +2 -0
- package/dist/api/types.d.ts +8 -3
- package/dist/base-update.d.ts +32 -0
- package/dist/base-update.js +31 -0
- package/dist/branch-metadata-file.d.ts +66 -0
- package/dist/branch-metadata-file.js +16 -1
- package/dist/branch-schema-cache.d.ts +12 -0
- package/dist/branch-schema-cache.js +27 -2
- package/dist/cli/backfill-item-keys.d.ts +77 -0
- package/dist/cli/backfill-item-keys.js +320 -0
- package/dist/cli/cli.d.ts +1 -1
- package/dist/cli/cli.js +9798 -1509
- package/dist/cli/generate-ai-content.js +373 -249
- package/dist/cli/github-app-manifest.d.ts +4 -1
- package/dist/cli/github-app-manifest.js +5 -1
- package/dist/cli/init.js +12 -0
- package/dist/cli/site-config.d.ts +14 -2
- package/dist/cli/site-config.js +20 -4
- package/dist/comment-store.d.ts +8 -0
- package/dist/comment-store.js +28 -0
- package/dist/content-index-generation.d.ts +1 -4
- package/dist/content-index-generation.js +1 -4
- package/dist/content-store.d.ts +39 -3
- package/dist/content-store.js +48 -15
- package/dist/editor/BranchManager.d.ts +4 -1
- package/dist/editor/BranchManager.js +9 -5
- package/dist/editor/Editor.js +462 -62
- package/dist/editor/EntryNavigator.d.ts +10 -2
- package/dist/editor/EntryNavigator.js +12 -4
- package/dist/editor/FormRenderer.d.ts +8 -2
- package/dist/editor/FormRenderer.js +5 -6
- package/dist/editor/admin/ConflictsStatusAlert.d.ts +8 -0
- package/dist/editor/admin/ConflictsStatusAlert.js +22 -0
- package/dist/editor/admin/SystemHealthPanel.js +4 -3
- package/dist/editor/base-update-wait.d.ts +78 -0
- package/dist/editor/base-update-wait.js +98 -0
- package/dist/editor/branch-status.js +1 -1
- package/dist/editor/components/BaseUpdateNotice.d.ts +12 -0
- package/dist/editor/components/BaseUpdateNotice.js +24 -0
- package/dist/editor/components/BranchPill.d.ts +4 -1
- package/dist/editor/components/BranchPill.js +5 -2
- package/dist/editor/components/ConflictsGroup.d.ts +112 -0
- package/dist/editor/components/ConflictsGroup.js +404 -0
- package/dist/editor/components/EditorHeader.d.ts +18 -1
- package/dist/editor/components/EditorHeader.js +99 -33
- package/dist/editor/components/EntryChangesDrawer.d.ts +16 -2
- package/dist/editor/components/EntryChangesDrawer.js +85 -167
- package/dist/editor/components/EntryHeader.d.ts +3 -1
- package/dist/editor/components/EntryHeader.js +2 -2
- package/dist/editor/components/GetLatestChangesButton.d.ts +15 -0
- package/dist/editor/components/GetLatestChangesButton.js +29 -0
- package/dist/editor/components/ReleaseHoldButton.d.ts +20 -0
- package/dist/editor/components/ReleaseHoldButton.js +36 -0
- package/dist/editor/components/RetryToastMessage.js +1 -1
- package/dist/editor/components/change-parts.d.ts +51 -0
- package/dist/editor/components/change-parts.js +171 -0
- package/dist/editor/conflict-cards.d.ts +93 -0
- package/dist/editor/conflict-cards.js +284 -0
- package/dist/editor/conflict-choices.d.ts +60 -0
- package/dist/editor/conflict-choices.js +108 -0
- package/dist/editor/conflict-notice.d.ts +25 -0
- package/dist/editor/conflict-notice.js +39 -0
- package/dist/editor/copy.d.ts +12 -0
- package/dist/editor/copy.js +12 -0
- package/dist/editor/entry-diff.d.ts +2 -1
- package/dist/editor/entry-diff.js +5 -21
- package/dist/editor/hooks/useBaseUpdate.d.ts +38 -0
- package/dist/editor/hooks/useBaseUpdate.js +279 -0
- package/dist/editor/hooks/useBranchChangesData.d.ts +7 -1
- package/dist/editor/hooks/useBranchChangesData.js +13 -0
- package/dist/editor/hooks/useBranchManager.d.ts +8 -1
- package/dist/editor/hooks/useBranchManager.js +108 -13
- package/dist/editor/hooks/useBranchesData.js +3 -0
- package/dist/editor/hooks/useConflictChoices.d.ts +24 -0
- package/dist/editor/hooks/useConflictChoices.js +75 -0
- package/dist/editor/hooks/useConflictsData.d.ts +26 -0
- package/dist/editor/hooks/useConflictsData.js +89 -0
- package/dist/editor/hooks/useDraftManager.d.ts +13 -1
- package/dist/editor/hooks/useDraftManager.js +40 -9
- package/dist/editor/hooks/useEntryManager.d.ts +17 -3
- package/dist/editor/hooks/useEntryManager.js +11 -5
- package/dist/editor/release-hold.d.ts +12 -0
- package/dist/editor/release-hold.js +39 -0
- package/dist/editor/server-clock.d.ts +4 -0
- package/dist/editor/server-clock.js +15 -0
- package/dist/editor/undo-change.d.ts +7 -0
- package/dist/editor/undo-change.js +19 -4
- package/dist/git-manager.d.ts +6 -0
- package/dist/git-manager.js +38 -3
- package/dist/item-keys.d.ts +13 -0
- package/dist/item-keys.js +36 -14
- package/dist/merge/conflict-path.d.ts +40 -0
- package/dist/merge/conflict-path.js +128 -0
- package/dist/merge/diff3.d.ts +27 -0
- package/dist/merge/diff3.js +113 -0
- package/dist/merge/entry-merge.d.ts +94 -0
- package/dist/merge/entry-merge.js +876 -0
- package/dist/merge/list-alignment.d.ts +55 -0
- package/dist/merge/list-alignment.js +490 -0
- package/dist/merge/rekey.d.ts +11 -0
- package/dist/merge/rekey.js +75 -0
- package/dist/merge/text-merge.d.ts +29 -0
- package/dist/merge/text-merge.js +59 -0
- package/dist/merge/value-equality.d.ts +18 -0
- package/dist/merge/value-equality.js +50 -0
- package/dist/merge-conflicts.d.ts +232 -0
- package/dist/merge-conflicts.js +451 -0
- package/dist/operating-mode/client-safe-strategy.d.ts +2 -0
- package/dist/operating-mode/client-safe-strategy.js +6 -0
- package/dist/operating-mode/client-unsafe-strategy.js +6 -0
- package/dist/operating-mode/types.d.ts +13 -0
- package/dist/services.d.ts +24 -1
- package/dist/services.js +138 -22
- package/dist/task-queue/cms-task-queue.d.ts +3 -1
- package/dist/types.d.ts +63 -5
- package/dist/utils/content-serialize.d.ts +18 -1
- package/dist/utils/content-serialize.js +12 -0
- package/dist/utils/content-write-lock.d.ts +3 -3
- package/dist/utils/content-write-lock.js +3 -3
- package/dist/utils/git.d.ts +7 -0
- package/dist/utils/git.js +35 -0
- package/dist/utils/markdown-body-splice.d.ts +1 -1
- package/dist/utils/markdown-body-splice.js +1 -19
- package/dist/utils/markdown-parse.d.ts +9 -0
- package/dist/utils/markdown-parse.js +27 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker/branch-merge.d.ts +80 -40
- package/dist/worker/branch-merge.js +244 -58
- package/dist/worker/branch-policy.d.ts +18 -2
- package/dist/worker/branch-policy.js +36 -0
- package/dist/worker/cms-worker.d.ts +37 -3
- package/dist/worker/cms-worker.js +88 -16
- package/dist/worker/conflicts-status.d.ts +20 -0
- package/dist/worker/conflicts-status.js +204 -0
- package/dist/worker/field-merge.d.ts +80 -0
- package/dist/worker/field-merge.js +729 -0
- package/dist/worker/git-sync.d.ts +3 -2
- package/dist/worker/git-sync.js +16 -6
- package/dist/worker/github-commits.d.ts +42 -10
- package/dist/worker/github-commits.js +444 -52
- package/dist/worker/github-errors.d.ts +15 -0
- package/dist/worker/github-errors.js +47 -0
- package/dist/worker/github-gateway-http.js +24 -9
- package/dist/worker/github-gateway-wire.d.ts +49 -20
- package/dist/worker/github-gateway-wire.js +19 -3
- package/dist/worker/github-gateway.d.ts +27 -1
- package/dist/worker/github-gateway.js +55 -7
- package/dist/worker/github-transfer.d.ts +5 -3
- package/dist/worker/github-transfer.js +8 -5
- package/dist/worker/history-rewrite.d.ts +40 -8
- package/dist/worker/history-rewrite.js +51 -23
- package/dist/worker/publish-decision.d.ts +37 -0
- package/dist/worker/publish-decision.js +16 -0
- package/dist/worker/rebase.d.ts +29 -8
- package/dist/worker/rebase.js +492 -211
- package/dist/worker/task-runner.d.ts +1 -1
- package/dist/worker/task-runner.js +30 -41
- package/dist/worker/worker-context.d.ts +24 -2
- package/package.json +1 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line-level three-way merge of long text: a multi-line string, or an md/mdx body. Each side's
|
|
3
|
+
* changes apply where the other left the lines alone; lines both sides changed differently are a
|
|
4
|
+
* conflict region. Never writes conflict markers: in a region, the merged text holds the branch's
|
|
5
|
+
* lines, and the region records all three versions for the person who resolves it.
|
|
6
|
+
*/
|
|
7
|
+
import { diff3Regions } from './diff3.js';
|
|
8
|
+
/** `text` as lines, its one line break at the very end apart. */
|
|
9
|
+
const split = (text) => {
|
|
10
|
+
const endBreak = text.endsWith('\n');
|
|
11
|
+
const rest = endBreak ? text.slice(0, -1) : text;
|
|
12
|
+
return { lines: rest === '' && !endBreak ? [] : rest.split('\n'), endBreak };
|
|
13
|
+
};
|
|
14
|
+
const sameLines = (x, y) => x.length === y.length && x.every((line, i) => line === y[i]);
|
|
15
|
+
/**
|
|
16
|
+
* `base` and `branch`, both changed from `ancestor`, merged line by line. A line break at the very
|
|
17
|
+
* end, which a serializer may add or drop by itself, is merged on its own and never makes a
|
|
18
|
+
* conflict: the side that changed it wins, or the branch in a text with conflicts. Undefined when
|
|
19
|
+
* the texts are too far apart to diff in time: the caller then treats each version as one whole.
|
|
20
|
+
*/
|
|
21
|
+
export function mergeText(ancestor, base, branch) {
|
|
22
|
+
const o = split(ancestor);
|
|
23
|
+
const a = split(base);
|
|
24
|
+
const b = split(branch);
|
|
25
|
+
const regions = diff3Regions(o.lines, a.lines, b.lines);
|
|
26
|
+
if (!regions)
|
|
27
|
+
return undefined;
|
|
28
|
+
const versions = { ancestor: o.lines, base: a.lines, branch: b.lines };
|
|
29
|
+
const out = [];
|
|
30
|
+
const conflicts = [];
|
|
31
|
+
for (const region of regions) {
|
|
32
|
+
if (region.stable) {
|
|
33
|
+
out.push(...versions[region.side].slice(...region.span));
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const [oLines, aLines, bLines] = [
|
|
37
|
+
o.lines.slice(...region.ancestor),
|
|
38
|
+
a.lines.slice(...region.base),
|
|
39
|
+
b.lines.slice(...region.branch),
|
|
40
|
+
];
|
|
41
|
+
if (sameLines(aLines, bLines)) {
|
|
42
|
+
out.push(...bLines);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
conflicts.push({
|
|
46
|
+
ancestor: oLines.join('\n'),
|
|
47
|
+
base: aLines.join('\n'),
|
|
48
|
+
branch: bLines.join('\n'),
|
|
49
|
+
line: out.length,
|
|
50
|
+
lines: bLines.length,
|
|
51
|
+
baseLines: aLines.length,
|
|
52
|
+
});
|
|
53
|
+
out.push(...bLines);
|
|
54
|
+
}
|
|
55
|
+
// A conflicted text holds the branch's lines, so it holds the branch's end too.
|
|
56
|
+
const endBreak = conflicts.length > 0 || o.endBreak === a.endBreak ? b.endBreak : a.endBreak;
|
|
57
|
+
const body = out.join('\n');
|
|
58
|
+
return { text: endBreak ? `${body}\n` : body, regions: conflicts };
|
|
59
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* When two content values count as the same, for every comparison of entry versions: the "Show
|
|
3
|
+
* changes" drawer (`editor/entry-diff.ts`) and the field merge (`entry-merge.ts`) both use it, so a
|
|
4
|
+
* value the drawer calls unchanged is one the merge never calls a conflict.
|
|
5
|
+
*/
|
|
6
|
+
/** Absent, null, `''`, `[]`, or a record holding nothing but those and an item key. */
|
|
7
|
+
export declare const isEmptyValue: (value: unknown) => boolean;
|
|
8
|
+
/** `value` without item keys and empty members, so equal content compares equal. */
|
|
9
|
+
export declare const normalizedValue: (value: unknown) => unknown;
|
|
10
|
+
/** A string naming `value` exactly: equal for two values exactly when they hold the same. */
|
|
11
|
+
export declare const exactToken: (value: unknown) => string;
|
|
12
|
+
/**
|
|
13
|
+
* A string naming `value` up to {@link sameValue}: two values share a token exactly when they are
|
|
14
|
+
* the same, so a sequence of values can be aligned as a sequence of strings.
|
|
15
|
+
*/
|
|
16
|
+
export declare const valueToken: (value: unknown) => string;
|
|
17
|
+
/** The same content: item keys and empty members aside, and every empty value alike. */
|
|
18
|
+
export declare const sameValue: (a: unknown, b: unknown) => boolean;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* When two content values count as the same, for every comparison of entry versions: the "Show
|
|
3
|
+
* changes" drawer (`editor/entry-diff.ts`) and the field merge (`entry-merge.ts`) both use it, so a
|
|
4
|
+
* value the drawer calls unchanged is one the merge never calls a conflict.
|
|
5
|
+
*/
|
|
6
|
+
import { ITEM_KEY } from '../item-keys.js';
|
|
7
|
+
import { isPlainRecord } from '../validation/field-traversal.js';
|
|
8
|
+
/** `value`'s own members, `__proto__` included as a member like any other. */
|
|
9
|
+
const members = (value) => Object.entries(value);
|
|
10
|
+
/** Absent, null, `''`, `[]`, or a record holding nothing but those and an item key. */
|
|
11
|
+
export const isEmptyValue = (value) => value === undefined ||
|
|
12
|
+
value === null ||
|
|
13
|
+
value === '' ||
|
|
14
|
+
(Array.isArray(value) && value.length === 0) ||
|
|
15
|
+
(isPlainRecord(value) && members(value).every(([k, v]) => k === ITEM_KEY || isEmptyValue(v)));
|
|
16
|
+
/** `value` without item keys and empty members, so equal content compares equal. */
|
|
17
|
+
export const normalizedValue = (value) => {
|
|
18
|
+
if (Array.isArray(value))
|
|
19
|
+
return value.map(normalizedValue);
|
|
20
|
+
if (!isPlainRecord(value))
|
|
21
|
+
return value;
|
|
22
|
+
return Object.fromEntries(members(value)
|
|
23
|
+
.filter(([k, v]) => k !== ITEM_KEY && !isEmptyValue(v))
|
|
24
|
+
.map(([k, v]) => [k, normalizedValue(v)]));
|
|
25
|
+
};
|
|
26
|
+
/** A string naming `value` exactly: equal for two values exactly when they hold the same. */
|
|
27
|
+
export const exactToken = (value) => {
|
|
28
|
+
if (Array.isArray(value))
|
|
29
|
+
return `[${value.map(exactToken).join(',')}]`;
|
|
30
|
+
if (value instanceof Date)
|
|
31
|
+
return `D${value.getTime()}`;
|
|
32
|
+
if (isPlainRecord(value)) {
|
|
33
|
+
const sorted = members(value).sort(([x], [y]) => (x < y ? -1 : x > y ? 1 : 0));
|
|
34
|
+
return `{${sorted.map(([k, v]) => `${JSON.stringify(k)}:${exactToken(v)}`).join(',')}}`;
|
|
35
|
+
}
|
|
36
|
+
if (typeof value === 'string')
|
|
37
|
+
return JSON.stringify(value);
|
|
38
|
+
if (typeof value === 'number')
|
|
39
|
+
return Object.is(value, -0) ? '0' : String(value);
|
|
40
|
+
if (typeof value === 'bigint')
|
|
41
|
+
return `${value}n`;
|
|
42
|
+
return String(value);
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* A string naming `value` up to {@link sameValue}: two values share a token exactly when they are
|
|
46
|
+
* the same, so a sequence of values can be aligned as a sequence of strings.
|
|
47
|
+
*/
|
|
48
|
+
export const valueToken = (value) => isEmptyValue(value) ? '' : exactToken(normalizedValue(value));
|
|
49
|
+
/** The same content: item keys and empty members aside, and every empty value alike. */
|
|
50
|
+
export const sameValue = (a, b) => valueToken(a) === valueToken(b);
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The conflicts the worker's merges found on a branch and nobody has resolved yet:
|
|
3
|
+
* `.canopy-meta/merge-conflicts.json` in the branch clone, never committed, one record per entry.
|
|
4
|
+
* The working tree holds the author's value at each one (merge/entry-merge.ts), so nothing here is
|
|
5
|
+
* needed to read the branch; it is what says which values a person still has to choose.
|
|
6
|
+
*
|
|
7
|
+
* `branch.json` `conflictFiles` is derived from it: the content IDs it names, none for a clone with
|
|
8
|
+
* no such file (`reconcileMergeConflicts`, worker/github-commits.ts).
|
|
9
|
+
*
|
|
10
|
+
* Writers run under the comment store's three layers (comment-store.ts): the in-process mutex, the
|
|
11
|
+
* cross-host lockfile, then version OCC. The worker writes it holding the branch's provisioning and
|
|
12
|
+
* content-write locks.
|
|
13
|
+
*/
|
|
14
|
+
import type { DataPathSegment } from './editor/entry-diff.js';
|
|
15
|
+
import type { FieldConflictKind, KeyRemap, SidePaths } from './merge/entry-merge.js';
|
|
16
|
+
import type { ContentId } from './paths/types.js';
|
|
17
|
+
import type { ConflictStatus } from './types.js';
|
|
18
|
+
/** A record field naming where the entry is, which both sides moved to different places. */
|
|
19
|
+
export { LOCATION_CONFLICT_KEY } from './merge/conflict-path.js';
|
|
20
|
+
/** Blob ids of the versions a merge compared; absent where that version had no file. */
|
|
21
|
+
export interface MergeSides {
|
|
22
|
+
ancestor?: string;
|
|
23
|
+
base?: string;
|
|
24
|
+
branch?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* - `fields`: values changed differently on both sides, listed in `fields`;
|
|
28
|
+
* - `deleted-on-base`: the base deleted the entry and the branch changed it; the branch's stands;
|
|
29
|
+
* - `deleted-on-branch`: the branch deleted it and the base changed it; it stays deleted;
|
|
30
|
+
* - `unreadable`: a version that can't be merged field by field (it does not parse, is over the
|
|
31
|
+
* size cap, changed its entry type, or is not an entry at all); the branch's file stands.
|
|
32
|
+
*/
|
|
33
|
+
export type MergeConflictKind = 'fields' | 'deleted-on-base' | 'deleted-on-branch' | 'unreadable';
|
|
34
|
+
export interface MergeConflictField {
|
|
35
|
+
/** `entry-diff.ts`' `EntryChange.key` for the place, or {@link LOCATION_CONFLICT_KEY}. */
|
|
36
|
+
key: string;
|
|
37
|
+
dataPath: DataPathSegment[];
|
|
38
|
+
/**
|
|
39
|
+
* Where each of `sides`' versions holds the value, as a read of that version names it
|
|
40
|
+
* (merge/entry-merge.ts `FieldConflict.sidePaths`); `ancestor`'s is the first ancestor's.
|
|
41
|
+
*/
|
|
42
|
+
sidePaths: SidePaths;
|
|
43
|
+
kind: FieldConflictKind;
|
|
44
|
+
item?: true;
|
|
45
|
+
/** Long text, merged by line: the regions both sides changed come from the three blobs. */
|
|
46
|
+
text?: true;
|
|
47
|
+
/** The merge that last found it. */
|
|
48
|
+
mergeSha: string;
|
|
49
|
+
/**
|
|
50
|
+
* The ancestor of the merge that first found it, which is what the author started from, and the
|
|
51
|
+
* base and branch of the merge that last found it.
|
|
52
|
+
*/
|
|
53
|
+
sides: MergeSides;
|
|
54
|
+
}
|
|
55
|
+
export interface MergeConflictEntry {
|
|
56
|
+
/** Where the entry is in the merged tree, or was for one the branch deleted. */
|
|
57
|
+
path: string;
|
|
58
|
+
/** The merge that last changed this record. */
|
|
59
|
+
mergeSha: string;
|
|
60
|
+
kind: MergeConflictKind;
|
|
61
|
+
sides: MergeSides;
|
|
62
|
+
/** For `fields`, never empty. */
|
|
63
|
+
fields?: MergeConflictField[];
|
|
64
|
+
/**
|
|
65
|
+
* The record as it stood before `mergeSha` changed it, kept until the branch holds that merge:
|
|
66
|
+
* a merge recorded and then never landed gives the record back. Absent once it lands, and for a
|
|
67
|
+
* record that merge created.
|
|
68
|
+
*/
|
|
69
|
+
previous?: MergeConflictEntry;
|
|
70
|
+
}
|
|
71
|
+
/** Item keys a merge changed, which the branch's comment threads take once it holds the merge. */
|
|
72
|
+
interface PendingRemap {
|
|
73
|
+
mergeSha: string;
|
|
74
|
+
/** The logical path the threads name the entry by. */
|
|
75
|
+
entryPath: string;
|
|
76
|
+
remaps: KeyRemap[];
|
|
77
|
+
}
|
|
78
|
+
/** What the file holds. */
|
|
79
|
+
export interface MergeConflictState {
|
|
80
|
+
entries: Record<string, MergeConflictEntry>;
|
|
81
|
+
remaps?: PendingRemap[];
|
|
82
|
+
/**
|
|
83
|
+
* The merge this worker is landing: written before it reaches `remote.git`, cleared once the
|
|
84
|
+
* clone holds it, or once neither the clone nor `remote.git` does; a clone whose ref can't be read
|
|
85
|
+
* keeps it. The one commit a clone left
|
|
86
|
+
* behind its own published merge may catch up with.
|
|
87
|
+
*/
|
|
88
|
+
landing?: Landing;
|
|
89
|
+
/**
|
|
90
|
+
* A record that didn't parse was set aside: which conflicts it held is lost, so Submit and Approve
|
|
91
|
+
* wait ({@link assertNoOpenConflicts}) until an admin releases the branch. A later merge does not:
|
|
92
|
+
* it takes the set-aside merge as merged, so the lost choices would read as the branch's own.
|
|
93
|
+
* {@link MergeConflictStore.update} keeps it through a change that leaves it out.
|
|
94
|
+
*/
|
|
95
|
+
held?: Held;
|
|
96
|
+
}
|
|
97
|
+
interface Held {
|
|
98
|
+
/** The name the unparseable file was moved aside to, in `.canopy-meta`. */
|
|
99
|
+
aside: string;
|
|
100
|
+
at: string;
|
|
101
|
+
}
|
|
102
|
+
interface Landing {
|
|
103
|
+
mergeSha: string;
|
|
104
|
+
/** For a merge of GitHub's commits, the paths whose values the branch kept over them. */
|
|
105
|
+
keptOverGitHub?: string[];
|
|
106
|
+
}
|
|
107
|
+
export interface MergeConflictsFile extends MergeConflictState {
|
|
108
|
+
version: number;
|
|
109
|
+
writeId?: string;
|
|
110
|
+
}
|
|
111
|
+
/** What one merge found for one entry, before it has a commit to name. */
|
|
112
|
+
export interface FoundEntryConflict {
|
|
113
|
+
contentId: ContentId;
|
|
114
|
+
path: string;
|
|
115
|
+
kind: MergeConflictKind;
|
|
116
|
+
sides: MergeSides;
|
|
117
|
+
fields?: Array<Omit<MergeConflictField, 'mergeSha' | 'sides'>>;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* merge-conflicts.json names another format than this module reads, `found`. It is left as it is,
|
|
121
|
+
* unlike a file that doesn't parse: a worker of that format reads it, and its choices would be lost
|
|
122
|
+
* if it were set aside. The worker's next merge refuses it too, so the branch shows as failing to
|
|
123
|
+
* take its base in until the file is dealt with.
|
|
124
|
+
*/
|
|
125
|
+
export declare class MergeConflictsFileFormatError extends Error {
|
|
126
|
+
readonly found: number;
|
|
127
|
+
constructor(found: number);
|
|
128
|
+
}
|
|
129
|
+
/** merge-conflicts.json is there but is not a record this module wrote. */
|
|
130
|
+
export declare class MergeConflictsFileMalformedError extends Error {
|
|
131
|
+
constructor(detail: string);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* `existing` with what one merge, `mergeSha`, found. An entry this merge found no conflict in keeps
|
|
135
|
+
* its record: its value is still the author's, which this merge took as the branch's own change.
|
|
136
|
+
* A field found again keeps the ancestor it was first found against, and that ancestor's path, so
|
|
137
|
+
* the person still sees what they started from, and takes this merge's base and branch. An
|
|
138
|
+
* entry-level conflict replaces the entry's record; an `unreadable` record stands against fields
|
|
139
|
+
* found later, taking this merge's base and branch. `paths` moves the kept records of entries this
|
|
140
|
+
* merge moved. Every record this merge changes keeps the one it replaced as `previous`. Pure.
|
|
141
|
+
*/
|
|
142
|
+
export declare function withFoundConflicts(existing: Readonly<Record<string, MergeConflictEntry>>, found: readonly FoundEntryConflict[], mergeSha: string, paths?: ReadonlyMap<string, string>): Record<string, MergeConflictEntry>;
|
|
143
|
+
/** The item keys one merge changed in one entry, which the entry's records follow. */
|
|
144
|
+
export interface EntryKeyRemaps {
|
|
145
|
+
contentId?: string;
|
|
146
|
+
remaps: readonly KeyRemap[];
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* `entries` with the item keys a merge, `mergeSha`, changed renamed in the conflicts on record,
|
|
150
|
+
* as the branch's comment threads are, so a conflict found again under the new key is the same
|
|
151
|
+
* one. A record this renames keeps the one it replaced as `previous`, so a merge that never lands
|
|
152
|
+
* gives the old keys back. Pure.
|
|
153
|
+
*/
|
|
154
|
+
export declare function withRemappedItemKeys(entries: Readonly<Record<string, MergeConflictEntry>>, remaps: readonly EntryKeyRemaps[], mergeSha: string): Record<string, MergeConflictEntry>;
|
|
155
|
+
/**
|
|
156
|
+
* `entries` as the branch's history has them: a record whose `mergeSha` fails `holds` gives back the
|
|
157
|
+
* one it replaced, or goes when it replaced none, and a record whose merge the branch holds drops
|
|
158
|
+
* the one it replaced. A field whose own merge fails `holds` goes too, and an entry with no field
|
|
159
|
+
* left. Pure.
|
|
160
|
+
*/
|
|
161
|
+
export declare function withoutMergesGone(entries: Readonly<Record<string, MergeConflictEntry>>, holds: (mergeSha: string) => boolean): Record<string, MergeConflictEntry>;
|
|
162
|
+
/** A field conflict a person resolved, named as the record names it. */
|
|
163
|
+
export interface ResolvedFieldConflict {
|
|
164
|
+
mergeSha: string;
|
|
165
|
+
key: string;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* `entries` without the fields of `contentId`'s record that `resolved` names, each by its own
|
|
169
|
+
* `mergeSha` and `key`, and without the record once no field is left. A pair the record no longer
|
|
170
|
+
* holds, as after a newer merge found that field again, is left out of `cleared`. A record left with
|
|
171
|
+
* fields keeps its `previous`; a record left with none goes with it, since a merge keeps every field
|
|
172
|
+
* the record it replaced held, so those were resolved too. Pure.
|
|
173
|
+
*/
|
|
174
|
+
export declare function withoutResolvedFields(entries: Readonly<Record<string, MergeConflictEntry>>, contentId: string, resolved: readonly ResolvedFieldConflict[]): {
|
|
175
|
+
entries: Record<string, MergeConflictEntry>;
|
|
176
|
+
cleared: ResolvedFieldConflict[];
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* `entries` without `contentId`'s entry-level record, when `mergeSha` is the merge that last changed
|
|
180
|
+
* it; null when it is not there, or a later merge changed it. Pure.
|
|
181
|
+
*/
|
|
182
|
+
export declare function withoutEntryConflict(entries: Readonly<Record<string, MergeConflictEntry>>, contentId: string, mergeSha: string): Record<string, MergeConflictEntry> | null;
|
|
183
|
+
/** How many choices `entries` leave a person: each field conflict, and each entry-level one. */
|
|
184
|
+
export declare function openConflictCount(entries: Readonly<Record<string, MergeConflictEntry>>): number;
|
|
185
|
+
/** What `branch.json` holds for `entries`: the content IDs with a conflict open, sorted. */
|
|
186
|
+
export declare function conflictFilesOf(entries: Readonly<Record<string, MergeConflictEntry>>): {
|
|
187
|
+
conflictStatus: ConflictStatus;
|
|
188
|
+
conflictFiles: ContentId[];
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* A submit or approval refused while the branch has conflicts open, `count` of them, or with none
|
|
192
|
+
* it can count (`count` null): a record that doesn't parse, which the worker's next cycle sets aside
|
|
193
|
+
* and holds; one held after that (`held`); or one of another format, left until a developer deals
|
|
194
|
+
* with it.
|
|
195
|
+
*/
|
|
196
|
+
export declare class OpenMergeConflictsError extends Error {
|
|
197
|
+
readonly count: number | null;
|
|
198
|
+
/** The record was set aside as unparseable, so which conflicts are open is lost. */
|
|
199
|
+
readonly held: boolean;
|
|
200
|
+
constructor(count: number | null,
|
|
201
|
+
/** The record was set aside as unparseable, so which conflicts are open is lost. */
|
|
202
|
+
held?: boolean);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Throws {@link OpenMergeConflictsError} while `branchRoot`'s record holds a conflict, is held after
|
|
206
|
+
* one was set aside, or does not parse. A clone with no record has none open.
|
|
207
|
+
*/
|
|
208
|
+
export declare function assertNoOpenConflicts(branchRoot: string): Promise<void>;
|
|
209
|
+
/** A branch clone's merge-conflicts.json. */
|
|
210
|
+
export declare class MergeConflictStore {
|
|
211
|
+
private readonly settleMs?;
|
|
212
|
+
private readonly filePath;
|
|
213
|
+
constructor(branchRoot: string, settleMs?: number | undefined);
|
|
214
|
+
/** Whether the clone has a record at all. */
|
|
215
|
+
exists(): Promise<boolean>;
|
|
216
|
+
/**
|
|
217
|
+
* Copy a file that does not parse as a record aside, and replace it with a new one that is
|
|
218
|
+
* `held`, as which conflicts were open is lost with it. Resolves the copy's path, or null when
|
|
219
|
+
* the file parses by the time the locks are held, as another call may have set it aside.
|
|
220
|
+
* The new record is written beside the file and renamed over it, so a worker killed at any step
|
|
221
|
+
* leaves a record that still refuses the gate: the damaged one, or the held one.
|
|
222
|
+
*/
|
|
223
|
+
quarantine(): Promise<string | null>;
|
|
224
|
+
/** The record, or null when the clone has none. Throws on a file that does not parse as one. */
|
|
225
|
+
read(): Promise<MergeConflictsFile | null>;
|
|
226
|
+
/**
|
|
227
|
+
* Replace what the file holds with what `change` makes of it, under the module's locks. `change`
|
|
228
|
+
* gets null when there is no file yet, and returns null to write nothing; a `held` it leaves out
|
|
229
|
+
* stays. Resolves what was written, or null.
|
|
230
|
+
*/
|
|
231
|
+
update(change: (current: MergeConflictState | null) => MergeConflictState | null): Promise<MergeConflictState | null>;
|
|
232
|
+
}
|