@hominis/fireforge 0.35.0 → 0.36.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 +29 -0
- package/README.md +34 -9
- package/dist/bin/fireforge.js +21 -6
- package/dist/src/cli.js +22 -5
- package/dist/src/commands/bootstrap-checks.js +0 -1
- package/dist/src/commands/bootstrap.d.ts +2 -1
- package/dist/src/commands/bootstrap.js +16 -7
- package/dist/src/commands/build.js +4 -3
- package/dist/src/commands/config.js +30 -10
- package/dist/src/commands/discard.js +18 -3
- package/dist/src/commands/doctor/post-rebase-audit.js +0 -1
- package/dist/src/commands/doctor-check-core.js +0 -1
- package/dist/src/commands/doctor-external-toolchains.d.ts +3 -0
- package/dist/src/commands/doctor-external-toolchains.js +54 -0
- package/dist/src/commands/doctor-furnace-jar.js +0 -1
- package/dist/src/commands/doctor-furnace-manifest-sync.js +0 -1
- package/dist/src/commands/doctor-furnace.js +2 -2
- package/dist/src/commands/doctor-working-tree.js +0 -1
- package/dist/src/commands/doctor.js +4 -1
- package/dist/src/commands/download.js +28 -13
- package/dist/src/commands/export-all.js +5 -3
- package/dist/src/commands/export-flow.js +12 -7
- package/dist/src/commands/export-placement-gate.js +0 -1
- package/dist/src/commands/export-placement-policy.js +0 -1
- package/dist/src/commands/export-shared.d.ts +5 -1
- package/dist/src/commands/export-shared.js +18 -6
- package/dist/src/commands/export.js +15 -10
- package/dist/src/commands/furnace/apply.js +22 -6
- package/dist/src/commands/furnace/chrome-doc-remove.js +0 -1
- package/dist/src/commands/furnace/chrome-doc-templates.js +0 -1
- package/dist/src/commands/furnace/chrome-doc-tests.js +0 -1
- package/dist/src/commands/furnace/chrome-doc.js +0 -1
- package/dist/src/commands/furnace/create-browser-test.js +0 -1
- package/dist/src/commands/furnace/create-dry-run.js +0 -1
- package/dist/src/commands/furnace/create-features.js +0 -1
- package/dist/src/commands/furnace/create-mochikit.js +0 -1
- package/dist/src/commands/furnace/create-readback.js +0 -1
- package/dist/src/commands/furnace/create-templates.js +0 -1
- package/dist/src/commands/furnace/create-validation.js +0 -1
- package/dist/src/commands/furnace/create-xpcshell.js +0 -1
- package/dist/src/commands/furnace/create.js +0 -1
- package/dist/src/commands/furnace/deploy.js +7 -83
- package/dist/src/commands/furnace/diff.js +0 -1
- package/dist/src/commands/furnace/index.js +59 -25
- package/dist/src/commands/furnace/init.js +0 -1
- package/dist/src/commands/furnace/list.js +0 -1
- package/dist/src/commands/furnace/override.js +0 -1
- package/dist/src/commands/furnace/preview.js +3 -3
- package/dist/src/commands/furnace/refresh.js +0 -1
- package/dist/src/commands/furnace/remove-state.d.ts +15 -0
- package/dist/src/commands/furnace/remove-state.js +47 -1
- package/dist/src/commands/furnace/remove.js +3 -9
- package/dist/src/commands/furnace/rename-browser-test.js +1 -5
- package/dist/src/commands/furnace/rename-helpers.d.ts +6 -0
- package/dist/src/commands/furnace/rename-helpers.js +30 -1
- package/dist/src/commands/furnace/rename-xpcshell.js +7 -6
- package/dist/src/commands/furnace/rename.js +15 -36
- package/dist/src/commands/furnace/scan.js +0 -1
- package/dist/src/commands/furnace/status.js +0 -1
- package/dist/src/commands/furnace/sync.js +76 -4
- package/dist/src/commands/furnace/validate.js +0 -1
- package/dist/src/commands/furnace/validation-output.js +0 -1
- package/dist/src/commands/import.js +35 -9
- package/dist/src/commands/lint-per-patch.js +19 -7
- package/dist/src/commands/lint.js +13 -3
- package/dist/src/commands/manifest.js +1 -2
- package/dist/src/commands/package.js +2 -1
- package/dist/src/commands/patch/compact.js +1 -1
- package/dist/src/commands/patch/delete.js +1 -1
- package/dist/src/commands/patch/index.js +1 -1
- package/dist/src/commands/patch/lint-ignore.js +21 -6
- package/dist/src/commands/patch/move-files.js +1 -1
- package/dist/src/commands/patch/patch-context.js +0 -1
- package/dist/src/commands/patch/rename.js +4 -4
- package/dist/src/commands/patch/reorder.js +3 -4
- package/dist/src/commands/patch/split-plan.js +2 -1
- package/dist/src/commands/patch/split.js +4 -4
- package/dist/src/commands/patch/staged-dependency.js +1 -1
- package/dist/src/commands/patch/tier.js +18 -5
- package/dist/src/commands/re-export-bulk-scan.js +0 -1
- package/dist/src/commands/re-export-files.js +1 -1
- package/dist/src/commands/re-export-options.js +0 -1
- package/dist/src/commands/re-export-scan.js +0 -1
- package/dist/src/commands/re-export.js +6 -4
- package/dist/src/commands/rebase/abort.js +2 -9
- package/dist/src/commands/rebase/confirm.js +0 -1
- package/dist/src/commands/rebase/conflict-summary.js +0 -1
- package/dist/src/commands/rebase/continue.js +0 -1
- package/dist/src/commands/rebase/index.d.ts +1 -1
- package/dist/src/commands/rebase/index.js +10 -15
- package/dist/src/commands/rebase/patch-loop.js +38 -7
- package/dist/src/commands/rebase/summary.js +2 -3
- package/dist/src/commands/rebase.js +0 -1
- package/dist/src/commands/register.js +1 -1
- package/dist/src/commands/reset.js +16 -9
- package/dist/src/commands/resolve.js +2 -2
- package/dist/src/commands/run.js +4 -4
- package/dist/src/commands/setup-support.js +0 -1
- package/dist/src/commands/setup.js +1 -2
- package/dist/src/commands/source.js +1 -2
- package/dist/src/commands/status-output.js +1 -12
- package/dist/src/commands/status.js +147 -133
- package/dist/src/commands/test-appdir.js +0 -1
- package/dist/src/commands/test-diagnose.js +18 -2
- package/dist/src/commands/test-modes.d.ts +15 -0
- package/dist/src/commands/test-modes.js +61 -0
- package/dist/src/commands/test-register.d.ts +4 -0
- package/dist/src/commands/test-register.js +64 -0
- package/dist/src/commands/test-run.d.ts +23 -7
- package/dist/src/commands/test-run.js +15 -14
- package/dist/src/commands/test.d.ts +0 -4
- package/dist/src/commands/test.js +85 -86
- package/dist/src/commands/token-coverage.js +0 -1
- package/dist/src/commands/token.js +1 -2
- package/dist/src/commands/typecheck.js +1 -1
- package/dist/src/commands/verify.js +3 -9
- package/dist/src/commands/watch.js +5 -1
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/ast-utils.js +0 -1
- package/dist/src/core/brand-validation.js +0 -1
- package/dist/src/core/branding.js +0 -1
- package/dist/src/core/browser-wire.js +0 -1
- package/dist/src/core/build-audit-platform.js +0 -1
- package/dist/src/core/build-audit-registration.js +0 -1
- package/dist/src/core/build-audit-resolve.js +0 -1
- package/dist/src/core/build-audit-transforms.js +0 -1
- package/dist/src/core/build-audit.js +0 -1
- package/dist/src/core/build-baseline-types.js +0 -1
- package/dist/src/core/build-baseline.js +0 -1
- package/dist/src/core/build-prepare.js +2 -2
- package/dist/src/core/config-mutate.js +0 -1
- package/dist/src/core/config-paths.d.ts +2 -2
- package/dist/src/core/config-paths.js +6 -1
- package/dist/src/core/config-state.js +0 -1
- package/dist/src/core/config-validate-patch-policy.js +0 -1
- package/dist/src/core/config-validate-test-toolchains.d.ts +8 -0
- package/dist/src/core/config-validate-test-toolchains.js +105 -0
- package/dist/src/core/config-validate.js +3 -1
- package/dist/src/core/config.js +0 -1
- package/dist/src/core/destructive.js +0 -1
- package/dist/src/core/diff-hunks.js +13 -7
- package/dist/src/core/engine-changes.js +0 -1
- package/dist/src/core/engine-session-lock.d.ts +12 -0
- package/dist/src/core/engine-session-lock.js +45 -0
- package/dist/src/core/file-lock.d.ts +8 -0
- package/dist/src/core/file-lock.js +157 -48
- package/dist/src/core/firefox-archive.d.ts +4 -0
- package/dist/src/core/firefox-archive.js +6 -1
- package/dist/src/core/firefox-cache.js +60 -4
- package/dist/src/core/firefox-download.js +0 -1
- package/dist/src/core/firefox-extract.js +75 -16
- package/dist/src/core/firefox-ignorefile.js +0 -1
- package/dist/src/core/firefox.d.ts +12 -0
- package/dist/src/core/firefox.js +64 -2
- package/dist/src/core/furnace-apply-dry-run.js +0 -1
- package/dist/src/core/furnace-apply-ftl.d.ts +7 -3
- package/dist/src/core/furnace-apply-ftl.js +10 -5
- package/dist/src/core/furnace-apply-helpers.js +0 -1
- package/dist/src/core/furnace-apply-output.js +2 -2
- package/dist/src/core/furnace-apply.d.ts +7 -1
- package/dist/src/core/furnace-apply.js +47 -13
- package/dist/src/core/furnace-checksum-utils.js +0 -1
- package/dist/src/core/furnace-config-array-utils.js +0 -1
- package/dist/src/core/furnace-config-custom.js +0 -1
- package/dist/src/core/furnace-config-order.js +0 -1
- package/dist/src/core/furnace-config-tokens.js +0 -1
- package/dist/src/core/furnace-config-validate.js +0 -1
- package/dist/src/core/furnace-config.d.ts +11 -0
- package/dist/src/core/furnace-config.js +37 -1
- package/dist/src/core/furnace-constants.js +0 -1
- package/dist/src/core/furnace-css-fragments.js +0 -1
- package/dist/src/core/furnace-graph-utils.js +0 -1
- package/dist/src/core/furnace-jsconfig.d.ts +2 -3
- package/dist/src/core/furnace-jsconfig.js +25 -18
- package/dist/src/core/furnace-marker.js +0 -1
- package/dist/src/core/furnace-operation.js +0 -1
- package/dist/src/core/furnace-refresh.js +0 -1
- package/dist/src/core/furnace-registration-ast.d.ts +14 -0
- package/dist/src/core/furnace-registration-ast.js +1 -2
- package/dist/src/core/furnace-registration-remove.js +0 -1
- package/dist/src/core/furnace-registration-validate.js +1 -5
- package/dist/src/core/furnace-registration.js +0 -1
- package/dist/src/core/furnace-rollback.js +0 -1
- package/dist/src/core/furnace-scanner.js +0 -1
- package/dist/src/core/furnace-staleness.js +0 -1
- package/dist/src/core/furnace-state-persist.d.ts +72 -0
- package/dist/src/core/furnace-state-persist.js +118 -0
- package/dist/src/core/furnace-step-errors.d.ts +23 -0
- package/dist/src/core/furnace-step-errors.js +25 -0
- package/dist/src/core/furnace-stories.js +0 -1
- package/dist/src/core/furnace-validate-accessibility.js +0 -1
- package/dist/src/core/furnace-validate-checks.js +0 -1
- package/dist/src/core/furnace-validate-compatibility.js +0 -1
- package/dist/src/core/furnace-validate-helpers.js +0 -1
- package/dist/src/core/furnace-validate-registration.js +7 -7
- package/dist/src/core/furnace-validate-structure.d.ts +8 -0
- package/dist/src/core/furnace-validate-structure.js +11 -2
- package/dist/src/core/furnace-validate.js +0 -1
- package/dist/src/core/furnace-version-drift.d.ts +6 -3
- package/dist/src/core/furnace-version-drift.js +6 -4
- package/dist/src/core/git-base.js +0 -1
- package/dist/src/core/git-diff.js +0 -1
- package/dist/src/core/git-file-ops.js +0 -1
- package/dist/src/core/git-status.d.ts +13 -0
- package/dist/src/core/git-status.js +32 -2
- package/dist/src/core/git.d.ts +10 -1
- package/dist/src/core/git.js +63 -6
- package/dist/src/core/license-headers.js +0 -1
- package/dist/src/core/lint-cache.js +0 -1
- package/dist/src/core/lint-projection.js +0 -1
- package/dist/src/core/mach-build-artifacts.js +0 -1
- package/dist/src/core/mach-error-hints.js +0 -1
- package/dist/src/core/mach-mozconfig.js +0 -1
- package/dist/src/core/mach-python.js +0 -1
- package/dist/src/core/mach-resource-shim.js +0 -1
- package/dist/src/core/mach.js +0 -1
- package/dist/src/core/manifest-helpers.js +0 -1
- package/dist/src/core/manifest-register.js +0 -1
- package/dist/src/core/manifest-rules.js +0 -1
- package/dist/src/core/manifest-tokenizers.js +0 -1
- package/dist/src/core/marionette-port.d.ts +8 -0
- package/dist/src/core/marionette-port.js +34 -3
- package/dist/src/core/marionette-preflight.js +0 -1
- package/dist/src/core/markdown-table.js +0 -1
- package/dist/src/core/ownership-table.js +2 -9
- package/dist/src/core/parser-fallback.js +0 -1
- package/dist/src/core/patch-apply-fuzz.d.ts +27 -9
- package/dist/src/core/patch-apply-fuzz.js +71 -34
- package/dist/src/core/patch-apply.d.ts +30 -0
- package/dist/src/core/patch-apply.js +101 -18
- package/dist/src/core/patch-artifact-normalize.d.ts +18 -5
- package/dist/src/core/patch-artifact-normalize.js +19 -15
- package/dist/src/core/patch-export-coverage.js +0 -1
- package/dist/src/core/patch-export-metadata.js +0 -1
- package/dist/src/core/patch-export-update.js +2 -3
- package/dist/src/core/patch-export.d.ts +2 -6
- package/dist/src/core/patch-export.js +24 -48
- package/dist/src/core/patch-files.js +0 -1
- package/dist/src/core/patch-identifier-suggest.js +0 -1
- package/dist/src/core/patch-lint-checkjs.js +0 -1
- package/dist/src/core/patch-lint-chrome-jsdoc.js +0 -1
- package/dist/src/core/patch-lint-cross.js +0 -1
- package/dist/src/core/patch-lint-css.js +0 -1
- package/dist/src/core/patch-lint-diff-tag.js +0 -1
- package/dist/src/core/patch-lint-diff.d.ts +2 -1
- package/dist/src/core/patch-lint-diff.js +17 -32
- package/dist/src/core/patch-lint-imports.js +0 -1
- package/dist/src/core/patch-lint-jsdoc.js +0 -1
- package/dist/src/core/patch-lint-observer.js +0 -1
- package/dist/src/core/patch-lint-ownership.js +0 -1
- package/dist/src/core/patch-lint-reexports.js +0 -1
- package/dist/src/core/patch-lint.d.ts +37 -2
- package/dist/src/core/patch-lint.js +47 -34
- package/dist/src/core/patch-lock.js +0 -1
- package/dist/src/core/patch-manifest-consistency.js +9 -1
- package/dist/src/core/patch-manifest-io.d.ts +16 -0
- package/dist/src/core/patch-manifest-io.js +30 -5
- package/dist/src/core/patch-manifest-query.js +18 -10
- package/dist/src/core/patch-manifest-resolve.js +0 -1
- package/dist/src/core/patch-manifest-validate.js +0 -1
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-manifest.js +1 -2
- package/dist/src/core/patch-parse.d.ts +59 -0
- package/dist/src/core/patch-parse.js +209 -92
- package/dist/src/core/patch-policy.js +0 -1
- package/dist/src/core/patch-registration-refs.js +16 -26
- package/dist/src/core/patch-source-metadata.js +0 -1
- package/dist/src/core/patch-transform.d.ts +4 -0
- package/dist/src/core/patch-transform.js +30 -38
- package/dist/src/core/rebase-session.js +0 -1
- package/dist/src/core/register-browser-content.js +0 -1
- package/dist/src/core/register-module.js +0 -1
- package/dist/src/core/register-result.js +0 -1
- package/dist/src/core/register-scaffold.js +0 -1
- package/dist/src/core/register-shared-css.js +0 -1
- package/dist/src/core/register-test-manifest.js +0 -1
- package/dist/src/core/register-xpcshell-test.js +0 -1
- package/dist/src/core/shared-ftl.js +0 -1
- package/dist/src/core/signal-critical.js +0 -1
- package/dist/src/core/smoke-patterns.js +0 -1
- package/dist/src/core/state-file.js +0 -1
- package/dist/src/core/status-classify.d.ts +16 -0
- package/dist/src/core/status-classify.js +24 -16
- package/dist/src/core/test-harness-crash.d.ts +64 -7
- package/dist/src/core/test-harness-crash.js +196 -15
- package/dist/src/core/test-harness-output.js +0 -1
- package/dist/src/core/test-path-scope.d.ts +21 -13
- package/dist/src/core/test-path-scope.js +43 -28
- package/dist/src/core/test-stale-check.js +0 -1
- package/dist/src/core/test-stale-symlink.js +0 -1
- package/dist/src/core/test-xpcshell-retry.js +0 -1
- package/dist/src/core/token-coverage.js +0 -1
- package/dist/src/core/token-dark-mode.js +0 -1
- package/dist/src/core/token-docs.js +0 -1
- package/dist/src/core/token-manager.js +0 -1
- package/dist/src/core/token-scaffold.js +0 -1
- package/dist/src/core/token-variant.js +0 -1
- package/dist/src/core/toolchain-preflight.d.ts +31 -10
- package/dist/src/core/toolchain-preflight.js +97 -36
- package/dist/src/core/typecheck-shim.js +13 -1
- package/dist/src/core/typecheck.js +0 -1
- package/dist/src/core/wire-destroy.js +0 -1
- package/dist/src/core/wire-dom-fragment.js +0 -1
- package/dist/src/core/wire-init.js +0 -1
- package/dist/src/core/wire-subscript.js +0 -1
- package/dist/src/core/wire-targets.js +0 -1
- package/dist/src/core/wire-utils.js +0 -1
- package/dist/src/core/xpcshell-appdir.js +1 -4
- package/dist/src/errors/base.d.ts +29 -0
- package/dist/src/errors/base.js +35 -1
- package/dist/src/errors/build.js +0 -1
- package/dist/src/errors/codes.d.ts +6 -0
- package/dist/src/errors/codes.js +6 -1
- package/dist/src/errors/config.js +0 -1
- package/dist/src/errors/download.js +0 -1
- package/dist/src/errors/furnace.d.ts +11 -1
- package/dist/src/errors/furnace.js +6 -5
- package/dist/src/errors/git.js +0 -1
- package/dist/src/errors/patch.d.ts +17 -0
- package/dist/src/errors/patch.js +29 -1
- package/dist/src/errors/rebase.js +0 -1
- package/dist/src/errors/run.js +1 -1
- package/dist/src/index.js +0 -1
- package/dist/src/types/cli.js +0 -1
- package/dist/src/types/commands/index.js +0 -1
- package/dist/src/types/commands/options.d.ts +13 -5
- package/dist/src/types/commands/options.js +0 -1
- package/dist/src/types/commands/patches.d.ts +8 -0
- package/dist/src/types/commands/patches.js +0 -1
- package/dist/src/types/commands/project.d.ts +11 -2
- package/dist/src/types/commands/project.js +0 -1
- package/dist/src/types/config.d.ts +32 -0
- package/dist/src/types/config.js +0 -1
- package/dist/src/types/furnace.d.ts +10 -0
- package/dist/src/types/furnace.js +0 -1
- package/dist/src/types/index.js +0 -1
- package/dist/src/types/typecheck.js +0 -1
- package/dist/src/utils/elapsed.js +0 -1
- package/dist/src/utils/errors.js +0 -1
- package/dist/src/utils/fs.js +37 -5
- package/dist/src/utils/logger.d.ts +7 -0
- package/dist/src/utils/logger.js +85 -3
- package/dist/src/utils/options.d.ts +15 -0
- package/dist/src/utils/options.js +25 -1
- package/dist/src/utils/package-root.js +11 -4
- package/dist/src/utils/parse.js +0 -1
- package/dist/src/utils/paths.js +0 -1
- package/dist/src/utils/platform.js +0 -1
- package/dist/src/utils/process.d.ts +33 -4
- package/dist/src/utils/process.js +183 -28
- package/dist/src/utils/regex.d.ts +13 -3
- package/dist/src/utils/regex.js +53 -4
- package/dist/src/utils/validation.d.ts +0 -12
- package/dist/src/utils/validation.js +13 -26
- package/package.json +8 -7
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { warn } from '../utils/logger.js';
|
|
1
3
|
import { ensureGit, git } from './git-base.js';
|
|
2
4
|
/**
|
|
3
5
|
* Parses NUL-delimited porcelain status output.
|
|
@@ -43,22 +45,51 @@ export async function getWorkingTreeStatus(repoDir) {
|
|
|
43
45
|
await ensureGit();
|
|
44
46
|
return parsePorcelainStatus(await git(['status', '--porcelain=v1', '-z'], repoDir));
|
|
45
47
|
}
|
|
48
|
+
/** Default per-directory cap for untracked-directory expansion. */
|
|
49
|
+
const DEFAULT_MAX_UNTRACKED_FILES_PER_DIR = 5000;
|
|
50
|
+
/**
|
|
51
|
+
* Resolves the per-directory expansion cap, honoring the same
|
|
52
|
+
* FIREFORGE_MAX_UNTRACKED_FILES override the status command documents.
|
|
53
|
+
*/
|
|
54
|
+
export function resolveMaxUntrackedFilesPerDir() {
|
|
55
|
+
const raw = process.env['FIREFORGE_MAX_UNTRACKED_FILES'];
|
|
56
|
+
if (raw === undefined || raw.length === 0)
|
|
57
|
+
return DEFAULT_MAX_UNTRACKED_FILES_PER_DIR;
|
|
58
|
+
const parsed = Number.parseInt(raw, 10);
|
|
59
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
60
|
+
warn(`Ignoring FIREFORGE_MAX_UNTRACKED_FILES="${raw}" — expected a positive integer. Falling back to ${String(DEFAULT_MAX_UNTRACKED_FILES_PER_DIR)}.`);
|
|
61
|
+
return DEFAULT_MAX_UNTRACKED_FILES_PER_DIR;
|
|
62
|
+
}
|
|
63
|
+
return parsed;
|
|
64
|
+
}
|
|
46
65
|
/**
|
|
47
66
|
* Expands collapsed untracked directory entries into individual file entries.
|
|
48
67
|
* Git status may report "?? dir/" instead of listing each file underneath.
|
|
68
|
+
*
|
|
69
|
+
* Expansion is capped per directory (FIREFORGE_MAX_UNTRACKED_FILES, default
|
|
70
|
+
* 5000) with a warning on overflow. The cap used to exist only in the
|
|
71
|
+
* status command's private expander; this shared one — used by reset,
|
|
72
|
+
* discard, verify, lint, export-all — was unbounded, so e.g. `reset
|
|
73
|
+
* --dry-run` after an interrupted download (unborn HEAD, the entire ~300k
|
|
74
|
+
* file tree untracked) enumerated and printed every file.
|
|
75
|
+
*
|
|
49
76
|
* @param repoDir - Repository directory
|
|
50
77
|
* @param entries - Parsed status entries
|
|
51
78
|
* @returns Status entries with untracked directories expanded to individual files
|
|
52
79
|
*/
|
|
53
80
|
export async function expandUntrackedDirectoryEntries(repoDir, entries) {
|
|
54
81
|
const expanded = [];
|
|
82
|
+
const maxPerDir = resolveMaxUntrackedFilesPerDir();
|
|
55
83
|
for (const entry of entries) {
|
|
56
84
|
if (!entry.isUntracked || !entry.file.endsWith('/')) {
|
|
57
85
|
expanded.push(entry);
|
|
58
86
|
continue;
|
|
59
87
|
}
|
|
60
88
|
const individualFiles = await getUntrackedFilesInDir(repoDir, entry.file);
|
|
61
|
-
|
|
89
|
+
if (individualFiles.length > maxPerDir) {
|
|
90
|
+
warn(`Untracked directory ${entry.file} contains ${String(individualFiles.length)} files — only the first ${String(maxPerDir)} are listed. Add a .gitignore entry or clean the directory.`);
|
|
91
|
+
}
|
|
92
|
+
for (const file of individualFiles.slice(0, maxPerDir)) {
|
|
62
93
|
expanded.push({
|
|
63
94
|
status: '??',
|
|
64
95
|
indexStatus: '?',
|
|
@@ -125,4 +156,3 @@ export async function getDirtyFiles(repoDir, files) {
|
|
|
125
156
|
const untracked = untrackedOutput.split('\n').filter((line) => line.trim().length > 0);
|
|
126
157
|
return [...new Set([...tracked, ...untracked])].sort();
|
|
127
158
|
}
|
|
128
|
-
//# sourceMappingURL=git-status.js.map
|
package/dist/src/core/git.d.ts
CHANGED
|
@@ -53,12 +53,21 @@ export declare function applyPatch(patchPath: string, repoDir: string, options?:
|
|
|
53
53
|
* Applies a patch idempotently using reverse-forward pattern.
|
|
54
54
|
* First tries to reverse the patch (in case it's already applied),
|
|
55
55
|
* then applies it forward.
|
|
56
|
+
*
|
|
56
57
|
* @param patchPath - Path to the patch file
|
|
57
58
|
* @param repoDir - Repository directory
|
|
58
|
-
* @param options -
|
|
59
|
+
* @param options.reject - Fall back to `git apply --reject`
|
|
60
|
+
* @param options.protectedFiles - Files that must NOT be reset to HEAD by
|
|
61
|
+
* the recovery step. Callers applying a patch QUEUE pass the files
|
|
62
|
+
* already touched by previously applied patches in the same run: two
|
|
63
|
+
* overlapping patches (an `--allow-overlap` queue) share files, and the
|
|
64
|
+
* blanket `checkout HEAD` used to wipe the earlier patch's changes from
|
|
65
|
+
* the shared file before applying the later one — leaving the engine in
|
|
66
|
+
* a hybrid state the summary never described.
|
|
59
67
|
*/
|
|
60
68
|
export declare function applyPatchIdempotent(patchPath: string, repoDir: string, options?: {
|
|
61
69
|
reject?: boolean;
|
|
70
|
+
protectedFiles?: ReadonlySet<string>;
|
|
62
71
|
}): Promise<void>;
|
|
63
72
|
/**
|
|
64
73
|
* Reverses a previously applied patch.
|
package/dist/src/core/git.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
import { readdir, stat } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { ExecTimeoutError } from '../errors/base.js';
|
|
4
5
|
import { GitError, GitIndexingTimeoutError, GitIndexLockError, PatchApplyError, } from '../errors/git.js';
|
|
5
6
|
import { elapsedSince } from '../utils/elapsed.js';
|
|
6
7
|
import { toError } from '../utils/errors.js';
|
|
@@ -8,7 +9,7 @@ import { pathExists, pathExistsStrict, removeFile } from '../utils/fs.js';
|
|
|
8
9
|
import { verbose } from '../utils/logger.js';
|
|
9
10
|
import { exec } from '../utils/process.js';
|
|
10
11
|
import { ensureFirefoxIgnorefileCompatibility } from './firefox-ignorefile.js';
|
|
11
|
-
import { configureGitPerformance, ensureGit, git, GIT_ADD_CHUNK_TIMEOUT_ENV_VAR, GIT_ADD_CHUNK_TIMEOUT_MS, GIT_ADD_TIMEOUT_MS, } from './git-base.js';
|
|
12
|
+
import { chunkPathspecs, configureGitPerformance, ensureGit, git, GIT_ADD_CHUNK_TIMEOUT_ENV_VAR, GIT_ADD_CHUNK_TIMEOUT_MS, GIT_ADD_TIMEOUT_MS, } from './git-base.js';
|
|
12
13
|
import { getWorkingTreeStatus } from './git-status.js';
|
|
13
14
|
// ── Functions that remain in this file ──
|
|
14
15
|
/**
|
|
@@ -47,6 +48,11 @@ const GIT_ADD_ENV = { GIT_INDEX_THREADS: '0' };
|
|
|
47
48
|
* detection here needs to recognise that shape too.
|
|
48
49
|
*/
|
|
49
50
|
function isTimeoutError(error) {
|
|
51
|
+
// `exec` now rejects with a typed ExecTimeoutError when its `timeout`
|
|
52
|
+
// option fires (the raw AbortError check below is kept for defence in
|
|
53
|
+
// depth against other abort paths).
|
|
54
|
+
if (error instanceof ExecTimeoutError)
|
|
55
|
+
return true;
|
|
50
56
|
if (error instanceof Error && error.name === 'AbortError')
|
|
51
57
|
return true;
|
|
52
58
|
if (!(error instanceof GitError))
|
|
@@ -410,9 +416,17 @@ export async function applyPatch(patchPath, repoDir, options = {}) {
|
|
|
410
416
|
* Applies a patch idempotently using reverse-forward pattern.
|
|
411
417
|
* First tries to reverse the patch (in case it's already applied),
|
|
412
418
|
* then applies it forward.
|
|
419
|
+
*
|
|
413
420
|
* @param patchPath - Path to the patch file
|
|
414
421
|
* @param repoDir - Repository directory
|
|
415
|
-
* @param options -
|
|
422
|
+
* @param options.reject - Fall back to `git apply --reject`
|
|
423
|
+
* @param options.protectedFiles - Files that must NOT be reset to HEAD by
|
|
424
|
+
* the recovery step. Callers applying a patch QUEUE pass the files
|
|
425
|
+
* already touched by previously applied patches in the same run: two
|
|
426
|
+
* overlapping patches (an `--allow-overlap` queue) share files, and the
|
|
427
|
+
* blanket `checkout HEAD` used to wipe the earlier patch's changes from
|
|
428
|
+
* the shared file before applying the later one — leaving the engine in
|
|
429
|
+
* a hybrid state the summary never described.
|
|
416
430
|
*/
|
|
417
431
|
export async function applyPatchIdempotent(patchPath, repoDir, options = {}) {
|
|
418
432
|
await ensureGit();
|
|
@@ -428,15 +442,59 @@ export async function applyPatchIdempotent(patchPath, repoDir, options = {}) {
|
|
|
428
442
|
const touchedFiles = listResult.stdout
|
|
429
443
|
.split('\n')
|
|
430
444
|
.map((line) => line.split('\t')[2])
|
|
431
|
-
.filter((f) => !!f)
|
|
445
|
+
.filter((f) => !!f)
|
|
446
|
+
.filter((f) => !(options.protectedFiles?.has(f) ?? false));
|
|
432
447
|
if (touchedFiles.length > 0) {
|
|
433
|
-
|
|
434
|
-
await exec('git', ['checkout', 'HEAD', '--', ...touchedFiles], { cwd: repoDir });
|
|
448
|
+
await restoreFilesToHead(repoDir, patchPath, touchedFiles);
|
|
435
449
|
}
|
|
436
450
|
}
|
|
437
451
|
// Apply forward
|
|
438
452
|
await applyPatch(patchPath, repoDir, options);
|
|
439
453
|
}
|
|
454
|
+
/**
|
|
455
|
+
* Restores `files` to their HEAD state, deleting files the patch would
|
|
456
|
+
* CREATE (present on disk from a partial apply but absent in HEAD — for
|
|
457
|
+
* those, `git checkout HEAD --` fails, and the historical code ignored
|
|
458
|
+
* that failure entirely, leaving stray files that made the subsequent
|
|
459
|
+
* forward apply die with "already exists" and no hint why).
|
|
460
|
+
*
|
|
461
|
+
* Every git invocation is chunked via {@link chunkPathspecs} so a very
|
|
462
|
+
* large patch cannot hit `E2BIG`, and every exit code is checked — a
|
|
463
|
+
* silent failed restore is precisely the confusing state this recovery
|
|
464
|
+
* step exists to prevent.
|
|
465
|
+
*/
|
|
466
|
+
async function restoreFilesToHead(repoDir, patchPath, files) {
|
|
467
|
+
// Partition into tracked-in-HEAD (restore via checkout) and absent-in-HEAD
|
|
468
|
+
// (patch-created leftovers: delete from the worktree).
|
|
469
|
+
const trackedInHead = new Set();
|
|
470
|
+
for (const chunk of chunkPathspecs(files)) {
|
|
471
|
+
const lsTree = await exec('git', ['ls-tree', '-r', '--name-only', 'HEAD', '--', ...chunk], {
|
|
472
|
+
cwd: repoDir,
|
|
473
|
+
});
|
|
474
|
+
if (lsTree.exitCode !== 0) {
|
|
475
|
+
throw new PatchApplyError(patchPath, new Error(`git ls-tree failed while preparing recovery: ${lsTree.stderr.trim()}`));
|
|
476
|
+
}
|
|
477
|
+
for (const line of lsTree.stdout.split('\n')) {
|
|
478
|
+
if (line.length > 0)
|
|
479
|
+
trackedInHead.add(line);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const toCheckout = files.filter((f) => trackedInHead.has(f));
|
|
483
|
+
const toDelete = files.filter((f) => !trackedInHead.has(f));
|
|
484
|
+
for (const chunk of chunkPathspecs(toCheckout)) {
|
|
485
|
+
const checkout = await exec('git', ['checkout', 'HEAD', '--', ...chunk], { cwd: repoDir });
|
|
486
|
+
if (checkout.exitCode !== 0) {
|
|
487
|
+
throw new PatchApplyError(patchPath, new Error(`Could not restore patch-touched files to HEAD before applying: ${checkout.stderr.trim()}`));
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
for (const file of toDelete) {
|
|
491
|
+
const target = join(repoDir, file);
|
|
492
|
+
if (await pathExists(target)) {
|
|
493
|
+
await removeFile(target);
|
|
494
|
+
verbose(`Removed stray patch-created file before apply: ${file}`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
440
498
|
/**
|
|
441
499
|
* Reverses a previously applied patch.
|
|
442
500
|
* @param patchPath - Path to the patch file
|
|
@@ -524,4 +582,3 @@ export async function getStatusWithCodes(repoDir) {
|
|
|
524
582
|
file: entry.file,
|
|
525
583
|
}));
|
|
526
584
|
}
|
|
527
|
-
//# sourceMappingURL=git.js.map
|
|
@@ -113,4 +113,3 @@ export async function generateMozconfig(configsDir, engineDir, config) {
|
|
|
113
113
|
// the single source of truth for both the render and the sanity-check.
|
|
114
114
|
await assertBrandingMozconfigAgreement(engineDir, outputPath, config);
|
|
115
115
|
}
|
|
116
|
-
//# sourceMappingURL=mach-mozconfig.js.map
|
package/dist/src/core/mach.js
CHANGED
|
@@ -48,6 +48,14 @@ export declare function probeMarionettePort(port?: number): Promise<MarionettePo
|
|
|
48
48
|
export declare function assertMarionettePortAvailable(port?: number, options?: {
|
|
49
49
|
binaryName?: string;
|
|
50
50
|
}): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Ensures the Marionette port is usable, optionally terminating a stale
|
|
53
|
+
* Firefox-family browser holder first. Unrelated listeners still fail.
|
|
54
|
+
*/
|
|
55
|
+
export declare function ensureMarionettePortAvailable(port?: number, options?: {
|
|
56
|
+
binaryName?: string;
|
|
57
|
+
killStaleBrowser?: boolean;
|
|
58
|
+
}): Promise<void>;
|
|
51
59
|
/**
|
|
52
60
|
* Extracts a `--marionette-port=N` (or `--marionette-port N`) argument from
|
|
53
61
|
* a list of forwarded mach args, if present. Used so an operator passing the
|
|
@@ -56,7 +56,7 @@ const BROWSER_BASENAMES = new Set([
|
|
|
56
56
|
* so a fork that ships under a custom name (e.g.
|
|
57
57
|
* `mybrowser-nightly`) is still recognised as a browser.
|
|
58
58
|
*/
|
|
59
|
-
function
|
|
59
|
+
function isBrowserMarionettePortHolder(holder, binaryName) {
|
|
60
60
|
if (/\s-marionette(?:\s|$)/.test(holder.commandLine)) {
|
|
61
61
|
return true;
|
|
62
62
|
}
|
|
@@ -195,7 +195,7 @@ export async function assertMarionettePortAvailable(port = DEFAULT_MARIONETTE_PO
|
|
|
195
195
|
if (!probe.inUse || !probe.holder)
|
|
196
196
|
return;
|
|
197
197
|
const holder = probe.holder;
|
|
198
|
-
if (
|
|
198
|
+
if (isBrowserMarionettePortHolder(holder, options.binaryName)) {
|
|
199
199
|
const killHint = process.platform === 'win32'
|
|
200
200
|
? `Stop-Process -Id ${holder.pid} -Force`
|
|
201
201
|
: `kill ${holder.pid} # or "kill -9 ${holder.pid}" if it doesn't exit`;
|
|
@@ -212,6 +212,38 @@ export async function assertMarionettePortAvailable(port = DEFAULT_MARIONETTE_PO
|
|
|
212
212
|
throw new GeneralError(`Marionette port ${port} is already in use by ${holder.command} (PID ${holder.pid}). ` +
|
|
213
213
|
`This is not a FireForge-launched browser; stop the holder process or free the port before rerunning.`);
|
|
214
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Ensures the Marionette port is usable, optionally terminating a stale
|
|
217
|
+
* Firefox-family browser holder first. Unrelated listeners still fail.
|
|
218
|
+
*/
|
|
219
|
+
export async function ensureMarionettePortAvailable(port = DEFAULT_MARIONETTE_PORT, options = {}) {
|
|
220
|
+
const probe = await probeMarionettePort(port);
|
|
221
|
+
if (!probe.inUse || !probe.holder)
|
|
222
|
+
return;
|
|
223
|
+
const holder = probe.holder;
|
|
224
|
+
const isBrowser = isBrowserMarionettePortHolder(holder, options.binaryName);
|
|
225
|
+
if (!options.killStaleBrowser || !isBrowser) {
|
|
226
|
+
await assertMarionettePortAvailable(port, options);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
if (process.platform === 'win32') {
|
|
231
|
+
await exec('powershell.exe', [
|
|
232
|
+
'-NoProfile',
|
|
233
|
+
'-NonInteractive',
|
|
234
|
+
'-Command',
|
|
235
|
+
`Stop-Process -Id ${holder.pid} -Force`,
|
|
236
|
+
]);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
process.kill(holder.pid, 'SIGTERM');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
throw new GeneralError(`Marionette port ${port} is held by stale browser ${holder.command} (PID ${holder.pid}), ` +
|
|
244
|
+
`but FireForge could not terminate it: ${error instanceof Error ? error.message : String(error)}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
215
247
|
/**
|
|
216
248
|
* Extracts a `--marionette-port=N` (or `--marionette-port N`) argument from
|
|
217
249
|
* a list of forwarded mach args, if present. Used so an operator passing the
|
|
@@ -350,4 +382,3 @@ export function isMarionetteFlavor(testPaths, machArgs) {
|
|
|
350
382
|
}
|
|
351
383
|
return false;
|
|
352
384
|
}
|
|
353
|
-
//# sourceMappingURL=marionette-port.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { info } from '../utils/logger.js';
|
|
2
|
+
import { buildPatchClaims } from './status-classify.js';
|
|
2
3
|
/**
|
|
3
4
|
* Builds the flat ownership table from the manifest, worktree status, and
|
|
4
5
|
* the duplicate-new-file-creation map produced by cross-patch lint.
|
|
@@ -29,14 +30,7 @@ import { info } from '../utils/logger.js';
|
|
|
29
30
|
* paths with a `.length > 1` owner list become duplicate-create conflicts
|
|
30
31
|
*/
|
|
31
32
|
export function buildOwnershipTable(manifestPatches, worktreeFiles, newFileCreatorsByPath, classifications = new Map()) {
|
|
32
|
-
const ownersByPath =
|
|
33
|
-
for (const patch of manifestPatches) {
|
|
34
|
-
for (const file of patch.filesAffected) {
|
|
35
|
-
const existing = ownersByPath.get(file) ?? [];
|
|
36
|
-
existing.push(patch.filename);
|
|
37
|
-
ownersByPath.set(file, existing);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
33
|
+
const ownersByPath = buildPatchClaims(manifestPatches);
|
|
40
34
|
// Merge duplicate-new-file-creation findings. The structured helper
|
|
41
35
|
// returns all new-file paths, so we filter to the ones with more
|
|
42
36
|
// than one creator. Paths are added to the table even when no patch
|
|
@@ -165,4 +159,3 @@ export function renderOwnershipTable(rows) {
|
|
|
165
159
|
info(`| ${pad(row.path, pathWidth)} | ${pad(ownerCell, ownerWidth)} | ${pad(stateCell, stateWidth)} |`);
|
|
166
160
|
}
|
|
167
161
|
}
|
|
168
|
-
//# sourceMappingURL=ownership-table.js.map
|
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Fuzz-like patch application with escalating context reduction.
|
|
3
3
|
*
|
|
4
|
-
* Tries an exact `git apply` first, then retries with
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Tries an exact `git apply` first, then retries with git's real
|
|
5
|
+
* drift-tolerance knob: `-C<n>` (reduced context matching). `git apply`
|
|
6
|
+
* has NO `--fuzz` option — that flag belongs to GNU `patch(1)`. The
|
|
7
|
+
* original implementation passed `--fuzz=N`, which git rejects with a
|
|
8
|
+
* usage error (exit 129) that the escalation loop swallowed, so every
|
|
9
|
+
* "fuzzy" attempt silently failed and patches that would have applied
|
|
10
|
+
* with context drift were reported as hard conflicts (2026-07-05 review,
|
|
11
|
+
* finding H1; verified against git 2.50). If all context-reduction levels
|
|
12
|
+
* fail, falls through to `git apply --reject` so the user gets `.rej`
|
|
13
|
+
* files for manual resolution.
|
|
14
|
+
*
|
|
15
|
+
* Semantics note: GNU fuzz *fuzzily matches* context; `-C<n>` *ignores*
|
|
16
|
+
* outer context lines instead. Step k maps to `-C(3-k)` (git's default
|
|
17
|
+
* context width is 3), so the final step `-C0` matches on line positions
|
|
18
|
+
* and -/+ content alone — the rough analogue of GNU `--fuzz=3` on a
|
|
19
|
+
* 3-context patch.
|
|
7
20
|
*/
|
|
8
21
|
export interface FuzzyApplyResult {
|
|
9
22
|
/** Whether the patch was applied successfully. */
|
|
10
23
|
success: boolean;
|
|
11
|
-
/**
|
|
24
|
+
/** Context-reduction steps taken (0 = exact match, k = applied with `-C(3-k)`). */
|
|
12
25
|
fuzzFactor: number;
|
|
13
26
|
/** Error description when `success` is false. */
|
|
14
27
|
error?: string;
|
|
@@ -16,14 +29,19 @@ export interface FuzzyApplyResult {
|
|
|
16
29
|
rejectFiles?: string[];
|
|
17
30
|
}
|
|
18
31
|
/**
|
|
19
|
-
* Attempts to apply a patch with escalating
|
|
32
|
+
* Attempts to apply a patch with escalating context reduction.
|
|
20
33
|
*
|
|
21
|
-
* 1. `git apply --check`
|
|
22
|
-
* 2. `git apply
|
|
34
|
+
* 1. `git apply --check` exact, then with `-C2`, `-C1`, `-C0`
|
|
35
|
+
* 2. `git apply` (same flags) at the first passing level
|
|
23
36
|
* 3. Fall through to `git apply --reject` if nothing succeeds
|
|
24
37
|
*
|
|
25
38
|
* @param patchPath - Absolute path to the `.patch` file
|
|
26
39
|
* @param engineDir - Working directory (engine/)
|
|
27
|
-
* @param maxFuzz - Maximum
|
|
40
|
+
* @param maxFuzz - Maximum context-reduction steps to try (default 3,
|
|
41
|
+
* 0 = exact only). Values above 3 are capped: git cannot reduce below
|
|
42
|
+
* `-C0`.
|
|
43
|
+
* @throws InvalidArgumentError when `maxFuzz` is not a non-negative integer —
|
|
44
|
+
* a NaN or negative value would silently skip every apply attempt
|
|
45
|
+
* (including the exact-match one) and fall straight through to `--reject`
|
|
28
46
|
*/
|
|
29
47
|
export declare function applyPatchWithFuzz(patchPath: string, engineDir: string, maxFuzz?: number): Promise<FuzzyApplyResult>;
|
|
@@ -1,70 +1,107 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Fuzz-like patch application with escalating context reduction.
|
|
4
4
|
*
|
|
5
|
-
* Tries an exact `git apply` first, then retries with
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Tries an exact `git apply` first, then retries with git's real
|
|
6
|
+
* drift-tolerance knob: `-C<n>` (reduced context matching). `git apply`
|
|
7
|
+
* has NO `--fuzz` option — that flag belongs to GNU `patch(1)`. The
|
|
8
|
+
* original implementation passed `--fuzz=N`, which git rejects with a
|
|
9
|
+
* usage error (exit 129) that the escalation loop swallowed, so every
|
|
10
|
+
* "fuzzy" attempt silently failed and patches that would have applied
|
|
11
|
+
* with context drift were reported as hard conflicts (2026-07-05 review,
|
|
12
|
+
* finding H1; verified against git 2.50). If all context-reduction levels
|
|
13
|
+
* fail, falls through to `git apply --reject` so the user gets `.rej`
|
|
14
|
+
* files for manual resolution.
|
|
15
|
+
*
|
|
16
|
+
* Semantics note: GNU fuzz *fuzzily matches* context; `-C<n>` *ignores*
|
|
17
|
+
* outer context lines instead. Step k maps to `-C(3-k)` (git's default
|
|
18
|
+
* context width is 3), so the final step `-C0` matches on line positions
|
|
19
|
+
* and -/+ content alone — the rough analogue of GNU `--fuzz=3` on a
|
|
20
|
+
* 3-context patch.
|
|
8
21
|
*/
|
|
22
|
+
import { InvalidArgumentError } from '../errors/base.js';
|
|
9
23
|
import { verbose } from '../utils/logger.js';
|
|
10
24
|
import { exec } from '../utils/process.js';
|
|
11
25
|
import { ensureGit } from './git-base.js';
|
|
26
|
+
/** Git's default diff context width — the ceiling for reduction steps. */
|
|
27
|
+
const GIT_DEFAULT_CONTEXT = 3;
|
|
12
28
|
// ── Implementation ──
|
|
13
29
|
/**
|
|
14
|
-
* Attempts to apply a patch with escalating
|
|
30
|
+
* Attempts to apply a patch with escalating context reduction.
|
|
15
31
|
*
|
|
16
|
-
* 1. `git apply --check`
|
|
17
|
-
* 2. `git apply
|
|
32
|
+
* 1. `git apply --check` exact, then with `-C2`, `-C1`, `-C0`
|
|
33
|
+
* 2. `git apply` (same flags) at the first passing level
|
|
18
34
|
* 3. Fall through to `git apply --reject` if nothing succeeds
|
|
19
35
|
*
|
|
20
36
|
* @param patchPath - Absolute path to the `.patch` file
|
|
21
37
|
* @param engineDir - Working directory (engine/)
|
|
22
|
-
* @param maxFuzz - Maximum
|
|
38
|
+
* @param maxFuzz - Maximum context-reduction steps to try (default 3,
|
|
39
|
+
* 0 = exact only). Values above 3 are capped: git cannot reduce below
|
|
40
|
+
* `-C0`.
|
|
41
|
+
* @throws InvalidArgumentError when `maxFuzz` is not a non-negative integer —
|
|
42
|
+
* a NaN or negative value would silently skip every apply attempt
|
|
43
|
+
* (including the exact-match one) and fall straight through to `--reject`
|
|
23
44
|
*/
|
|
24
45
|
export async function applyPatchWithFuzz(patchPath, engineDir, maxFuzz = 3) {
|
|
46
|
+
if (!Number.isInteger(maxFuzz) || maxFuzz < 0) {
|
|
47
|
+
throw new InvalidArgumentError(`maxFuzz must be a non-negative integer, got ${String(maxFuzz)}.`, 'maxFuzz');
|
|
48
|
+
}
|
|
25
49
|
await ensureGit();
|
|
26
|
-
// Try exact match first, then escalate
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
50
|
+
// Try exact match first, then escalate context reduction.
|
|
51
|
+
const maxSteps = Math.min(maxFuzz, GIT_DEFAULT_CONTEXT);
|
|
52
|
+
for (let step = 0; step <= maxSteps; step++) {
|
|
53
|
+
const contextArgs = step > 0 ? [`-C${String(GIT_DEFAULT_CONTEXT - step)}`] : [];
|
|
54
|
+
const check = await exec('git', ['apply', '--check', ...contextArgs, '--', patchPath], {
|
|
30
55
|
cwd: engineDir,
|
|
31
56
|
});
|
|
32
57
|
if (check.exitCode === 0) {
|
|
33
58
|
// --check passed: apply for real
|
|
34
|
-
const apply = await exec('git', ['apply', ...
|
|
59
|
+
const apply = await exec('git', ['apply', ...contextArgs, '--', patchPath], {
|
|
35
60
|
cwd: engineDir,
|
|
36
61
|
});
|
|
37
62
|
if (apply.exitCode === 0) {
|
|
38
|
-
if (
|
|
39
|
-
verbose(`Patch applied with
|
|
63
|
+
if (step > 0) {
|
|
64
|
+
verbose(`Patch applied with reduced context (-C${String(GIT_DEFAULT_CONTEXT - step)}): ${patchPath}`);
|
|
40
65
|
}
|
|
41
|
-
return { success: true, fuzzFactor:
|
|
66
|
+
return { success: true, fuzzFactor: step };
|
|
42
67
|
}
|
|
43
|
-
// Unlikely: --check passed but apply failed; fall through to next
|
|
44
|
-
verbose(`git apply
|
|
68
|
+
// Unlikely: --check passed but apply failed; fall through to next step
|
|
69
|
+
verbose(`git apply -C${String(GIT_DEFAULT_CONTEXT - step)} --check passed but apply failed: ${apply.stderr.trim()}`);
|
|
45
70
|
}
|
|
46
71
|
}
|
|
47
|
-
// All
|
|
72
|
+
// All context-reduction levels failed → generate .rej files for manual resolution
|
|
48
73
|
const rejectResult = await exec('git', ['apply', '--reject', '--', patchPath], {
|
|
49
74
|
cwd: engineDir,
|
|
50
75
|
});
|
|
51
|
-
const errorMessage = rejectResult.stderr.trim() || 'All
|
|
52
|
-
// Extract .rej file paths from stderr
|
|
53
|
-
const rejectFiles = [];
|
|
54
|
-
for (const line of rejectResult.stderr.split('\n')) {
|
|
55
|
-
const match = line.match(/Applying patch .* with (\d+) reject/);
|
|
56
|
-
if (match)
|
|
57
|
-
continue;
|
|
58
|
-
const rejMatch = line.match(/Rejected hunk.*to (.+\.rej)/);
|
|
59
|
-
if (rejMatch?.[1]) {
|
|
60
|
-
rejectFiles.push(rejMatch[1]);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
76
|
+
const errorMessage = rejectResult.stderr.trim() || 'All context-reduction levels failed';
|
|
63
77
|
return {
|
|
64
78
|
success: false,
|
|
65
|
-
fuzzFactor:
|
|
79
|
+
fuzzFactor: maxSteps,
|
|
66
80
|
error: errorMessage,
|
|
67
|
-
rejectFiles,
|
|
81
|
+
rejectFiles: extractRejectFiles(rejectResult.stderr),
|
|
68
82
|
};
|
|
69
83
|
}
|
|
70
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Extracts `.rej` file paths from `git apply --reject` stderr.
|
|
86
|
+
*
|
|
87
|
+
* Git's actual output shape (verified against git 2.50):
|
|
88
|
+
*
|
|
89
|
+
* Applying patch f.txt with 1 reject...
|
|
90
|
+
* Rejected hunk #1.
|
|
91
|
+
*
|
|
92
|
+
* The reject file is `<file>.rej` next to the target. The previous regex
|
|
93
|
+
* (`Rejected hunk.*to (.+\.rej)`) targeted GNU `patch(1)`'s "saving
|
|
94
|
+
* rejects to file X.rej" phrasing, which git never prints — so
|
|
95
|
+
* `rejectFiles` was always empty and the conflict-summary ".rej files
|
|
96
|
+
* created" hint never fired.
|
|
97
|
+
*/
|
|
98
|
+
function extractRejectFiles(stderr) {
|
|
99
|
+
const rejectFiles = [];
|
|
100
|
+
for (const line of stderr.split('\n')) {
|
|
101
|
+
const match = /^Applying patch (.+) with \d+ rejects?\.\.\./.exec(line);
|
|
102
|
+
if (match?.[1]) {
|
|
103
|
+
rejectFiles.push(`${match[1]}.rej`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return rejectFiles;
|
|
107
|
+
}
|
|
@@ -40,6 +40,28 @@ export interface ApplyPatchesOptions {
|
|
|
40
40
|
*/
|
|
41
41
|
untilFilename?: string | undefined;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Decides whether a patch filename matches an `--until` identifier.
|
|
45
|
+
*
|
|
46
|
+
* The identifier is one of three shapes and the three shapes must stay
|
|
47
|
+
* disjoint so the operator can reason about which patch they picked:
|
|
48
|
+
*
|
|
49
|
+
* 1. **Exact filename** — `005-foo.patch`. Matches only that filename.
|
|
50
|
+
* 2. **Filename without extension** — `005-foo`. Matches `005-foo.patch`.
|
|
51
|
+
* 3. **Bare numeric ordinal** — `5` or `005`. Matches the patch whose
|
|
52
|
+
* order prefix parses to the same integer (so `5` and `005` both
|
|
53
|
+
* match `005-foo.patch`, and `05` matches too because parseInt
|
|
54
|
+
* normalizes leading zeros).
|
|
55
|
+
*
|
|
56
|
+
* A purely-numeric identifier is treated **only** as an ordinal: it does
|
|
57
|
+
* not also match a filename that happens to literally equal the digits.
|
|
58
|
+
* That would require a patch literally named `5` or `005` — which would
|
|
59
|
+
* collide with the filename prefix anyway — and we prefer the explicit
|
|
60
|
+
* ordinal interpretation. The earlier form's `patchFilename === needle`
|
|
61
|
+
* short-circuit was kept behind the numeric gate so the match stays
|
|
62
|
+
* single-meaning per identifier.
|
|
63
|
+
*/
|
|
64
|
+
export declare function matchesUntilFilename(patchFilename: string, needle: string): boolean;
|
|
43
65
|
/**
|
|
44
66
|
* Enhanced patch application with continue mode.
|
|
45
67
|
* When continueOnFailure is false, rolls back all previously applied patches
|
|
@@ -58,3 +80,11 @@ export declare function applyPatchesWithContinue(patchesDir: string, engineDir:
|
|
|
58
80
|
* @returns Content after all patches applied, or null if file doesn't exist
|
|
59
81
|
*/
|
|
60
82
|
export declare function computePatchedContent(patchesDir: string, engineDir: string, filePath: string): Promise<string | null>;
|
|
83
|
+
/**
|
|
84
|
+
* Builds a batched variant of {@link computePatchedContent} that loads the
|
|
85
|
+
* manifest and discovers patch files ONCE for many lookups. The per-call
|
|
86
|
+
* function re-runs `loadPatchesManifest` + `discoverPatches` for every
|
|
87
|
+
* file — O(dirtyFiles × patches) redundant IO when classifying a broad
|
|
88
|
+
* engine edit session during `import`.
|
|
89
|
+
*/
|
|
90
|
+
export declare function createPatchedContentComputer(patchesDir: string, engineDir: string): Promise<(filePath: string) => Promise<string | null>>;
|