@nimbus-sh/worker 0.9.0 → 0.11.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/NOTICE.md +5 -0
- package/README.md +10 -0
- package/dist/_shared/session-router.d.ts +7 -0
- package/dist/_shared/session-router.d.ts.map +1 -1
- package/dist/_shared/session-router.js +7 -0
- package/dist/bindings/body.d.ts +6 -0
- package/dist/bindings/body.d.ts.map +1 -0
- package/dist/bindings/body.js +37 -0
- package/dist/bindings/kv.d.ts +0 -2
- package/dist/bindings/kv.d.ts.map +1 -1
- package/dist/bindings/kv.js +3 -42
- package/dist/bindings/r2.d.ts +0 -2
- package/dist/bindings/r2.d.ts.map +1 -1
- package/dist/bindings/r2.js +4 -44
- package/dist/esbuild-cli-artifact.generated.d.ts +18 -0
- package/dist/esbuild-cli-artifact.generated.d.ts.map +1 -0
- package/dist/esbuild-cli-artifact.generated.js +17 -0
- package/dist/esbuild-wasm-bundle.generated.d.ts +24 -24
- package/dist/esbuild-wasm-bundle.generated.d.ts.map +1 -1
- package/dist/esbuild-wasm-bundle.generated.js +24 -24
- package/dist/facets/cirrus-real.d.ts.map +1 -1
- package/dist/facets/cirrus-real.js +11 -6
- package/dist/facets/compose.d.ts +11 -7
- package/dist/facets/compose.d.ts.map +1 -1
- package/dist/facets/compose.js +8 -31
- package/dist/facets/data-plan.d.ts +79 -0
- package/dist/facets/data-plan.d.ts.map +1 -0
- package/dist/facets/data-plan.js +477 -0
- package/dist/facets/esbuild-bundle-pool.d.ts +1 -1
- package/dist/facets/esbuild-bundle-pool.js +3 -3
- package/dist/facets/esbuild-transform.d.ts +38 -8
- package/dist/facets/esbuild-transform.d.ts.map +1 -1
- package/dist/facets/esbuild-transform.js +219 -19
- package/dist/facets/exec-telemetry.d.ts +7 -3
- package/dist/facets/exec-telemetry.d.ts.map +1 -1
- package/dist/facets/manager.d.ts +198 -69
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +1276 -652
- package/dist/facets/opencode-staging.d.ts +0 -2
- package/dist/facets/opencode-staging.d.ts.map +1 -1
- package/dist/facets/opencode-staging.js +5 -11
- package/dist/facets/process.d.ts +4 -0
- package/dist/facets/process.d.ts.map +1 -1
- package/dist/facets/process.js +3 -0
- package/dist/facets/read-profile.d.ts +149 -0
- package/dist/facets/read-profile.d.ts.map +1 -0
- package/dist/facets/read-profile.js +483 -0
- package/dist/facets/real-vite-hmr.d.ts +2 -0
- package/dist/facets/real-vite-hmr.d.ts.map +1 -1
- package/dist/facets/real-vite-hmr.js +3 -2
- package/dist/facets/resident-identity.d.ts +0 -1
- package/dist/facets/resident-identity.d.ts.map +1 -1
- package/dist/facets/resident-identity.js +0 -3
- package/dist/facets/wasm-swap-registry.d.ts +34 -25
- package/dist/facets/wasm-swap-registry.d.ts.map +1 -1
- package/dist/facets/wasm-swap-registry.js +62 -56
- package/dist/facets/ws-terminal.d.ts +4 -4
- package/dist/facets/ws-terminal.d.ts.map +1 -1
- package/dist/facets/ws-terminal.js +4 -5
- package/dist/git/commands.d.ts +33 -10
- package/dist/git/commands.d.ts.map +1 -1
- package/dist/git/commands.js +1891 -197
- package/dist/git/network-facet.d.ts +12 -2
- package/dist/git/network-facet.d.ts.map +1 -1
- package/dist/git/network-facet.js +69 -18
- package/dist/git/unified-diff.d.ts +74 -0
- package/dist/git/unified-diff.d.ts.map +1 -0
- package/dist/git/unified-diff.js +676 -0
- package/dist/git-bundle.generated.d.ts +7 -11
- package/dist/git-bundle.generated.d.ts.map +1 -1
- package/dist/git-bundle.generated.js +7 -11
- package/dist/hosted/commands.d.ts.map +1 -1
- package/dist/hosted/commands.js +64 -245
- package/dist/hosted/runtime.d.ts +22 -15
- package/dist/hosted/runtime.d.ts.map +1 -1
- package/dist/hosted/runtime.js +72 -14
- package/dist/hosted/services.d.ts +8 -1
- package/dist/hosted/services.d.ts.map +1 -1
- package/dist/hosted/services.js +12 -13
- package/dist/hosted/session.d.ts +165 -0
- package/dist/hosted/session.d.ts.map +1 -0
- package/dist/hosted/session.js +132 -0
- package/dist/loaders/generated-workers.d.ts +6 -3
- package/dist/loaders/generated-workers.d.ts.map +1 -1
- package/dist/loaders/generated-workers.js +8 -5
- package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -1
- package/dist/loaders/npm-resolve-preamble.js +0 -27
- package/dist/loaders/pre-bundle-preamble.d.ts +6 -3
- package/dist/loaders/pre-bundle-preamble.d.ts.map +1 -1
- package/dist/loaders/pre-bundle-preamble.js +13 -8
- package/dist/node-shims-artifact.generated.d.ts +13 -4
- package/dist/node-shims-artifact.generated.d.ts.map +1 -1
- package/dist/node-shims-artifact.generated.js +16 -7
- package/dist/npm/bin-links.d.ts +3 -1
- package/dist/npm/bin-links.d.ts.map +1 -1
- package/dist/npm/bin-links.js +32 -8
- package/dist/npm/cache.d.ts +11 -1
- package/dist/npm/cache.d.ts.map +1 -1
- package/dist/npm/cache.js +21 -7
- package/dist/npm/install-batch-facet.d.ts +4 -2
- package/dist/npm/install-batch-facet.d.ts.map +1 -1
- package/dist/npm/install-batch-facet.js +32 -18
- package/dist/npm/installer.d.ts +23 -3
- package/dist/npm/installer.d.ts.map +1 -1
- package/dist/npm/installer.js +330 -131
- package/dist/npm/npx-install.d.ts +3 -1
- package/dist/npm/npx-install.d.ts.map +1 -1
- package/dist/npm/npx-install.js +4 -1
- package/dist/npm/package-lock.d.ts +32 -0
- package/dist/npm/package-lock.d.ts.map +1 -0
- package/dist/npm/package-lock.js +65 -0
- package/dist/npm/placement.d.ts +16 -0
- package/dist/npm/placement.d.ts.map +1 -0
- package/dist/npm/placement.js +33 -0
- package/dist/npm/pre-bundle-facet.d.ts +1 -30
- package/dist/npm/pre-bundle-facet.d.ts.map +1 -1
- package/dist/npm/pre-bundle-facet.js +3 -5
- package/dist/npm/r2-cache.d.ts +12 -6
- package/dist/npm/r2-cache.d.ts.map +1 -1
- package/dist/npm/r2-cache.js +34 -16
- package/dist/npm/resolve-one-facet.d.ts +3 -0
- package/dist/npm/resolve-one-facet.d.ts.map +1 -1
- package/dist/npm/resolve-one-facet.js +15 -3
- package/dist/npm/resolver.d.ts +11 -13
- package/dist/npm/resolver.d.ts.map +1 -1
- package/dist/npm/resolver.js +12 -25
- package/dist/opencode-artifact.generated.js +2 -2
- package/dist/replica/routing.d.ts +2 -38
- package/dist/replica/routing.d.ts.map +1 -1
- package/dist/replica/routing.js +3 -55
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +15 -2
- package/dist/router/public-directory.d.ts +18 -0
- package/dist/router/public-directory.d.ts.map +1 -1
- package/dist/router/public-directory.js +1 -1
- package/dist/router/remote-api.d.ts +3 -1
- package/dist/router/remote-api.d.ts.map +1 -1
- package/dist/router/remote-api.js +42 -79
- package/dist/runtime/bash-repl.js +2 -2
- package/dist/runtime/bun-repl.d.ts.map +1 -1
- package/dist/runtime/bun-repl.js +2 -13
- package/dist/runtime/cpython-resident.d.ts.map +1 -1
- package/dist/runtime/cpython-resident.js +2 -10
- package/dist/runtime/esbuild-wasm-bytes.d.ts +37 -18
- package/dist/runtime/esbuild-wasm-bytes.d.ts.map +1 -1
- package/dist/runtime/esbuild-wasm-bytes.js +66 -92
- package/dist/runtime/facet-loader-host.d.ts.map +1 -1
- package/dist/runtime/facet-loader-host.js +0 -7
- package/dist/runtime/git-bundle-artifact.d.ts +17 -0
- package/dist/runtime/git-bundle-artifact.d.ts.map +1 -0
- package/dist/runtime/git-bundle-artifact.js +25 -0
- package/dist/runtime/node-repl.d.ts.map +1 -1
- package/dist/runtime/node-repl.js +2 -13
- package/dist/runtime/node-shims-artifact.d.ts +22 -23
- package/dist/runtime/node-shims-artifact.d.ts.map +1 -1
- package/dist/runtime/node-shims-artifact.js +48 -78
- package/dist/runtime/node-shims.d.ts.map +1 -1
- package/dist/runtime/node-shims.js +2358 -996
- package/dist/runtime/opencode-artifact.d.ts.map +1 -1
- package/dist/runtime/opencode-artifact.js +17 -53
- package/dist/runtime/opencode-facet-runner.d.ts +3 -6
- package/dist/runtime/opencode-facet-runner.d.ts.map +1 -1
- package/dist/runtime/opencode-facet-runner.js +45 -10
- package/dist/runtime/opentui-wasm-bytes.d.ts.map +1 -1
- package/dist/runtime/opentui-wasm-bytes.js +16 -49
- package/dist/runtime/package-manager.d.ts +0 -2
- package/dist/runtime/package-manager.d.ts.map +1 -1
- package/dist/runtime/package-manager.js +0 -3
- package/dist/runtime/python-repl.d.ts.map +1 -1
- package/dist/runtime/python-repl.js +4 -35
- package/dist/runtime/repl-session.d.ts +10 -0
- package/dist/runtime/repl-session.d.ts.map +1 -1
- package/dist/runtime/repl-session.js +7 -0
- package/dist/runtime/ruby-repl.d.ts.map +1 -1
- package/dist/runtime/ruby-repl.js +2 -2
- package/dist/runtime/ruby-resident.d.ts.map +1 -1
- package/dist/runtime/ruby-resident.js +41 -51
- package/dist/runtime/runtime-catalog.d.ts +10 -3
- package/dist/runtime/runtime-catalog.d.ts.map +1 -1
- package/dist/runtime/runtime-catalog.js +125 -15
- package/dist/runtime/sqlite-wasm-bytes.d.ts +4 -4
- package/dist/runtime/sqlite-wasm-bytes.d.ts.map +1 -1
- package/dist/runtime/sqlite-wasm-bytes.js +21 -57
- package/dist/runtime/staged-source.d.ts +67 -0
- package/dist/runtime/staged-source.d.ts.map +1 -0
- package/dist/runtime/staged-source.js +113 -0
- package/dist/runtime/wasi/preamble.d.ts +0 -22
- package/dist/runtime/wasi/preamble.d.ts.map +1 -1
- package/dist/runtime/wasi/preamble.js +98 -1425
- package/dist/runtime-catalog.generated.js +1 -1
- package/dist/session/agent.d.ts +14 -2
- package/dist/session/agent.d.ts.map +1 -1
- package/dist/session/agent.js +57 -19
- package/dist/session/ai.d.ts +0 -11
- package/dist/session/ai.d.ts.map +1 -1
- package/dist/session/ai.js +0 -4
- package/dist/session/fs-watch.d.ts +0 -11
- package/dist/session/fs-watch.d.ts.map +1 -1
- package/dist/session/fs-watch.js +0 -21
- package/dist/session/hibernation.d.ts +68 -2
- package/dist/session/hibernation.d.ts.map +1 -1
- package/dist/session/hibernation.js +95 -1
- package/dist/session/init-phases.d.ts +1 -2
- package/dist/session/init-phases.d.ts.map +1 -1
- package/dist/session/init-phases.js +1 -2
- package/dist/session/init.d.ts.map +1 -1
- package/dist/session/init.js +7 -20
- package/dist/session/keys.d.ts +0 -3
- package/dist/session/keys.d.ts.map +1 -1
- package/dist/session/keys.js +0 -3
- package/dist/session/legacy-reset.d.ts +11 -0
- package/dist/session/legacy-reset.d.ts.map +1 -0
- package/dist/session/legacy-reset.js +16 -0
- package/dist/session/nimbus-session.d.ts +50 -16
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +77 -71
- package/dist/session/npm-install-port.d.ts.map +1 -1
- package/dist/session/npm-install-port.js +2 -0
- package/dist/session/programmatic.d.ts +13 -18
- package/dist/session/programmatic.d.ts.map +1 -1
- package/dist/session/programmatic.js +53 -39
- package/dist/session/replica-routes.d.ts +2 -3
- package/dist/session/replica-routes.d.ts.map +1 -1
- package/dist/session/replica-routes.js +3 -10
- package/dist/session/routes.d.ts.map +1 -1
- package/dist/session/routes.js +11 -12
- package/dist/session/rpc.d.ts +108 -21
- package/dist/session/rpc.d.ts.map +1 -1
- package/dist/session/rpc.js +194 -39
- package/dist/session/start-real-vite.js +2 -2
- package/dist/session/state-store.d.ts +1 -17
- package/dist/session/state-store.d.ts.map +1 -1
- package/dist/session/state-store.js +3 -53
- package/dist/session/supervisor-op.d.ts +7 -0
- package/dist/session/supervisor-op.d.ts.map +1 -1
- package/dist/session/supervisor-op.js +6 -1
- package/dist/session/supervisor-rpc.d.ts +116 -19
- package/dist/session/supervisor-rpc.d.ts.map +1 -1
- package/dist/session/supervisor-rpc.js +194 -84
- package/dist/session/vite-command.d.ts.map +1 -1
- package/dist/session/vite-command.js +17 -14
- package/dist/session/ws.d.ts.map +1 -1
- package/dist/session/ws.js +20 -8
- package/dist/shell/npm-bin-entrypoints.d.ts.map +1 -1
- package/dist/shell/npm-bin-entrypoints.js +30 -5
- package/dist/vfs/facet-resident-limits.d.ts +33 -0
- package/dist/vfs/facet-resident-limits.d.ts.map +1 -0
- package/dist/vfs/facet-resident-limits.js +33 -0
- package/dist/vfs/facet-resident-store.d.ts +2 -10
- package/dist/vfs/facet-resident-store.d.ts.map +1 -1
- package/dist/vfs/facet-resident-store.js +1594 -219
- package/dist/workspace-host.d.ts +3 -1
- package/dist/workspace-host.d.ts.map +1 -1
- package/dist/workspace-host.js +2 -0
- package/dist/wrangler/nimbus-wrangler.d.ts +0 -2
- package/dist/wrangler/nimbus-wrangler.d.ts.map +1 -1
- package/dist/wrangler/nimbus-wrangler.js +8 -22
- package/package.json +34 -7
- package/public/_assets/esbuild-0.24.2.js +2380 -0
- package/public/_assets/opencode/1.16.2/index-attach.js +2 -2
- package/public/_assets/runtime/esbuild-cli-c4fda380d52befc1.js +6892 -0
- package/public/_assets/runtime/git-de3e60c9f3ff006a.js +103 -0
- package/public/_assets/runtime/{node-shims-acaa037e6d26ec0d.js → node-shims-06565b121555c194.js} +2879 -992
- package/public/_assets/runtime/resident-store-6ffb1939c53d982f.js +2264 -0
- package/public/_assets/runtime/vfs-write-ledger-e501e8fc793bc2c1.js +889 -0
- package/scripts/build-opencode-attach-entry.mjs +51 -1
- package/scripts/bundle-esbuild-wasm.mjs +55 -39
- package/scripts/bundle-facet-workers.mjs +159 -10
- package/scripts/bundle-git.mjs +88 -27
- package/scripts/bundle-node-shims.mjs +72 -46
- package/scripts/bundle-runtime.mjs +18 -421
- package/scripts/cf-git-patch.mjs +137 -0
- package/scripts/patch-install-deps.mjs +49 -96
- package/scripts/runtime-specs.mjs +435 -0
- package/vendor/git-http.generated.d.ts +82 -0
- package/vendor/git-upstream.generated.d.ts +4378 -0
- package/vendor/git.LICENSE +7 -0
- package/vendor/git.generated.d.mts +21 -0
- package/vendor/git.generated.mjs +103 -0
- package/dist/replica/suspension.d.ts +0 -36
- package/dist/replica/suspension.d.ts.map +0 -1
- package/dist/replica/suspension.js +0 -49
- package/dist/runtime/static-server.d.ts +0 -3
- package/dist/runtime/static-server.d.ts.map +0 -1
- package/dist/runtime/static-server.js +0 -133
- package/scripts/clean-dist.mjs +0 -7
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
// git's diff formats (diff.c, xdiff/xemit.c, quote.c) over jsdiff's Myers diff: hunks may sit where xdiff's would not; the framing is git's.
|
|
2
|
+
// Text is carried as binary strings, one char per byte, so content and path bytes reach the output unchanged.
|
|
3
|
+
import { diffArrays } from 'diff';
|
|
4
|
+
const ZERO_OID = '0000000000000000000000000000000000000000';
|
|
5
|
+
export const DEFAULT_CONTEXT = 3;
|
|
6
|
+
const EMPTY = new Uint8Array(0);
|
|
7
|
+
export function absentSpec(path) {
|
|
8
|
+
return { path, valid: false, oid: ZERO_OID, mode: 0, data: EMPTY };
|
|
9
|
+
}
|
|
10
|
+
const S_IFMT = 0o170000;
|
|
11
|
+
/** diff_resolve_rename_copy's status letter. */
|
|
12
|
+
export function pairStatus(pair) {
|
|
13
|
+
if (!pair.one.valid)
|
|
14
|
+
return 'A';
|
|
15
|
+
if (!pair.two.valid)
|
|
16
|
+
return 'D';
|
|
17
|
+
// A regular file and a symlink are different kinds of object: a type change.
|
|
18
|
+
if ((pair.one.mode & S_IFMT) !== (pair.two.mode & S_IFMT))
|
|
19
|
+
return 'T';
|
|
20
|
+
return pair.renameScore === undefined ? 'M' : 'R';
|
|
21
|
+
}
|
|
22
|
+
// ── Binary strings ──────────────────────────────────────────────────────
|
|
23
|
+
const utf8 = new TextEncoder();
|
|
24
|
+
export function binaryFromBytes(bytes) {
|
|
25
|
+
let out = '';
|
|
26
|
+
for (let i = 0; i < bytes.length; i += 0x8000) {
|
|
27
|
+
out += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000));
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
export function bytesFromBinary(text) {
|
|
32
|
+
const out = new Uint8Array(text.length);
|
|
33
|
+
for (let i = 0; i < text.length; i++)
|
|
34
|
+
out[i] = text.charCodeAt(i);
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
/** A path's UTF-8 bytes as a binary string. */
|
|
38
|
+
export function binaryPath(path) {
|
|
39
|
+
return binaryFromBytes(utf8.encode(path));
|
|
40
|
+
}
|
|
41
|
+
// ── Path quoting (quote.c) ──────────────────────────────────────────────
|
|
42
|
+
const C_ESCAPES = {
|
|
43
|
+
0x07: 'a', 0x08: 'b', 0x09: 't', 0x0a: 'n', 0x0b: 'v', 0x0c: 'f', 0x0d: 'r', 0x22: '"', 0x5c: '\\',
|
|
44
|
+
};
|
|
45
|
+
/** quote_c_style's body, or null when no byte needs quoting. */
|
|
46
|
+
function cQuoted(bin) {
|
|
47
|
+
let quoted = null;
|
|
48
|
+
for (let i = 0; i < bin.length; i++) {
|
|
49
|
+
const c = bin.charCodeAt(i);
|
|
50
|
+
if (c >= 0x20 && c < 0x7f && c !== 0x22 && c !== 0x5c) {
|
|
51
|
+
if (quoted !== null)
|
|
52
|
+
quoted += bin[i];
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (quoted === null)
|
|
56
|
+
quoted = bin.slice(0, i);
|
|
57
|
+
const letter = C_ESCAPES[c];
|
|
58
|
+
quoted += letter ? `\\${letter}` : `\\${c.toString(8).padStart(3, '0')}`;
|
|
59
|
+
}
|
|
60
|
+
return quoted;
|
|
61
|
+
}
|
|
62
|
+
/** A path as git prints it on a '\n'-ended line: C-quoted if any byte needs it. */
|
|
63
|
+
export function quotePath(path) {
|
|
64
|
+
const bin = binaryPath(path);
|
|
65
|
+
const body = cQuoted(bin);
|
|
66
|
+
return body === null ? bin : `"${body}"`;
|
|
67
|
+
}
|
|
68
|
+
/** One entry of a path list: quoted and '\n'-ended, or raw and NUL-ended under -z. */
|
|
69
|
+
export function pathLine(path, z) {
|
|
70
|
+
return z ? binaryPath(path) + '\0' : quotePath(path) + '\n';
|
|
71
|
+
}
|
|
72
|
+
function quoteTwo(prefix, path) {
|
|
73
|
+
const bin = binaryPath(path);
|
|
74
|
+
const body = cQuoted(bin);
|
|
75
|
+
return body === null ? prefix + bin : `"${prefix}${body}"`;
|
|
76
|
+
}
|
|
77
|
+
// ── Line diff ───────────────────────────────────────────────────────────
|
|
78
|
+
function splitLines(bin) {
|
|
79
|
+
const lines = [];
|
|
80
|
+
let start = 0;
|
|
81
|
+
while (start < bin.length) {
|
|
82
|
+
const nl = bin.indexOf('\n', start);
|
|
83
|
+
const end = nl < 0 ? bin.length : nl + 1;
|
|
84
|
+
lines.push(bin.slice(start, end));
|
|
85
|
+
start = end;
|
|
86
|
+
}
|
|
87
|
+
return lines;
|
|
88
|
+
}
|
|
89
|
+
// jsdiff's cost is quadratic in edits (~0.5 s at 2000); past that a pair is one replacement hunk.
|
|
90
|
+
const MAX_EDIT_LENGTH = 2000;
|
|
91
|
+
function lineChanges(a, b) {
|
|
92
|
+
const shorter = Math.min(a.length, b.length);
|
|
93
|
+
let head = 0;
|
|
94
|
+
while (head < shorter && a[head] === b[head])
|
|
95
|
+
head++;
|
|
96
|
+
let tail = 0;
|
|
97
|
+
while (tail < shorter - head && a[a.length - 1 - tail] === b[b.length - 1 - tail])
|
|
98
|
+
tail++;
|
|
99
|
+
const oldLines = a.slice(head, a.length - tail);
|
|
100
|
+
const newLines = b.slice(head, b.length - tail);
|
|
101
|
+
if (oldLines.length === 0 && newLines.length === 0)
|
|
102
|
+
return [];
|
|
103
|
+
const whole = [{ i1: head, n1: oldLines.length, i2: head, n2: newLines.length }];
|
|
104
|
+
if (oldLines.length === 0 || newLines.length === 0)
|
|
105
|
+
return whole;
|
|
106
|
+
const parts = diffArrays(oldLines, newLines, { maxEditLength: MAX_EDIT_LENGTH });
|
|
107
|
+
if (!parts)
|
|
108
|
+
return whole;
|
|
109
|
+
const changes = [];
|
|
110
|
+
let i = head;
|
|
111
|
+
let j = head;
|
|
112
|
+
let open = null;
|
|
113
|
+
for (const part of parts) {
|
|
114
|
+
const count = part.count ?? part.value.length;
|
|
115
|
+
if (!part.added && !part.removed) {
|
|
116
|
+
open = null;
|
|
117
|
+
i += count;
|
|
118
|
+
j += count;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (!open)
|
|
122
|
+
changes.push(open = { i1: i, n1: 0, i2: j, n2: 0 });
|
|
123
|
+
if (part.removed) {
|
|
124
|
+
open.n1 += count;
|
|
125
|
+
i += count;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
open.n2 += count;
|
|
129
|
+
j += count;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return changes;
|
|
133
|
+
}
|
|
134
|
+
function diffText(one, two) {
|
|
135
|
+
const a = splitLines(binaryFromBytes(one));
|
|
136
|
+
const b = splitLines(binaryFromBytes(two));
|
|
137
|
+
return { a, b, changes: lineChanges(a, b) };
|
|
138
|
+
}
|
|
139
|
+
// ── Hunks (xdiff/xemit.c) ───────────────────────────────────────────────
|
|
140
|
+
/** def_ff: a line opening with a letter, '_' or '$', cut to 80 bytes, trailing space trimmed. */
|
|
141
|
+
function funcName(line) {
|
|
142
|
+
const c = line.charCodeAt(0);
|
|
143
|
+
if (!((c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) || c === 0x5f || c === 0x24))
|
|
144
|
+
return null;
|
|
145
|
+
let len = Math.min(line.length, 80);
|
|
146
|
+
// git's isspace (sane_ctype) is only \t \n \r and space.
|
|
147
|
+
while (len > 0 && /[\t\n\r ]/.test(line[len - 1]))
|
|
148
|
+
len--;
|
|
149
|
+
return line.slice(0, len);
|
|
150
|
+
}
|
|
151
|
+
/** xdl_format_hunk_hdr: counts of 1 are omitted, an empty side names the line before it. */
|
|
152
|
+
function hunkHeader(s1, c1, s2, c2, func) {
|
|
153
|
+
let header = `@@ -${c1 ? s1 : s1 - 1}${c1 === 1 ? '' : `,${c1}`} +${c2 ? s2 : s2 - 1}${c2 === 1 ? '' : `,${c2}`} @@`;
|
|
154
|
+
if (func) {
|
|
155
|
+
header += ' ';
|
|
156
|
+
// The header is built in a 128-byte buffer that ends in '\n'.
|
|
157
|
+
header += func.slice(0, 128 - header.length - 1);
|
|
158
|
+
}
|
|
159
|
+
return header + '\n';
|
|
160
|
+
}
|
|
161
|
+
function record(prefix, line) {
|
|
162
|
+
return line.endsWith('\n') ? prefix + line : `${prefix}${line}\n\\n`;
|
|
163
|
+
}
|
|
164
|
+
function formatHunks({ a, b, changes }, context) {
|
|
165
|
+
let out = '';
|
|
166
|
+
let func = '';
|
|
167
|
+
let funcSearchedTo = -1;
|
|
168
|
+
for (let first = 0; first < changes.length;) {
|
|
169
|
+
let last = first;
|
|
170
|
+
while (last + 1 < changes.length
|
|
171
|
+
&& changes[last + 1].i1 - (changes[last].i1 + changes[last].n1) <= 2 * context)
|
|
172
|
+
last++;
|
|
173
|
+
const start = changes[first];
|
|
174
|
+
const end = changes[last];
|
|
175
|
+
const s1 = Math.max(start.i1 - context, 0);
|
|
176
|
+
const s2 = Math.max(start.i2 - context, 0);
|
|
177
|
+
const trailing = Math.min(context, a.length - (end.i1 + end.n1), b.length - (end.i2 + end.n2));
|
|
178
|
+
const e1 = end.i1 + end.n1 + trailing;
|
|
179
|
+
const e2 = end.i2 + end.n2 + trailing;
|
|
180
|
+
// get_func_line: nearest earlier old line that names a function; it carries across hunks.
|
|
181
|
+
for (let l = s1 - 1; l > funcSearchedTo; l--) {
|
|
182
|
+
const name = funcName(a[l]);
|
|
183
|
+
if (name !== null) {
|
|
184
|
+
func = name;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
funcSearchedTo = s1 - 1;
|
|
189
|
+
out += hunkHeader(s1 + 1, e1 - s1, s2 + 1, e2 - s2, func);
|
|
190
|
+
for (let j = s2; j < start.i2; j++)
|
|
191
|
+
out += record(' ', b[j]);
|
|
192
|
+
let j = start.i2;
|
|
193
|
+
for (let k = first; k <= last; k++) {
|
|
194
|
+
const change = changes[k];
|
|
195
|
+
for (; j < change.i2; j++)
|
|
196
|
+
out += record(' ', b[j]);
|
|
197
|
+
for (let i = change.i1; i < change.i1 + change.n1; i++)
|
|
198
|
+
out += record('-', a[i]);
|
|
199
|
+
for (j = change.i2; j < change.i2 + change.n2; j++)
|
|
200
|
+
out += record('+', b[j]);
|
|
201
|
+
}
|
|
202
|
+
for (; j < e2; j++)
|
|
203
|
+
out += record(' ', b[j]);
|
|
204
|
+
first = last + 1;
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
// ── Patch (diff.c builtin_diff) ─────────────────────────────────────────
|
|
209
|
+
/** buffer_is_binary: a NUL in the first 8000 bytes. */
|
|
210
|
+
export function isBinary(data) {
|
|
211
|
+
return data.subarray(0, 8000).includes(0);
|
|
212
|
+
}
|
|
213
|
+
function sameBytes(a, b) {
|
|
214
|
+
if (a.length !== b.length)
|
|
215
|
+
return false;
|
|
216
|
+
for (let i = 0; i < a.length; i++)
|
|
217
|
+
if (a[i] !== b[i])
|
|
218
|
+
return false;
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
function octal6(mode) {
|
|
222
|
+
return mode.toString(8).padStart(6, '0');
|
|
223
|
+
}
|
|
224
|
+
// A label holding a space gets a trailing tab, so patch(1) can find where the name ends.
|
|
225
|
+
function labelLine(marker, label) {
|
|
226
|
+
return `${marker} ${label}${label.includes(' ') ? '\t' : ''}\n`;
|
|
227
|
+
}
|
|
228
|
+
/** One file's `diff --git` section, exactly as `git diff` prints it without color. */
|
|
229
|
+
export function formatPatch(pair, context = DEFAULT_CONTEXT) {
|
|
230
|
+
const { one, two } = pair;
|
|
231
|
+
// run_diff: a change of kind is shown as the old one's deletion and the new one's creation.
|
|
232
|
+
if (pairStatus(pair) === 'T') {
|
|
233
|
+
return formatPatch({ one, two: absentSpec(two.path) }, context)
|
|
234
|
+
+ formatPatch({ one: absentSpec(one.path), two }, context);
|
|
235
|
+
}
|
|
236
|
+
// "Never use a non-valid filename anywhere if at all possible."
|
|
237
|
+
const nameA = one.valid ? one.path : two.path;
|
|
238
|
+
const nameB = two.valid ? two.path : nameA;
|
|
239
|
+
const aOne = quoteTwo('a/', nameA.startsWith('/') ? nameA.slice(1) : nameA);
|
|
240
|
+
const bTwo = quoteTwo('b/', nameB.startsWith('/') ? nameB.slice(1) : nameB);
|
|
241
|
+
const lbl0 = one.valid ? aOne : '/dev/null';
|
|
242
|
+
const lbl1 = two.valid ? bTwo : '/dev/null';
|
|
243
|
+
const index = one.oid === two.oid
|
|
244
|
+
? ''
|
|
245
|
+
: `index ${one.oid.slice(0, 7)}..${two.oid.slice(0, 7)}${one.mode === two.mode ? ` ${octal6(one.mode)}` : ''}\n`;
|
|
246
|
+
let header = `diff --git ${aOne} ${bTwo}\n`;
|
|
247
|
+
let mustShowHeader = true;
|
|
248
|
+
if (!one.valid)
|
|
249
|
+
header += `new file mode ${octal6(two.mode)}\n${index}`;
|
|
250
|
+
else if (!two.valid)
|
|
251
|
+
header += `deleted file mode ${octal6(one.mode)}\n${index}`;
|
|
252
|
+
else {
|
|
253
|
+
if (one.mode !== two.mode)
|
|
254
|
+
header += `old mode ${octal6(one.mode)}\nnew mode ${octal6(two.mode)}\n`;
|
|
255
|
+
else
|
|
256
|
+
mustShowHeader = pair.renameScore !== undefined;
|
|
257
|
+
if (pair.renameScore !== undefined) {
|
|
258
|
+
header += `similarity index ${similarityIndex(pair.renameScore)}%\n`
|
|
259
|
+
+ `rename from ${quotePath(one.path)}\nrename to ${quotePath(two.path)}\n`;
|
|
260
|
+
}
|
|
261
|
+
header += index;
|
|
262
|
+
}
|
|
263
|
+
if (isBinary(one.data) || isBinary(two.data)) {
|
|
264
|
+
if (one.valid && two.valid && sameBytes(one.data, two.data))
|
|
265
|
+
return mustShowHeader ? header : '';
|
|
266
|
+
return `${header}Binary files ${lbl0} and ${lbl1} differ\n`;
|
|
267
|
+
}
|
|
268
|
+
const hunks = formatHunks(diffText(one.data, two.data), context);
|
|
269
|
+
if (!hunks)
|
|
270
|
+
return mustShowHeader ? header : '';
|
|
271
|
+
return header + labelLine('---', lbl0) + labelLine('+++', lbl1) + hunks;
|
|
272
|
+
}
|
|
273
|
+
// ── Name lists ──────────────────────────────────────────────────────────
|
|
274
|
+
export function formatNameOnly(pair, z) {
|
|
275
|
+
return pathLine(pair.two.path, z);
|
|
276
|
+
}
|
|
277
|
+
export function formatNameStatus(pair, z) {
|
|
278
|
+
const status = pairStatus(pair);
|
|
279
|
+
if (status === 'R') {
|
|
280
|
+
const score = String(similarityIndex(pair.renameScore ?? 0)).padStart(3, '0');
|
|
281
|
+
return z
|
|
282
|
+
? `R${score}\0${binaryPath(pair.one.path)}\0${binaryPath(pair.two.path)}\0`
|
|
283
|
+
: `R${score}\t${quotePath(pair.one.path)}\t${quotePath(pair.two.path)}\n`;
|
|
284
|
+
}
|
|
285
|
+
const name = pair.one.mode ? pair.one.path : pair.two.path;
|
|
286
|
+
return z ? `${status}\0${binaryPath(name)}\0` : `${status}\t${quotePath(name)}\n`;
|
|
287
|
+
}
|
|
288
|
+
/** pprint_rename: `a => b`, with a shared directory prefix and suffix hoisted around braces. */
|
|
289
|
+
function renameName(from, to) {
|
|
290
|
+
const a = binaryPath(from);
|
|
291
|
+
const b = binaryPath(to);
|
|
292
|
+
const qa = cQuoted(a);
|
|
293
|
+
const qb = cQuoted(b);
|
|
294
|
+
if (qa !== null || qb !== null) {
|
|
295
|
+
return `${qa === null ? a : `"${qa}"`} => ${qb === null ? b : `"${qb}"`}`;
|
|
296
|
+
}
|
|
297
|
+
let prefix = 0;
|
|
298
|
+
for (let i = 0; i < a.length && i < b.length && a[i] === b[i]; i++) {
|
|
299
|
+
if (a[i] === '/')
|
|
300
|
+
prefix = i + 1;
|
|
301
|
+
}
|
|
302
|
+
// Both scans stop at a '/' or 0; with a prefix the suffix scan may reach its slash.
|
|
303
|
+
let suffix = 0;
|
|
304
|
+
const floor = prefix - (prefix ? 1 : 0);
|
|
305
|
+
const at = (s, k) => (k < s.length ? s[k] : '\0');
|
|
306
|
+
for (let i = a.length, j = b.length; i >= floor && j >= floor && at(a, i) === at(b, j); i--, j--) {
|
|
307
|
+
if (at(a, i) === '/')
|
|
308
|
+
suffix = a.length - i;
|
|
309
|
+
}
|
|
310
|
+
const aMid = Math.max(a.length - prefix - suffix, 0);
|
|
311
|
+
const bMid = Math.max(b.length - prefix - suffix, 0);
|
|
312
|
+
const middle = `${a.slice(prefix, prefix + aMid)} => ${b.slice(prefix, prefix + bMid)}`;
|
|
313
|
+
return prefix + suffix ? `${a.slice(0, prefix)}{${middle}}${a.slice(a.length - suffix)}` : middle;
|
|
314
|
+
}
|
|
315
|
+
export function statFile(pair) {
|
|
316
|
+
const { one, two } = pair;
|
|
317
|
+
const name = one.path === two.path ? quotePath(two.path) : renameName(one.path, two.path);
|
|
318
|
+
if (isBinary(one.data) || isBinary(two.data)) {
|
|
319
|
+
const differ = !(one.valid && two.valid && one.oid === two.oid);
|
|
320
|
+
return { name, binary: true, added: differ ? two.data.length : 0, deleted: differ ? one.data.length : 0 };
|
|
321
|
+
}
|
|
322
|
+
let added = 0;
|
|
323
|
+
let deleted = 0;
|
|
324
|
+
for (const change of diffText(one.data, two.data).changes) {
|
|
325
|
+
added += change.n2;
|
|
326
|
+
deleted += change.n1;
|
|
327
|
+
}
|
|
328
|
+
return { name, binary: false, added, deleted };
|
|
329
|
+
}
|
|
330
|
+
function decimalWidth(n) {
|
|
331
|
+
return String(n).length;
|
|
332
|
+
}
|
|
333
|
+
function scaleLinear(it, width, maxChange) {
|
|
334
|
+
if (!it)
|
|
335
|
+
return 0;
|
|
336
|
+
// At least one mark for any change: scale to one column less, then add one.
|
|
337
|
+
return 1 + Math.floor(it * (width - 1) / maxChange);
|
|
338
|
+
}
|
|
339
|
+
/** The --stat block for `columns` terminal columns (git's term_columns: $COLUMNS, else 80). */
|
|
340
|
+
export function formatStat(files, columns) {
|
|
341
|
+
if (files.length === 0)
|
|
342
|
+
return '';
|
|
343
|
+
let maxLen = 0;
|
|
344
|
+
let maxChange = 0;
|
|
345
|
+
let numberWidth = 0;
|
|
346
|
+
let binWidth = 0;
|
|
347
|
+
for (const file of files) {
|
|
348
|
+
maxLen = Math.max(maxLen, file.name.length);
|
|
349
|
+
if (file.binary) {
|
|
350
|
+
// "Bin XXX -> YYY bytes"
|
|
351
|
+
binWidth = Math.max(binWidth, 14 + decimalWidth(file.added) + decimalWidth(file.deleted));
|
|
352
|
+
numberWidth = 3;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
maxChange = Math.max(maxChange, file.added + file.deleted);
|
|
356
|
+
}
|
|
357
|
+
numberWidth = Math.max(decimalWidth(maxChange), numberWidth);
|
|
358
|
+
// Guarantee 3/8*16 == 6 columns for the graph and 5/8*16 == 10 for the name.
|
|
359
|
+
const width = Math.max(columns, 16 + 6 + numberWidth);
|
|
360
|
+
let graphWidth = maxChange + 4 > binWidth ? maxChange : binWidth - 4;
|
|
361
|
+
let nameWidth = maxLen;
|
|
362
|
+
if (nameWidth + numberWidth + 6 + graphWidth > width) {
|
|
363
|
+
const graphShare = Math.floor(width * 3 / 8) - numberWidth - 6;
|
|
364
|
+
if (graphWidth > graphShare)
|
|
365
|
+
graphWidth = Math.max(graphShare, 6);
|
|
366
|
+
if (nameWidth > width - numberWidth - 6 - graphWidth)
|
|
367
|
+
nameWidth = width - numberWidth - 6 - graphWidth;
|
|
368
|
+
else
|
|
369
|
+
graphWidth = width - numberWidth - 6 - nameWidth;
|
|
370
|
+
}
|
|
371
|
+
let out = '';
|
|
372
|
+
let insertions = 0;
|
|
373
|
+
let deletions = 0;
|
|
374
|
+
for (const file of files) {
|
|
375
|
+
let name = file.name;
|
|
376
|
+
let prefix = '';
|
|
377
|
+
let len = nameWidth;
|
|
378
|
+
if (nameWidth < name.length) {
|
|
379
|
+
prefix = '...';
|
|
380
|
+
len = Math.max(len - 3, 0);
|
|
381
|
+
name = name.slice(name.length - len);
|
|
382
|
+
const slash = name.indexOf('/');
|
|
383
|
+
if (slash >= 0)
|
|
384
|
+
name = name.slice(slash);
|
|
385
|
+
}
|
|
386
|
+
const lead = ` ${prefix}${name}${' '.repeat(Math.max(len - name.length, 0))} | `;
|
|
387
|
+
if (file.binary) {
|
|
388
|
+
out += lead + 'Bin'.padStart(numberWidth)
|
|
389
|
+
+ (file.added || file.deleted ? ` ${file.deleted} -> ${file.added} bytes\n` : '\n');
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
insertions += file.added;
|
|
393
|
+
deletions += file.deleted;
|
|
394
|
+
const total = file.added + file.deleted;
|
|
395
|
+
let add = file.added;
|
|
396
|
+
let del = file.deleted;
|
|
397
|
+
if (graphWidth <= maxChange) {
|
|
398
|
+
let marks = scaleLinear(total, graphWidth, maxChange);
|
|
399
|
+
if (marks < 2 && add && del)
|
|
400
|
+
marks = 2;
|
|
401
|
+
if (add < del) {
|
|
402
|
+
add = scaleLinear(add, graphWidth, maxChange);
|
|
403
|
+
del = marks - add;
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
del = scaleLinear(del, graphWidth, maxChange);
|
|
407
|
+
add = marks - del;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
out += `${lead}${String(total).padStart(numberWidth)}${total ? ' ' : ''}${'+'.repeat(add)}${'-'.repeat(del)}\n`;
|
|
411
|
+
}
|
|
412
|
+
let summary = files.length === 1 ? ` ${files.length} file changed` : ` ${files.length} files changed`;
|
|
413
|
+
if (insertions || deletions === 0)
|
|
414
|
+
summary += `, ${insertions} insertion${insertions === 1 ? '' : 's'}(+)`;
|
|
415
|
+
if (deletions || insertions === 0)
|
|
416
|
+
summary += `, ${deletions} deletion${deletions === 1 ? '' : 's'}(-)`;
|
|
417
|
+
return `${out}${summary}\n`;
|
|
418
|
+
}
|
|
419
|
+
// ── Renames (diffcore-rename.c, diffcore-delta.c) ───────────────────────
|
|
420
|
+
export const MAX_SCORE = 60000;
|
|
421
|
+
export const DEFAULT_RENAME_SCORE = 30000;
|
|
422
|
+
const DEFAULT_RENAME_LIMIT = 1000;
|
|
423
|
+
const NUM_CANDIDATE_PER_DST = 4;
|
|
424
|
+
const HASHBASE = 107927;
|
|
425
|
+
export function similarityIndex(score) {
|
|
426
|
+
return Math.floor(score * 100 / MAX_SCORE);
|
|
427
|
+
}
|
|
428
|
+
/** parse_rename_score (`5`, `50%` and `.5` are all 50%), or null when anything follows the number. */
|
|
429
|
+
export function parseRenameScore(text) {
|
|
430
|
+
let num = 0;
|
|
431
|
+
let scale = 1;
|
|
432
|
+
let dot = false;
|
|
433
|
+
let i = 0;
|
|
434
|
+
for (; i < text.length; i++) {
|
|
435
|
+
const ch = text[i];
|
|
436
|
+
if (!dot && ch === '.') {
|
|
437
|
+
scale = 1;
|
|
438
|
+
dot = true;
|
|
439
|
+
}
|
|
440
|
+
else if (ch === '%') {
|
|
441
|
+
scale = dot ? scale * 100 : 100;
|
|
442
|
+
i++;
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
else if (ch >= '0' && ch <= '9') {
|
|
446
|
+
if (scale < 100000) {
|
|
447
|
+
scale *= 10;
|
|
448
|
+
num = num * 10 + Number(ch);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (i < text.length)
|
|
456
|
+
return null;
|
|
457
|
+
return num >= scale ? MAX_SCORE : Math.floor(MAX_SCORE * num / scale);
|
|
458
|
+
}
|
|
459
|
+
const isRegular = (mode) => (mode & S_IFMT) === 0o100000;
|
|
460
|
+
const basenameOf = (path) => path.slice(path.lastIndexOf('/') + 1);
|
|
461
|
+
/** hash_chars: bytes per span (a line, or 64 bytes) keyed by the span's hash; CR before LF is skipped in text. */
|
|
462
|
+
function spanCounts(data) {
|
|
463
|
+
const text = !isBinary(data);
|
|
464
|
+
const counts = new Map();
|
|
465
|
+
let n = 0;
|
|
466
|
+
let accum1 = 0;
|
|
467
|
+
let accum2 = 0;
|
|
468
|
+
for (let i = 0; i < data.length; i++) {
|
|
469
|
+
const c = data[i];
|
|
470
|
+
if (text && c === 0x0d && i + 1 < data.length && data[i + 1] === 0x0a)
|
|
471
|
+
continue;
|
|
472
|
+
const old1 = accum1;
|
|
473
|
+
accum1 = (((accum1 << 7) ^ (accum2 >>> 25)) + c) >>> 0;
|
|
474
|
+
accum2 = ((accum2 << 7) ^ (old1 >>> 25)) >>> 0;
|
|
475
|
+
if (++n < 64 && c !== 0x0a)
|
|
476
|
+
continue;
|
|
477
|
+
const hash = ((accum1 + Math.imul(accum2, 0x61)) >>> 0) % HASHBASE;
|
|
478
|
+
counts.set(hash, (counts.get(hash) ?? 0) + n);
|
|
479
|
+
n = accum1 = accum2 = 0;
|
|
480
|
+
}
|
|
481
|
+
if (n > 0) {
|
|
482
|
+
const hash = ((accum1 + Math.imul(accum2, 0x61)) >>> 0) % HASHBASE;
|
|
483
|
+
counts.set(hash, (counts.get(hash) ?? 0) + n);
|
|
484
|
+
}
|
|
485
|
+
return counts;
|
|
486
|
+
}
|
|
487
|
+
/** estimate_similarity: how much of the larger side the source's spans account for, out of MAX_SCORE. */
|
|
488
|
+
function estimateSimilarity(src, dst, minimumScore) {
|
|
489
|
+
const maxSize = Math.max(src.size, dst.size);
|
|
490
|
+
const delta = maxSize - Math.min(src.size, dst.size);
|
|
491
|
+
// Edits that change the size this much are not considered.
|
|
492
|
+
if (maxSize * (MAX_SCORE - minimumScore) < delta * MAX_SCORE)
|
|
493
|
+
return 0;
|
|
494
|
+
if (!dst.size)
|
|
495
|
+
return 0;
|
|
496
|
+
let copied = 0;
|
|
497
|
+
for (const [hash, count] of src.spans) {
|
|
498
|
+
const other = dst.spans.get(hash);
|
|
499
|
+
if (other !== undefined)
|
|
500
|
+
copied += Math.min(count, other);
|
|
501
|
+
}
|
|
502
|
+
return Math.floor(copied * MAX_SCORE / maxSize);
|
|
503
|
+
}
|
|
504
|
+
/** score_compare: the unused slots last, then by score, then by a shared basename. */
|
|
505
|
+
function scoreCompare(a, b) {
|
|
506
|
+
if (a.dst < 0)
|
|
507
|
+
return b.dst >= 0 ? 1 : 0;
|
|
508
|
+
if (b.dst < 0)
|
|
509
|
+
return -1;
|
|
510
|
+
if (a.score === b.score)
|
|
511
|
+
return b.nameScore - a.nameScore;
|
|
512
|
+
return b.score - a.score;
|
|
513
|
+
}
|
|
514
|
+
function recordIfBetter(slots, candidate) {
|
|
515
|
+
let worst = 0;
|
|
516
|
+
for (let i = 1; i < slots.length; i++)
|
|
517
|
+
if (scoreCompare(slots[i], slots[worst]) > 0)
|
|
518
|
+
worst = i;
|
|
519
|
+
if (scoreCompare(slots[worst], candidate) > 0)
|
|
520
|
+
slots[worst] = candidate;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* git diff's default rename detection over a path-ordered queue: exact
|
|
524
|
+
* renames, then unique basenames at a higher bar, then the similarity
|
|
525
|
+
* matrix, skipped (as git skips it) past `renameLimit` squared pairs. A
|
|
526
|
+
* rename takes its destination's place in the queue. `neededRenameLimit`
|
|
527
|
+
* is non-zero when the matrix was skipped.
|
|
528
|
+
*/
|
|
529
|
+
export async function detectRenames(queue, read, { minimumScore = DEFAULT_RENAME_SCORE, renameLimit = DEFAULT_RENAME_LIMIT } = {}) {
|
|
530
|
+
const srcs = [];
|
|
531
|
+
const dsts = [];
|
|
532
|
+
const srcOf = new Map();
|
|
533
|
+
const dstOf = new Map();
|
|
534
|
+
for (const pair of queue) {
|
|
535
|
+
if (!pair.one && pair.two) {
|
|
536
|
+
const dst = { side: pair.two };
|
|
537
|
+
dsts.push(dst);
|
|
538
|
+
dstOf.set(pair, dst);
|
|
539
|
+
}
|
|
540
|
+
else if (pair.one && !pair.two) {
|
|
541
|
+
const src = { side: pair.one, used: false };
|
|
542
|
+
srcs.push(src);
|
|
543
|
+
srcOf.set(pair, src);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
const contents = new Map();
|
|
547
|
+
const content = async (side) => {
|
|
548
|
+
let known = contents.get(side);
|
|
549
|
+
if (!known) {
|
|
550
|
+
const data = await read(side);
|
|
551
|
+
contents.set(side, known = { size: data.length, spans: spanCounts(data) });
|
|
552
|
+
}
|
|
553
|
+
return known;
|
|
554
|
+
};
|
|
555
|
+
const similarity = async (src, dst, minimum) => (isRegular(src.mode) && isRegular(dst.mode)
|
|
556
|
+
? estimateSimilarity(await content(src), await content(dst), minimum)
|
|
557
|
+
: 0);
|
|
558
|
+
const record = (dst, src, score) => {
|
|
559
|
+
src.used = true;
|
|
560
|
+
dst.rename = { one: src.side, two: dst.side, renameScore: score };
|
|
561
|
+
};
|
|
562
|
+
let neededRenameLimit = 0;
|
|
563
|
+
match: {
|
|
564
|
+
if (!dsts.length || !srcs.length)
|
|
565
|
+
break match;
|
|
566
|
+
// Exact: the first unused source with the same blob, one with the same basename first.
|
|
567
|
+
const byOid = new Map();
|
|
568
|
+
for (const src of srcs) {
|
|
569
|
+
const list = byOid.get(src.side.oid);
|
|
570
|
+
if (list)
|
|
571
|
+
list.push(src);
|
|
572
|
+
else
|
|
573
|
+
byOid.set(src.side.oid, [src]);
|
|
574
|
+
}
|
|
575
|
+
let renames = 0;
|
|
576
|
+
for (const dst of dsts) {
|
|
577
|
+
let best = null;
|
|
578
|
+
let bestScore = -1;
|
|
579
|
+
let tries = 100;
|
|
580
|
+
for (const src of byOid.get(dst.side.oid) ?? []) {
|
|
581
|
+
// Only regular files may change mode across a rename.
|
|
582
|
+
if ((!isRegular(src.side.mode) || !isRegular(dst.side.mode)) && src.side.mode !== dst.side.mode)
|
|
583
|
+
continue;
|
|
584
|
+
if (src.used)
|
|
585
|
+
continue;
|
|
586
|
+
const score = 1 + (basenameOf(src.side.path) === basenameOf(dst.side.path) ? 1 : 0);
|
|
587
|
+
if (score > bestScore) {
|
|
588
|
+
best = src;
|
|
589
|
+
bestScore = score;
|
|
590
|
+
if (score === 2)
|
|
591
|
+
break;
|
|
592
|
+
}
|
|
593
|
+
if (!--tries)
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
if (best) {
|
|
597
|
+
record(dst, best, MAX_SCORE);
|
|
598
|
+
renames++;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (minimumScore === MAX_SCORE)
|
|
602
|
+
break match;
|
|
603
|
+
// A basename unique among both sides pairs up if it clears the higher bar.
|
|
604
|
+
let pool = srcs.filter((src) => !src.used);
|
|
605
|
+
const minBasenameScore = minimumScore + Math.trunc(0.5 * (MAX_SCORE - minimumScore));
|
|
606
|
+
const srcBases = new Map();
|
|
607
|
+
pool.forEach((src, i) => {
|
|
608
|
+
const base = basenameOf(src.side.path);
|
|
609
|
+
srcBases.set(base, srcBases.has(base) ? -1 : i);
|
|
610
|
+
});
|
|
611
|
+
const dstBases = new Map();
|
|
612
|
+
for (const dst of dsts) {
|
|
613
|
+
if (dst.rename)
|
|
614
|
+
continue;
|
|
615
|
+
const base = basenameOf(dst.side.path);
|
|
616
|
+
dstBases.set(base, dstBases.has(base) ? null : dst);
|
|
617
|
+
}
|
|
618
|
+
for (const [i, src] of pool.entries()) {
|
|
619
|
+
const base = basenameOf(src.side.path);
|
|
620
|
+
const dst = dstBases.get(base);
|
|
621
|
+
if (!dst || srcBases.get(base) !== i || dst.rename)
|
|
622
|
+
continue;
|
|
623
|
+
const score = await similarity(src.side, dst.side, minBasenameScore);
|
|
624
|
+
if (score < minBasenameScore)
|
|
625
|
+
continue;
|
|
626
|
+
record(dst, src, score);
|
|
627
|
+
renames++;
|
|
628
|
+
}
|
|
629
|
+
pool = pool.filter((src) => !src.used);
|
|
630
|
+
const remaining = dsts.length - renames;
|
|
631
|
+
if (!remaining || !pool.length)
|
|
632
|
+
break match;
|
|
633
|
+
if (renameLimit > 0 && remaining * pool.length > renameLimit * renameLimit) {
|
|
634
|
+
neededRenameLimit = Math.max(pool.length, remaining);
|
|
635
|
+
break match;
|
|
636
|
+
}
|
|
637
|
+
for (const src of pool)
|
|
638
|
+
if (isRegular(src.side.mode))
|
|
639
|
+
await content(src.side);
|
|
640
|
+
for (const dst of dsts)
|
|
641
|
+
if (!dst.rename && isRegular(dst.side.mode))
|
|
642
|
+
await content(dst.side);
|
|
643
|
+
const matrix = [];
|
|
644
|
+
for (const [d, dst] of dsts.entries()) {
|
|
645
|
+
if (dst.rename)
|
|
646
|
+
continue;
|
|
647
|
+
const slots = Array.from({ length: NUM_CANDIDATE_PER_DST }, () => ({ dst: -1, src: -1, score: 0, nameScore: 0 }));
|
|
648
|
+
for (const [s, src] of pool.entries()) {
|
|
649
|
+
const score = isRegular(src.side.mode) && isRegular(dst.side.mode)
|
|
650
|
+
? estimateSimilarity(contents.get(src.side), contents.get(dst.side), minimumScore)
|
|
651
|
+
: 0;
|
|
652
|
+
recordIfBetter(slots, { dst: d, src: s, score, nameScore: basenameOf(src.side.path) === basenameOf(dst.side.path) ? 1 : 0 });
|
|
653
|
+
}
|
|
654
|
+
matrix.push(...slots);
|
|
655
|
+
}
|
|
656
|
+
matrix.sort(scoreCompare);
|
|
657
|
+
for (const candidate of matrix) {
|
|
658
|
+
if (candidate.dst < 0 || candidate.score < minimumScore)
|
|
659
|
+
break;
|
|
660
|
+
const dst = dsts[candidate.dst];
|
|
661
|
+
const src = pool[candidate.src];
|
|
662
|
+
if (dst.rename || src.used)
|
|
663
|
+
continue;
|
|
664
|
+
record(dst, src, candidate.score);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
const out = [];
|
|
668
|
+
for (const pair of queue) {
|
|
669
|
+
const dst = dstOf.get(pair);
|
|
670
|
+
if (dst)
|
|
671
|
+
out.push(dst.rename ?? pair);
|
|
672
|
+
else if (!srcOf.get(pair)?.used)
|
|
673
|
+
out.push(pair);
|
|
674
|
+
}
|
|
675
|
+
return { queue: out, neededRenameLimit };
|
|
676
|
+
}
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* git-bundle.generated.ts — AUTO-GENERATED. DO NOT EDIT.
|
|
2
|
+
* git-bundle.generated.ts — AUTO-GENERATED by scripts/bundle-git.mjs. DO NOT EDIT.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Consumed by git-network-facet.ts: passed to LOADER.load()'s
|
|
9
|
-
* `modules` record so the facet can `import` isomorphic-git.
|
|
10
|
-
*
|
|
11
|
-
* Size: 482.3 KiB
|
|
4
|
+
* Pins the staged copy of vendor/git.generated.mjs (isomorphic-git from
|
|
5
|
+
* github:AshishKumar4/cf-git + isomorphic-git/http/web) that the git network
|
|
6
|
+
* facet imports as `git-bundle.js`. GIT_BUNDLE_SHA256 is verified at fetch time.
|
|
12
7
|
*/
|
|
13
|
-
export declare const
|
|
14
|
-
export declare const
|
|
8
|
+
export declare const GIT_BUNDLE_ENTRY: string;
|
|
9
|
+
export declare const GIT_BUNDLE_BUILD_ID: string;
|
|
10
|
+
export declare const GIT_BUNDLE_SHA256: string;
|
|
15
11
|
//# sourceMappingURL=git-bundle.generated.d.ts.map
|