@hominis/fireforge 0.44.4 → 0.46.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 +120 -0
- package/dist/bin/fireforge.js +6 -0
- package/dist/build-info.json +4 -4
- package/dist/src/cli-usage-error.d.ts +46 -0
- package/dist/src/cli-usage-error.js +81 -0
- package/dist/src/cli.d.ts +5 -1
- package/dist/src/cli.js +20 -21
- package/dist/src/commands/bootstrap.js +1 -2
- package/dist/src/commands/build.js +15 -6
- package/dist/src/commands/destructive-decision.d.ts +19 -0
- package/dist/src/commands/destructive-decision.js +31 -0
- package/dist/src/commands/doctor-check-core.d.ts +0 -6
- package/dist/src/commands/doctor-check-core.js +0 -8
- package/dist/src/commands/doctor-orphaned-harness.d.ts +0 -6
- package/dist/src/commands/doctor-orphaned-harness.js +4 -12
- package/dist/src/commands/doctor-post-rebase-audit.js +1 -1
- package/dist/src/commands/doctor-source-pin.d.ts +29 -0
- package/dist/src/commands/doctor-source-pin.js +81 -0
- package/dist/src/commands/doctor.d.ts +2 -0
- package/dist/src/commands/doctor.js +9 -8
- package/dist/src/commands/download.js +35 -24
- package/dist/src/commands/export-all.js +21 -4
- package/dist/src/commands/export-flow.js +15 -60
- package/dist/src/commands/export-placement-conflicts.js +3 -3
- package/dist/src/commands/export-placement-gate.js +2 -8
- package/dist/src/commands/export-placement-policy.js +3 -5
- package/dist/src/commands/export-shared.d.ts +42 -23
- package/dist/src/commands/export-shared.js +4 -19
- package/dist/src/commands/export.js +10 -1
- package/dist/src/commands/furnace/authoring-config.d.ts +13 -0
- package/dist/src/commands/furnace/authoring-config.js +19 -0
- package/dist/src/commands/furnace/chrome-doc-remove.js +1 -1
- package/dist/src/commands/furnace/create-browser-test.js +3 -8
- package/dist/src/commands/furnace/create-dry-run.js +2 -5
- package/dist/src/commands/furnace/create-templates.d.ts +17 -1
- package/dist/src/commands/furnace/create-templates.js +2 -1
- package/dist/src/commands/furnace/create.js +25 -19
- package/dist/src/commands/furnace/deploy.js +11 -2
- package/dist/src/commands/furnace/init.js +2 -1
- package/dist/src/commands/furnace/override.js +28 -19
- package/dist/src/commands/furnace/refresh.js +9 -1
- package/dist/src/commands/furnace/remove-mutations.d.ts +6 -7
- package/dist/src/commands/furnace/remove-mutations.js +0 -1
- package/dist/src/commands/furnace/remove.js +2 -6
- package/dist/src/commands/furnace/rename-browser-test.js +1 -7
- package/dist/src/commands/furnace/rename.js +29 -29
- package/dist/src/commands/furnace/status.js +12 -9
- package/dist/src/commands/furnace/test-file-name.d.ts +28 -0
- package/dist/src/commands/furnace/test-file-name.js +39 -0
- package/dist/src/commands/furnace/validation-output.d.ts +21 -9
- package/dist/src/commands/furnace/validation-output.js +3 -8
- package/dist/src/commands/import.js +2 -9
- package/dist/src/commands/lint-per-patch.d.ts +2 -2
- package/dist/src/commands/lint-per-patch.js +19 -4
- package/dist/src/commands/lint-per-run-checkjs.d.ts +2 -2
- package/dist/src/commands/lint.d.ts +2 -1
- package/dist/src/commands/lint.js +1 -1
- package/dist/src/commands/manifest.js +1 -1
- package/dist/src/commands/package.js +2 -4
- package/dist/src/commands/patch/compact.js +14 -15
- package/dist/src/commands/patch/delete.js +15 -25
- package/dist/src/commands/patch/entry-projection.d.ts +13 -0
- package/dist/src/commands/patch/entry-projection.js +24 -0
- package/dist/src/commands/patch/lint-ignore.d.ts +0 -8
- package/dist/src/commands/patch/lint-ignore.js +19 -29
- package/dist/src/commands/patch/move-files-create.js +24 -10
- package/dist/src/commands/patch/move-files-into.js +38 -37
- package/dist/src/commands/patch/move-files.js +4 -13
- package/dist/src/commands/patch/rename.js +27 -41
- package/dist/src/commands/patch/reorder.js +25 -38
- package/dist/src/commands/patch/split-plan.d.ts +4 -5
- package/dist/src/commands/patch/split-plan.js +4 -11
- package/dist/src/commands/patch/split.js +45 -43
- package/dist/src/commands/patch/staged-dependency.js +16 -22
- package/dist/src/commands/patch/tier.js +15 -25
- package/dist/src/commands/patch-rename-map.d.ts +19 -0
- package/dist/src/commands/patch-rename-map.js +36 -0
- package/dist/src/commands/pending-resolution.d.ts +15 -0
- package/dist/src/commands/pending-resolution.js +25 -0
- package/dist/src/commands/re-export-adjacent.d.ts +32 -2
- package/dist/src/commands/re-export-adjacent.js +51 -10
- package/dist/src/commands/re-export-bulk-scan.js +6 -10
- package/dist/src/commands/re-export-drift.d.ts +8 -0
- package/dist/src/commands/re-export-drift.js +65 -4
- package/dist/src/commands/re-export-files.d.ts +2 -3
- package/dist/src/commands/re-export-files.js +38 -32
- package/dist/src/commands/re-export-lint.d.ts +3 -4
- package/dist/src/commands/re-export-lint.js +9 -2
- package/dist/src/commands/re-export-options.js +19 -5
- package/dist/src/commands/re-export-register.js +5 -1
- package/dist/src/commands/re-export-scan.js +8 -8
- package/dist/src/commands/re-export-single.d.ts +22 -3
- package/dist/src/commands/re-export-single.js +19 -11
- package/dist/src/commands/re-export.js +44 -27
- package/dist/src/commands/rebase/abort.js +3 -8
- package/dist/src/commands/rebase/continue.js +12 -12
- package/dist/src/commands/rebase/index.js +2 -2
- package/dist/src/commands/rebase/patch-loop.d.ts +2 -2
- package/dist/src/commands/rebase/patch-loop.js +2 -7
- package/dist/src/commands/resolve.js +18 -10
- package/dist/src/commands/run.d.ts +0 -13
- package/dist/src/commands/run.js +12 -25
- package/dist/src/commands/setup-support.d.ts +16 -4
- package/dist/src/commands/setup-support.js +16 -16
- package/dist/src/commands/setup.js +3 -9
- package/dist/src/commands/source.js +8 -1
- package/dist/src/commands/status-check.js +2 -2
- package/dist/src/commands/status-output.js +2 -2
- package/dist/src/commands/status.js +3 -4
- package/dist/src/commands/test-build-phase.d.ts +2 -2
- package/dist/src/commands/test-build-phase.js +33 -8
- package/dist/src/commands/test-diagnose.js +14 -5
- package/dist/src/commands/test-modes.d.ts +2 -2
- package/dist/src/commands/test-modes.js +0 -1
- package/dist/src/commands/test-register.js +6 -3
- package/dist/src/commands/test-run.js +29 -21
- package/dist/src/commands/test-stale-gate.js +8 -5
- package/dist/src/commands/test-verdict.d.ts +16 -2
- package/dist/src/commands/test-verdict.js +18 -3
- package/dist/src/commands/test.js +51 -7
- package/dist/src/commands/token-list.d.ts +26 -0
- package/dist/src/commands/token-list.js +153 -0
- package/dist/src/commands/token.js +17 -1
- package/dist/src/commands/tree.js +7 -2
- package/dist/src/commands/typecheck.js +9 -9
- package/dist/src/commands/wire.js +4 -3
- package/dist/src/core/branding.d.ts +0 -7
- package/dist/src/core/branding.js +3 -2
- package/dist/src/core/browser-wire.js +6 -3
- package/dist/src/core/build-audit-platform.d.ts +29 -0
- package/dist/src/core/build-audit-platform.js +140 -0
- package/dist/src/core/build-audit-registration.js +3 -2
- package/dist/src/core/build-audit-skip.d.ts +57 -0
- package/dist/src/core/build-audit-skip.js +112 -0
- package/dist/src/core/build-audit.d.ts +15 -2
- package/dist/src/core/build-audit.js +154 -12
- package/dist/src/core/build-baseline.d.ts +45 -28
- package/dist/src/core/build-baseline.js +6 -29
- package/dist/src/core/build-jar-escalation.d.ts +64 -0
- package/dist/src/core/build-jar-escalation.js +128 -0
- package/dist/src/core/build-overwrite-guard.js +1 -1
- package/dist/src/core/build-prepare.d.ts +5 -2
- package/dist/src/core/build-prepare.js +34 -6
- package/dist/src/core/config-paths.d.ts +2 -6
- package/dist/src/core/config-paths.js +4 -2
- package/dist/src/core/config-state.d.ts +0 -6
- package/dist/src/core/config-state.js +1 -19
- package/dist/src/core/config-validate-build-audit.d.ts +8 -0
- package/dist/src/core/config-validate-build-audit.js +80 -0
- package/dist/src/core/config-validate-file-size.d.ts +12 -0
- package/dist/src/core/config-validate-file-size.js +65 -0
- package/dist/src/core/config-validate-patch-policy.d.ts +2 -2
- package/dist/src/core/config-validate-patch-policy.js +3 -3
- package/dist/src/core/config-validate-test-toolchains.d.ts +2 -2
- package/dist/src/core/config-validate-test-toolchains.js +4 -5
- package/dist/src/core/config-validate.js +20 -3
- package/dist/src/core/config.d.ts +2 -2
- package/dist/src/core/config.js +11 -15
- package/dist/src/core/coverage-extend.js +4 -4
- package/dist/src/core/discard-baseline.js +7 -7
- package/dist/src/core/engine-precondition.d.ts +1 -9
- package/dist/src/core/engine-precondition.js +3 -8
- package/dist/src/core/engine-session-lock.js +13 -17
- package/dist/src/core/export-moved-code-shape.js +2 -2
- package/dist/src/core/file-lock-owner.d.ts +103 -0
- package/dist/src/core/file-lock-owner.js +259 -0
- package/dist/src/core/file-lock.d.ts +2 -7
- package/dist/src/core/file-lock.js +40 -91
- package/dist/src/core/firefox-cache.d.ts +13 -1
- package/dist/src/core/firefox-cache.js +59 -34
- package/dist/src/core/firefox-download.d.ts +15 -0
- package/dist/src/core/firefox-download.js +37 -21
- package/dist/src/core/firefox-extract.d.ts +6 -5
- package/dist/src/core/firefox-extract.js +36 -18
- package/dist/src/core/firefox.d.ts +30 -32
- package/dist/src/core/firefox.js +5 -36
- package/dist/src/core/furnace-apply-dry-run.d.ts +1 -6
- package/dist/src/core/furnace-apply-dry-run.js +1 -7
- package/dist/src/core/furnace-apply-helpers.d.ts +0 -11
- package/dist/src/core/furnace-apply-helpers.js +8 -19
- package/dist/src/core/furnace-apply-overwrite-warn.js +4 -2
- package/dist/src/core/furnace-apply.d.ts +1 -1
- package/dist/src/core/furnace-apply.js +30 -10
- package/dist/src/core/furnace-config.d.ts +1 -1
- package/dist/src/core/furnace-config.js +23 -36
- package/dist/src/core/furnace-constants.js +3 -2
- package/dist/src/core/furnace-dir-entry.d.ts +20 -0
- package/dist/src/core/furnace-dir-entry.js +21 -0
- package/dist/src/core/furnace-operation.d.ts +2 -0
- package/dist/src/core/furnace-operation.js +3 -1
- package/dist/src/core/furnace-refresh.d.ts +18 -6
- package/dist/src/core/furnace-refresh.js +3 -6
- package/dist/src/core/furnace-registration-ast.d.ts +0 -1
- package/dist/src/core/furnace-registration-ast.js +0 -3
- package/dist/src/core/furnace-registration.d.ts +2 -1
- package/dist/src/core/furnace-registration.js +22 -29
- package/dist/src/core/furnace-scanner.js +3 -6
- package/dist/src/core/furnace-state-persist.d.ts +2 -2
- package/dist/src/core/furnace-validate-registration.js +5 -7
- package/dist/src/core/git-base.d.ts +3 -1
- package/dist/src/core/git-base.js +25 -3
- package/dist/src/core/git-diff-new-file.d.ts +40 -0
- package/dist/src/core/git-diff-new-file.js +119 -0
- package/dist/src/core/git-diff.d.ts +9 -9
- package/dist/src/core/git-diff.js +88 -157
- package/dist/src/core/git-file-ops.d.ts +0 -7
- package/dist/src/core/git-file-ops.js +4 -28
- package/dist/src/core/git-readonly-index.d.ts +0 -2
- package/dist/src/core/git-readonly-index.js +0 -4
- package/dist/src/core/git-status.d.ts +7 -0
- package/dist/src/core/git-status.js +21 -9
- package/dist/src/core/git.d.ts +0 -6
- package/dist/src/core/git.js +14 -43
- package/dist/src/core/harness-orphans.d.ts +73 -0
- package/dist/src/core/harness-orphans.js +187 -0
- package/dist/src/core/history-log.d.ts +19 -0
- package/dist/src/core/history-log.js +29 -0
- package/dist/src/core/mach-error-hints.d.ts +0 -5
- package/dist/src/core/mach-error-hints.js +1 -1
- package/dist/src/core/mach-known-noise-filter.d.ts +25 -3
- package/dist/src/core/mach-known-noise-filter.js +31 -10
- package/dist/src/core/mach-mozconfig.js +2 -1
- package/dist/src/core/mach-python.d.ts +2 -0
- package/dist/src/core/mach-python.js +12 -7
- package/dist/src/core/mach-resource-shim.d.ts +13 -1
- package/dist/src/core/mach-resource-shim.js +112 -9
- package/dist/src/core/mach.d.ts +46 -53
- package/dist/src/core/mach.js +31 -83
- package/dist/src/core/marionette-port.d.ts +0 -17
- package/dist/src/core/marionette-port.js +12 -58
- package/dist/src/core/marionette-preflight.js +9 -27
- package/dist/src/core/mochitest-server-port.js +3 -3
- package/dist/src/core/moz-manifest-rules.js +37 -33
- package/dist/src/core/patch-apply-fuzz.js +4 -4
- package/dist/src/core/patch-apply.d.ts +4 -5
- package/dist/src/core/patch-apply.js +55 -32
- package/dist/src/core/patch-export-coverage.d.ts +0 -9
- package/dist/src/core/patch-export-coverage.js +1 -30
- package/dist/src/core/patch-export-update.d.ts +18 -1
- package/dist/src/core/patch-export-update.js +4 -3
- package/dist/src/core/patch-export.d.ts +11 -21
- package/dist/src/core/patch-export.js +44 -66
- package/dist/src/core/patch-files.d.ts +0 -4
- package/dist/src/core/patch-files.js +0 -11
- package/dist/src/core/patch-lint-binary.d.ts +6 -12
- package/dist/src/core/patch-lint-cache.d.ts +17 -1
- package/dist/src/core/patch-lint-cache.js +3 -6
- package/dist/src/core/patch-lint-checkjs.d.ts +33 -16
- package/dist/src/core/patch-lint-checkjs.js +43 -25
- package/dist/src/core/patch-lint-creators.d.ts +2 -6
- package/dist/src/core/patch-lint-cross.d.ts +16 -8
- package/dist/src/core/patch-lint-cross.js +49 -92
- package/dist/src/core/patch-lint-css.js +10 -6
- package/dist/src/core/patch-lint-file-size.d.ts +52 -0
- package/dist/src/core/patch-lint-file-size.js +53 -0
- package/dist/src/core/patch-lint-forward-registration.d.ts +58 -0
- package/dist/src/core/patch-lint-forward-registration.js +210 -0
- package/dist/src/core/patch-lint-jsdoc.js +36 -36
- package/dist/src/core/patch-lint-module-registration.d.ts +2 -8
- package/dist/src/core/patch-lint-prettier.d.ts +36 -0
- package/dist/src/core/patch-lint-prettier.js +126 -0
- package/dist/src/core/patch-lint-queue-types.d.ts +60 -0
- package/dist/src/core/patch-lint-queue-types.js +12 -0
- package/dist/src/core/patch-lint-staged-registration.d.ts +61 -0
- package/dist/src/core/patch-lint-staged-registration.js +90 -0
- package/dist/src/core/patch-lint-unmanaged-companion.d.ts +106 -0
- package/dist/src/core/patch-lint-unmanaged-companion.js +214 -0
- package/dist/src/core/patch-lint.d.ts +14 -9
- package/dist/src/core/patch-lint.js +27 -27
- package/dist/src/core/patch-lock.js +3 -3
- package/dist/src/core/patch-manifest-io.js +8 -7
- package/dist/src/core/patch-parse.d.ts +19 -8
- package/dist/src/core/patch-parse.js +25 -0
- package/dist/src/core/patch-policy.js +15 -27
- package/dist/src/core/patch-source-metadata.d.ts +1 -3
- package/dist/src/core/patch-source-metadata.js +0 -4
- package/dist/src/core/patch-transform.d.ts +21 -8
- package/dist/src/core/patch-transform.js +38 -10
- package/dist/src/core/process-cpu-time.js +1 -1
- package/dist/src/core/rebase-session.d.ts +2 -8
- package/dist/src/core/rebase-session.js +5 -17
- package/dist/src/core/register-browser-content.js +3 -2
- package/dist/src/core/register-browser-test.js +2 -1
- package/dist/src/core/register-module.js +2 -1
- package/dist/src/core/register-scaffold.js +5 -4
- package/dist/src/core/register-shared-css.js +2 -1
- package/dist/src/core/run-log-redact.d.ts +38 -0
- package/dist/src/core/run-log-redact.js +69 -0
- package/dist/src/core/run-log.d.ts +13 -2
- package/dist/src/core/run-log.js +67 -18
- package/dist/src/core/signal-critical.js +2 -4
- package/dist/src/core/smoke-patterns.d.ts +0 -1
- package/dist/src/core/smoke-patterns.js +18 -11
- package/dist/src/core/state-file.d.ts +12 -1
- package/dist/src/core/state-file.js +14 -1
- package/dist/src/core/status-classify.js +2 -1
- package/dist/src/core/test-changed-prefs.js +26 -11
- package/dist/src/core/test-harness-crash.js +61 -48
- package/dist/src/core/test-harness-verdict-notes.d.ts +55 -0
- package/dist/src/core/test-harness-verdict-notes.js +48 -0
- package/dist/src/core/test-stale-check.d.ts +23 -8
- package/dist/src/core/test-stale-check.js +77 -4
- package/dist/src/core/test-stall-triage.js +37 -19
- package/dist/src/core/test-xpcshell-retry.d.ts +26 -7
- package/dist/src/core/test-xpcshell-retry.js +14 -11
- package/dist/src/core/token-category.d.ts +33 -0
- package/dist/src/core/token-category.js +56 -48
- package/dist/src/core/token-dark-mode.js +2 -14
- package/dist/src/core/token-inventory.d.ts +56 -0
- package/dist/src/core/token-inventory.js +109 -0
- package/dist/src/core/token-manager.d.ts +1 -1
- package/dist/src/core/token-manager.js +1 -1
- package/dist/src/core/token-variant.js +21 -22
- package/dist/src/core/toolchain-preflight.js +1 -1
- package/dist/src/core/tree-cow.d.ts +0 -2
- package/dist/src/core/tree-cow.js +2 -2
- package/dist/src/core/tree-store.js +4 -5
- package/dist/src/core/typecheck.js +2 -2
- package/dist/src/core/wire-dom-fragment.js +6 -17
- package/dist/src/core/wire-utils.js +5 -17
- package/dist/src/core/xpcshell-appdir.js +2 -4
- package/dist/src/errors/base.d.ts +53 -1
- package/dist/src/errors/base.js +79 -11
- package/dist/src/errors/build.d.ts +28 -0
- package/dist/src/errors/build.js +51 -10
- package/dist/src/errors/config.d.ts +0 -14
- package/dist/src/errors/config.js +13 -34
- package/dist/src/errors/download.d.ts +13 -0
- package/dist/src/errors/download.js +53 -24
- package/dist/src/errors/furnace.js +8 -9
- package/dist/src/errors/git.d.ts +8 -8
- package/dist/src/errors/git.js +20 -30
- package/dist/src/errors/patch.js +8 -10
- package/dist/src/errors/rebase.js +7 -7
- package/dist/src/types/commands/options.d.ts +96 -123
- package/dist/src/types/commands/patches.d.ts +17 -6
- package/dist/src/types/config.d.ts +75 -2
- package/dist/src/types/index.d.ts +0 -1
- package/dist/src/utils/assert.d.ts +3 -15
- package/dist/src/utils/assert.js +1 -18
- package/dist/src/utils/build-info.d.ts +1 -1
- package/dist/src/utils/child-env.d.ts +2 -1
- package/dist/src/utils/elapsed.js +10 -1
- package/dist/src/utils/fs.d.ts +4 -2
- package/dist/src/utils/fs.js +11 -14
- package/dist/src/utils/hash.d.ts +12 -0
- package/dist/src/utils/hash.js +16 -0
- package/dist/src/utils/line-dispatch.d.ts +15 -0
- package/dist/src/utils/line-dispatch.js +48 -0
- package/dist/src/utils/options.d.ts +1 -7
- package/dist/src/utils/options.js +6 -7
- package/dist/src/utils/package-root.js +3 -12
- package/dist/src/utils/platform.d.ts +0 -4
- package/dist/src/utils/process-group.d.ts +2 -1
- package/dist/src/utils/process-group.js +20 -19
- package/dist/src/utils/process.d.ts +40 -26
- package/dist/src/utils/process.js +115 -166
- package/dist/src/utils/ps-duration.js +3 -3
- package/dist/src/utils/sleep.d.ts +26 -0
- package/dist/src/utils/sleep.js +31 -0
- package/dist/src/utils/validation.d.ts +2 -38
- package/dist/src/utils/validation.js +7 -16
- package/docs/configuration.md +136 -1
- package/docs/exit-codes.md +34 -20
- package/docs/lifecycle-invariants.md +18 -12
- package/docs/machine-output.md +31 -8
- package/docs/patch-workflow.md +2 -1
- package/docs/run-logs.md +44 -4
- package/docs/testing.md +70 -11
- package/package.json +8 -7
- package/dist/src/commands/rebase.d.ts +0 -4
- package/dist/src/commands/rebase.js +0 -5
- package/dist/src/core/patch-artifact-normalize.d.ts +0 -21
- package/dist/src/core/patch-artifact-normalize.js +0 -24
- package/dist/src/core/patch-lint-reexports.d.ts +0 -7
- package/dist/src/core/patch-lint-reexports.js +0 -8
- package/dist/src/core/wire-targets.d.ts +0 -7
- package/dist/src/core/wire-targets.js +0 -8
|
@@ -1,38 +1,15 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { basename, join } from 'node:path';
|
|
2
|
+
import { stat } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
5
4
|
import { GitError } from '../errors/git.js';
|
|
6
5
|
import { toError } from '../utils/errors.js';
|
|
7
|
-
import { pathExists
|
|
6
|
+
import { pathExists } from '../utils/fs.js';
|
|
8
7
|
import { verbose } from '../utils/logger.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { fileExistsInHead, hashObjectBatch,
|
|
12
|
-
import { mintDisposableGitIndex
|
|
8
|
+
import { chunkPathspecs, git } from './git-base.js';
|
|
9
|
+
import { readNewFileContent, withForcedBinaryAttribute } from './git-diff-new-file.js';
|
|
10
|
+
import { fileExistsInHead, hashObjectBatch, listTrackedInHead } from './git-file-ops.js';
|
|
11
|
+
import { mintDisposableGitIndex } from './git-readonly-index.js';
|
|
13
12
|
import { getUntrackedFiles, getUntrackedFilesInDir } from './git-status.js';
|
|
14
|
-
async function execGitWithAllowedExitCodes(repoDir, args, allowedExitCodes = [0], envOverride) {
|
|
15
|
-
// Honour a read-only command's private-index scope: the `--intent-to-add`
|
|
16
|
-
// / `update-index` staging dance below is a genuine
|
|
17
|
-
// index write, and inside a read-only command it must land on the
|
|
18
|
-
// private index, not the primary checkout's.
|
|
19
|
-
//
|
|
20
|
-
// `envOverride` wins over that scope: a caller that minted an index for
|
|
21
|
-
// one specific invocation has said something more specific than the
|
|
22
|
-
// ambient scope did.
|
|
23
|
-
const readOnlyIndexEnv = readOnlyGitIndexEnv(repoDir);
|
|
24
|
-
const env = readOnlyIndexEnv !== undefined || envOverride !== undefined
|
|
25
|
-
? { ...readOnlyIndexEnv, ...envOverride }
|
|
26
|
-
: undefined;
|
|
27
|
-
const result = await exec('git', args, {
|
|
28
|
-
cwd: repoDir,
|
|
29
|
-
...(env !== undefined ? { env } : {}),
|
|
30
|
-
});
|
|
31
|
-
if (allowedExitCodes.includes(result.exitCode)) {
|
|
32
|
-
return result;
|
|
33
|
-
}
|
|
34
|
-
throw new GitError(result.stderr.trim() || 'Git command failed', args.join(' '));
|
|
35
|
-
}
|
|
36
13
|
/**
|
|
37
14
|
* Gets the diff for a specific file.
|
|
38
15
|
*
|
|
@@ -46,7 +23,6 @@ async function execGitWithAllowedExitCodes(repoDir, args, allowedExitCodes = [0]
|
|
|
46
23
|
* @returns Diff content
|
|
47
24
|
*/
|
|
48
25
|
export async function getFileDiff(repoDir, filePath) {
|
|
49
|
-
await ensureGit();
|
|
50
26
|
return git(['diff', '--binary', 'HEAD', '--', filePath], repoDir);
|
|
51
27
|
}
|
|
52
28
|
/**
|
|
@@ -113,22 +89,36 @@ function buildNewFileDiffBody(filePath, content, blobHash) {
|
|
|
113
89
|
return diffLines.join('\n') + '\n';
|
|
114
90
|
}
|
|
115
91
|
/**
|
|
116
|
-
*
|
|
92
|
+
* Defensive check shared by the new-file readers: a directory here means a
|
|
93
|
+
* caller bypassed the expansion layers and handed the leaf reader a path it
|
|
94
|
+
* cannot read. Surfaces an actionable message naming the offending path
|
|
95
|
+
* rather than the raw `EISDIR` the read would throw. This is a recurring bug
|
|
96
|
+
* class, hence the guard at every leaf.
|
|
97
|
+
* @param repoDir - Repository directory
|
|
98
|
+
* @param filePath - Path to the file (relative to repo)
|
|
99
|
+
*/
|
|
100
|
+
async function rejectDirectory(repoDir, filePath) {
|
|
101
|
+
const fileStat = await stat(join(repoDir, filePath));
|
|
102
|
+
if (fileStat.isDirectory()) {
|
|
103
|
+
throw new GitError(`expected a file but found a directory at '${filePath}' — caller must expand directory entries before diffing`, `hash-object ${filePath}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Generates a unified diff for a new (untracked) file. A file that is binary
|
|
108
|
+
* or not valid UTF-8 (see {@link readNewFileContent}) is routed through
|
|
109
|
+
* {@link generateBinaryFilePatch} instead, because a text body cannot carry
|
|
110
|
+
* its bytes faithfully.
|
|
117
111
|
* @param repoDir - Repository directory
|
|
118
112
|
* @param filePath - Path to the file (relative to repo)
|
|
119
113
|
* @returns Diff content in unified diff format
|
|
120
114
|
*/
|
|
121
115
|
export async function generateNewFileDiff(repoDir, filePath) {
|
|
122
116
|
const fullPath = join(repoDir, filePath);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const fileStat = await stat(fullPath);
|
|
128
|
-
if (fileStat.isDirectory()) {
|
|
129
|
-
throw new GitError(`expected a file but found a directory at '${filePath}' — caller must expand directory entries before diffing`, `hash-object ${filePath}`);
|
|
117
|
+
await rejectDirectory(repoDir, filePath);
|
|
118
|
+
const file = await readNewFileContent(repoDir, filePath);
|
|
119
|
+
if (file.binary) {
|
|
120
|
+
return generateBinaryFilePatch(repoDir, filePath);
|
|
130
121
|
}
|
|
131
|
-
const content = await readText(fullPath);
|
|
132
122
|
// Compute the abbreviated git blob hash for the index line
|
|
133
123
|
let blobHash = '0000000000';
|
|
134
124
|
try {
|
|
@@ -138,7 +128,7 @@ export async function generateNewFileDiff(repoDir, filePath) {
|
|
|
138
128
|
catch (error) {
|
|
139
129
|
verbose(`git hash-object failed for ${filePath}; falling back to zero blob hash: ${toError(error).message}`);
|
|
140
130
|
}
|
|
141
|
-
return buildNewFileDiffBody(filePath, content, blobHash);
|
|
131
|
+
return buildNewFileDiffBody(filePath, file.content, blobHash);
|
|
142
132
|
}
|
|
143
133
|
/**
|
|
144
134
|
* Generates a patch for a file.
|
|
@@ -150,7 +140,6 @@ export async function generateNewFileDiff(repoDir, filePath) {
|
|
|
150
140
|
* @returns Diff content in unified diff format
|
|
151
141
|
*/
|
|
152
142
|
export async function generateFullFilePatch(repoDir, filePath) {
|
|
153
|
-
await ensureGit();
|
|
154
143
|
// If file exists in HEAD, use standard git diff HEAD -- <file>
|
|
155
144
|
// This generates a contextual diff that is safer for rebasing
|
|
156
145
|
if (await fileExistsInHead(repoDir, filePath)) {
|
|
@@ -159,56 +148,6 @@ export async function generateFullFilePatch(repoDir, filePath) {
|
|
|
159
148
|
// If file is new/untracked, use the full-file "new file" format
|
|
160
149
|
return generateNewFileDiff(repoDir, filePath);
|
|
161
150
|
}
|
|
162
|
-
/**
|
|
163
|
-
* Generates a unified diff between base content and current file content.
|
|
164
|
-
* @param repoDir - Repository directory
|
|
165
|
-
* @param filePath - Path to the file (relative to repo)
|
|
166
|
-
* @param baseContent - The base content to diff against
|
|
167
|
-
* @returns Unified diff in git format
|
|
168
|
-
*/
|
|
169
|
-
export async function generateModificationDiff(repoDir, filePath, baseContent) {
|
|
170
|
-
const fullPath = join(repoDir, filePath);
|
|
171
|
-
const currentContent = await readText(fullPath);
|
|
172
|
-
// If contents are identical, return empty diff
|
|
173
|
-
if (baseContent === currentContent) {
|
|
174
|
-
return '';
|
|
175
|
-
}
|
|
176
|
-
const tempDir = await mkdtemp(join(tmpdir(), 'fireforge-diff-'));
|
|
177
|
-
const tempFile = join(tempDir, basename(filePath));
|
|
178
|
-
try {
|
|
179
|
-
await writeFile(tempFile, baseContent);
|
|
180
|
-
// git diff --no-index exits code 1 when files differ — that's normal
|
|
181
|
-
const result = await execGitWithAllowedExitCodes(repoDir, ['diff', '--no-index', '--', tempFile, fullPath], [0, 1]);
|
|
182
|
-
const output = result.stdout;
|
|
183
|
-
if (!output) {
|
|
184
|
-
return '';
|
|
185
|
-
}
|
|
186
|
-
// Post-process: fix paths in the diff header only (before the first @@ hunk)
|
|
187
|
-
const lines = output.split('\n');
|
|
188
|
-
let pastHeader = false;
|
|
189
|
-
const fixedLines = lines.map((line) => {
|
|
190
|
-
if (!pastHeader && line.startsWith('@@')) {
|
|
191
|
-
pastHeader = true;
|
|
192
|
-
}
|
|
193
|
-
if (!pastHeader) {
|
|
194
|
-
if (line.startsWith('diff --git')) {
|
|
195
|
-
return `diff --git a/${filePath} b/${filePath}`;
|
|
196
|
-
}
|
|
197
|
-
if (line.startsWith('--- ')) {
|
|
198
|
-
return `--- a/${filePath}`;
|
|
199
|
-
}
|
|
200
|
-
if (line.startsWith('+++ ')) {
|
|
201
|
-
return `+++ b/${filePath}`;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return line;
|
|
205
|
-
});
|
|
206
|
-
return fixedLines.join('\n');
|
|
207
|
-
}
|
|
208
|
-
finally {
|
|
209
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
151
|
/**
|
|
213
152
|
* Gets the diff for all modified files, including untracked (new) files.
|
|
214
153
|
*
|
|
@@ -219,16 +158,16 @@ export async function generateModificationDiff(repoDir, filePath, baseContent) {
|
|
|
219
158
|
* @returns Diff content
|
|
220
159
|
*/
|
|
221
160
|
export async function getAllDiff(repoDir) {
|
|
222
|
-
await ensureGit();
|
|
223
161
|
// Get diff for tracked files
|
|
224
162
|
const trackedDiff = await git(['diff', '--binary', 'HEAD'], repoDir);
|
|
225
163
|
// Get untracked files (properly expanded, not directories)
|
|
226
164
|
const untrackedFiles = await getUntrackedFiles(repoDir);
|
|
227
|
-
// Classify first. `
|
|
228
|
-
//
|
|
165
|
+
// Classify first. `readNewFileContent` reads the file directly and spawns
|
|
166
|
+
// nothing, so the only per-file process in this loop would be the
|
|
229
167
|
// `git hash-object` inside `generateNewFileDiff` — one spawn per untracked
|
|
230
168
|
// TEXT file, uncapped, against a Firefox tree where "untracked" can mean a
|
|
231
|
-
// very large number. Hence classify → one batched hash → render.
|
|
169
|
+
// very large number. Hence classify → one batched hash → render. The
|
|
170
|
+
// classification carries each text file's content so it is read once.
|
|
232
171
|
//
|
|
233
172
|
// Everything else here is deliberately untouched: `git diff HEAD` (with
|
|
234
173
|
// renames, unlike the `--no-renames` scoped sibling), the `ls-files`
|
|
@@ -236,40 +175,33 @@ export async function getAllDiff(repoDir) {
|
|
|
236
175
|
// sentinel are all observable. `getAllDiff`'s output is SHA-256'd into
|
|
237
176
|
// every tree fingerprint (tree-store.ts) and written verbatim into `.patch`
|
|
238
177
|
// files (export-all.ts), so its bytes are a contract.
|
|
239
|
-
const
|
|
240
|
-
const textFiles = [];
|
|
178
|
+
const classified = new Map();
|
|
241
179
|
for (const file of untrackedFiles) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
textFiles.push(file);
|
|
247
|
-
}
|
|
180
|
+
await rejectDirectory(repoDir, file);
|
|
181
|
+
classified.set(file, await readNewFileContent(repoDir, file));
|
|
248
182
|
}
|
|
249
|
-
const textHashes = await hashObjectBatch(repoDir,
|
|
183
|
+
const textHashes = await hashObjectBatch(repoDir, untrackedFiles
|
|
184
|
+
.filter((file) => classified.get(file)?.binary === false)
|
|
185
|
+
.map((file) => join(repoDir, file)));
|
|
250
186
|
// Binary patches stage into the index and must stay serial; text patches
|
|
251
187
|
// are pure formatting over the already-batched hashes.
|
|
252
188
|
const binaryDiffs = new Map();
|
|
253
|
-
for (const file of
|
|
254
|
-
|
|
189
|
+
for (const file of untrackedFiles) {
|
|
190
|
+
if (classified.get(file)?.binary === true) {
|
|
191
|
+
binaryDiffs.set(file, await generateBinaryFilePatch(repoDir, file));
|
|
192
|
+
}
|
|
255
193
|
}
|
|
256
194
|
const untrackedDiffs = [];
|
|
257
195
|
for (const file of untrackedFiles) {
|
|
258
|
-
const
|
|
259
|
-
if (
|
|
260
|
-
|
|
196
|
+
const entry = classified.get(file);
|
|
197
|
+
if (entry === undefined || entry.binary) {
|
|
198
|
+
const binary = binaryDiffs.get(file);
|
|
199
|
+
if (binary !== undefined)
|
|
200
|
+
untrackedDiffs.push(binary);
|
|
261
201
|
continue;
|
|
262
202
|
}
|
|
263
203
|
const fullPath = join(repoDir, file);
|
|
264
|
-
|
|
265
|
-
// directory here means a caller bypassed the expansion layers, and its
|
|
266
|
-
// comment records this as a recurring bug class.
|
|
267
|
-
const fileStat = await stat(fullPath);
|
|
268
|
-
if (fileStat.isDirectory()) {
|
|
269
|
-
throw new GitError(`expected a file but found a directory at '${file}' — caller must expand directory entries before diffing`, `hash-object ${file}`);
|
|
270
|
-
}
|
|
271
|
-
const content = await readText(fullPath);
|
|
272
|
-
untrackedDiffs.push(buildNewFileDiffBody(file, content, abbreviateBlobHash(textHashes.get(fullPath))));
|
|
204
|
+
untrackedDiffs.push(buildNewFileDiffBody(file, entry.content, abbreviateBlobHash(textHashes.get(fullPath))));
|
|
273
205
|
}
|
|
274
206
|
// Combine all diffs — each already ends with \n, so concatenate directly
|
|
275
207
|
// to avoid inserting blank lines between diff sections.
|
|
@@ -376,7 +308,6 @@ async function buildTrackedSections(repoDir, trackedFiles) {
|
|
|
376
308
|
* @returns Combined diff content
|
|
377
309
|
*/
|
|
378
310
|
export async function getDiffForFilesAgainstHead(repoDir, files) {
|
|
379
|
-
await ensureGit();
|
|
380
311
|
// Expand any directory entries (paths ending with `/`) into their
|
|
381
312
|
// individual untracked files before diffing. `git status --porcelain=v1`
|
|
382
313
|
// reports collapsed untracked directories as `?? dir/`, and every caller
|
|
@@ -435,25 +366,27 @@ export async function getDiffForFilesAgainstHead(repoDir, files) {
|
|
|
435
366
|
sectionByFile.set(file, innerDiff);
|
|
436
367
|
continue;
|
|
437
368
|
}
|
|
438
|
-
|
|
369
|
+
// Binary — by git's NUL heuristic or because the bytes are not valid
|
|
370
|
+
// UTF-8 — takes the byte-faithful binary-patch arm.
|
|
371
|
+
const newFile = await readNewFileContent(repoDir, file);
|
|
372
|
+
if (newFile.binary) {
|
|
439
373
|
const diff = await generateBinaryFilePatch(repoDir, file);
|
|
440
374
|
if (diff.trim())
|
|
441
375
|
sectionByFile.set(file, diff);
|
|
442
376
|
continue;
|
|
443
377
|
}
|
|
444
|
-
newTextFiles.push(file);
|
|
378
|
+
newTextFiles.push({ file, content: newFile.content });
|
|
445
379
|
}
|
|
446
380
|
// Batch 3: blob hashes for every new text file in one `git hash-object`.
|
|
447
381
|
// A miss (rare: a path became unreadable after the stat above) falls back to
|
|
448
382
|
// the zero hash with the same verbose log the per-file path emitted.
|
|
449
|
-
const blobHashes = await hashObjectBatch(repoDir, newTextFiles.map((file) => join(repoDir, file)));
|
|
450
|
-
for (const file of newTextFiles) {
|
|
451
|
-
const
|
|
452
|
-
const fullHash = blobHashes.get(fullPath);
|
|
383
|
+
const blobHashes = await hashObjectBatch(repoDir, newTextFiles.map(({ file }) => join(repoDir, file)));
|
|
384
|
+
for (const { file, content } of newTextFiles) {
|
|
385
|
+
const fullHash = blobHashes.get(join(repoDir, file));
|
|
453
386
|
if (fullHash === undefined) {
|
|
454
387
|
verbose(`git hash-object failed for ${file}; falling back to zero blob hash`);
|
|
455
388
|
}
|
|
456
|
-
const body = buildNewFileDiffBody(file,
|
|
389
|
+
const body = buildNewFileDiffBody(file, content, abbreviateBlobHash(fullHash));
|
|
457
390
|
if (body.trim())
|
|
458
391
|
sectionByFile.set(file, body);
|
|
459
392
|
}
|
|
@@ -484,7 +417,6 @@ export async function getDiffForFilesAgainstHead(repoDir, files) {
|
|
|
484
417
|
* @returns Diff content for the staged files
|
|
485
418
|
*/
|
|
486
419
|
export async function getStagedDiffForFiles(repoDir, files) {
|
|
487
|
-
await ensureGit();
|
|
488
420
|
return git(['diff', '--binary', '--cached', 'HEAD', '--', ...files], repoDir);
|
|
489
421
|
}
|
|
490
422
|
/**
|
|
@@ -507,22 +439,31 @@ function runWithBinaryStagingLock(task) {
|
|
|
507
439
|
* Generates a GIT binary patch for a binary file.
|
|
508
440
|
* For tracked files, uses `git diff --binary HEAD`.
|
|
509
441
|
* For untracked files, temporarily stages with `--intent-to-add` to produce a diff.
|
|
442
|
+
*
|
|
443
|
+
* Every `git diff` here runs under {@link withForcedBinaryAttribute}: a
|
|
444
|
+
* caller sends a file down this arm because a text body cannot carry its
|
|
445
|
+
* bytes (NUL, or not valid UTF-8), and git's own NUL-only heuristic would
|
|
446
|
+
* otherwise still render the non-UTF-8 case as text.
|
|
510
447
|
* @param repoDir - Repository directory
|
|
511
448
|
* @param filePath - File path (relative to repo root)
|
|
512
449
|
* @returns The binary diff string, or empty string if no diff
|
|
513
450
|
*/
|
|
514
451
|
export async function generateBinaryFilePatch(repoDir, filePath) {
|
|
515
|
-
|
|
452
|
+
return withForcedBinaryAttribute(filePath, (gitConfigArgs) => generateBinaryFilePatchWithConfig(repoDir, filePath, gitConfigArgs));
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* The body of {@link generateBinaryFilePatch}, with the forced-binary config
|
|
456
|
+
* arguments already resolved.
|
|
457
|
+
* @param repoDir - Repository directory
|
|
458
|
+
* @param filePath - File path (relative to repo root)
|
|
459
|
+
* @param gitConfigArgs - `-c` arguments spliced ahead of every `diff`
|
|
460
|
+
* @returns The binary diff string, or empty string if no diff
|
|
461
|
+
*/
|
|
462
|
+
async function generateBinaryFilePatchWithConfig(repoDir, filePath, gitConfigArgs) {
|
|
516
463
|
// Try tracked file diff first (read-only — no index lock needed)
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
'HEAD',
|
|
521
|
-
'--',
|
|
522
|
-
filePath,
|
|
523
|
-
]);
|
|
524
|
-
if (result.stdout.trim())
|
|
525
|
-
return result.stdout;
|
|
464
|
+
const tracked = await git([...gitConfigArgs, 'diff', '--binary', 'HEAD', '--', filePath], repoDir);
|
|
465
|
+
if (tracked.trim())
|
|
466
|
+
return tracked;
|
|
526
467
|
// The diff for an untracked file has to come from an index entry, so one
|
|
527
468
|
// has to be written. Writing it to a PRIVATE index is what keeps that
|
|
528
469
|
// write invisible: the primary `.git/index` never moves, so a concurrent
|
|
@@ -539,9 +480,10 @@ export async function generateBinaryFilePatch(repoDir, filePath) {
|
|
|
539
480
|
const disposable = await mintDisposableGitIndex(repoDir);
|
|
540
481
|
if (disposable !== undefined) {
|
|
541
482
|
try {
|
|
542
|
-
await
|
|
543
|
-
|
|
544
|
-
|
|
483
|
+
await git(['add', '--intent-to-add', '--', filePath], repoDir, { env: disposable.env });
|
|
484
|
+
return await git([...gitConfigArgs, 'diff', '--binary', '--', filePath], repoDir, {
|
|
485
|
+
env: disposable.env,
|
|
486
|
+
});
|
|
545
487
|
}
|
|
546
488
|
finally {
|
|
547
489
|
await disposable.dispose();
|
|
@@ -562,16 +504,10 @@ export async function generateBinaryFilePatch(repoDir, filePath) {
|
|
|
562
504
|
// between the read-only diff above and this staging block (a concurrent
|
|
563
505
|
// writer, or a staged-add whose worktree file was deleted). Restoring
|
|
564
506
|
// the captured entry is exact regardless of how the entry got there.
|
|
565
|
-
const priorEntry =
|
|
507
|
+
const priorEntry = await git(['ls-files', '--stage', '--', filePath], repoDir);
|
|
566
508
|
try {
|
|
567
|
-
await
|
|
568
|
-
|
|
569
|
-
'diff',
|
|
570
|
-
'--binary',
|
|
571
|
-
'--',
|
|
572
|
-
filePath,
|
|
573
|
-
]);
|
|
574
|
-
return diffResult.stdout;
|
|
509
|
+
await git(['add', '--intent-to-add', '--', filePath], repoDir);
|
|
510
|
+
return await git([...gitConfigArgs, 'diff', '--binary', '--', filePath], repoDir);
|
|
575
511
|
}
|
|
576
512
|
finally {
|
|
577
513
|
await restoreIndexEntry(repoDir, filePath, priorEntry);
|
|
@@ -595,13 +531,8 @@ async function restoreIndexEntry(repoDir, filePath, priorEntry) {
|
|
|
595
531
|
const mode = entryMatch?.[1];
|
|
596
532
|
const oid = entryMatch?.[2];
|
|
597
533
|
if (mode === undefined || oid === undefined) {
|
|
598
|
-
await
|
|
534
|
+
await git(['reset', 'HEAD', '--', filePath], repoDir);
|
|
599
535
|
return;
|
|
600
536
|
}
|
|
601
|
-
await
|
|
602
|
-
'update-index',
|
|
603
|
-
'--add',
|
|
604
|
-
'--cacheinfo',
|
|
605
|
-
`${mode},${oid},${filePath}`,
|
|
606
|
-
]);
|
|
537
|
+
await git(['update-index', '--add', '--cacheinfo', `${mode},${oid},${filePath}`], repoDir);
|
|
607
538
|
}
|
|
@@ -77,13 +77,6 @@ export declare function hashObjectBatch(repoDir: string, fullPaths: string[]): P
|
|
|
77
77
|
* @returns File content or null if file doesn't exist at that ref
|
|
78
78
|
*/
|
|
79
79
|
export declare function getFileContentAtRef(repoDir: string, filePath: string, ref?: string): Promise<string | null>;
|
|
80
|
-
/**
|
|
81
|
-
* Gets the content of a file from HEAD commit.
|
|
82
|
-
* @param repoDir - Repository directory
|
|
83
|
-
* @param filePath - Path to the file (relative to repo)
|
|
84
|
-
* @returns File content or null if file doesn't exist in HEAD
|
|
85
|
-
*/
|
|
86
|
-
export declare function getFileContentFromHead(repoDir: string, filePath: string): Promise<string | null>;
|
|
87
80
|
/**
|
|
88
81
|
* Checks if a file is binary by looking for NUL bytes in the first 8KB.
|
|
89
82
|
* Uses the same heuristic as git.
|
|
@@ -11,27 +11,16 @@ import { chunkPathspecs, ensureGit, git } from './git-base.js';
|
|
|
11
11
|
* @param filePath - Path to the file (relative to repo)
|
|
12
12
|
*/
|
|
13
13
|
export async function restoreTrackedPath(repoDir, filePath) {
|
|
14
|
-
await ensureGit();
|
|
15
14
|
await git(['restore', '--source', 'HEAD', '--staged', '--worktree', '--', filePath], repoDir);
|
|
16
15
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Removes an untracked path from disk.
|
|
19
|
-
* @param repoDir - Repository directory
|
|
20
|
-
* @param filePath - Path to the file (relative to repo)
|
|
21
|
-
*/
|
|
22
|
-
async function removeUntrackedPath(repoDir, filePath) {
|
|
23
|
-
const fullPath = join(repoDir, filePath);
|
|
24
|
-
await removeFile(fullPath);
|
|
25
|
-
}
|
|
26
16
|
/**
|
|
27
17
|
* Removes a path that is present only in the index/worktree and not in HEAD.
|
|
28
18
|
* @param repoDir - Repository directory
|
|
29
19
|
* @param filePath - Path to remove
|
|
30
20
|
*/
|
|
31
21
|
async function removeAddedPath(repoDir, filePath) {
|
|
32
|
-
await ensureGit();
|
|
33
22
|
await git(['reset', 'HEAD', '--', filePath], repoDir);
|
|
34
|
-
await
|
|
23
|
+
await removeFile(join(repoDir, filePath));
|
|
35
24
|
}
|
|
36
25
|
/**
|
|
37
26
|
* Discards a status entry according to its git state.
|
|
@@ -40,7 +29,7 @@ async function removeAddedPath(repoDir, filePath) {
|
|
|
40
29
|
*/
|
|
41
30
|
export async function discardStatusEntry(repoDir, entry) {
|
|
42
31
|
if (entry.isUntracked) {
|
|
43
|
-
await
|
|
32
|
+
await removeFile(join(repoDir, entry.file));
|
|
44
33
|
return;
|
|
45
34
|
}
|
|
46
35
|
if (entry.isRenameOrCopy && entry.originalPath) {
|
|
@@ -65,7 +54,6 @@ export async function discardStatusEntry(repoDir, entry) {
|
|
|
65
54
|
* @param files - File paths to stage (relative to repo)
|
|
66
55
|
*/
|
|
67
56
|
export async function stageFiles(repoDir, files) {
|
|
68
|
-
await ensureGit();
|
|
69
57
|
await git(['add', '--', ...files], repoDir);
|
|
70
58
|
}
|
|
71
59
|
/**
|
|
@@ -74,7 +62,6 @@ export async function stageFiles(repoDir, files) {
|
|
|
74
62
|
* @param files - File paths to unstage (relative to repo)
|
|
75
63
|
*/
|
|
76
64
|
export async function unstageFiles(repoDir, files) {
|
|
77
|
-
await ensureGit();
|
|
78
65
|
await git(['reset', 'HEAD', '--', ...files], repoDir);
|
|
79
66
|
}
|
|
80
67
|
/**
|
|
@@ -84,7 +71,6 @@ export async function unstageFiles(repoDir, files) {
|
|
|
84
71
|
* @returns true if file exists in HEAD
|
|
85
72
|
*/
|
|
86
73
|
export async function fileExistsInHead(repoDir, filePath) {
|
|
87
|
-
await ensureGit();
|
|
88
74
|
return (await git(['ls-tree', 'HEAD', '--', filePath], repoDir)).trim().length > 0;
|
|
89
75
|
}
|
|
90
76
|
/**
|
|
@@ -107,7 +93,6 @@ export async function listTrackedInHead(repoDir, files) {
|
|
|
107
93
|
const tracked = new Set();
|
|
108
94
|
if (files.length === 0)
|
|
109
95
|
return tracked;
|
|
110
|
-
await ensureGit();
|
|
111
96
|
const wanted = new Set(files);
|
|
112
97
|
for (const chunk of chunkPathspecs(files)) {
|
|
113
98
|
const output = await git(['ls-tree', '-r', 'HEAD', '--name-only', '-z', '--', ...chunk], repoDir);
|
|
@@ -151,8 +136,8 @@ export async function hashObjectBatch(repoDir, fullPaths) {
|
|
|
151
136
|
const result = await exec('git', ['hash-object', '--', ...chunk], { cwd: repoDir });
|
|
152
137
|
const lines = result.stdout.split('\n').filter((line) => line.length > 0);
|
|
153
138
|
if (result.exitCode === 0 && lines.length === chunk.length) {
|
|
154
|
-
for (
|
|
155
|
-
hashes.set(
|
|
139
|
+
for (const [i, path] of chunk.entries()) {
|
|
140
|
+
hashes.set(path, lines[i]);
|
|
156
141
|
}
|
|
157
142
|
continue;
|
|
158
143
|
}
|
|
@@ -190,15 +175,6 @@ export async function getFileContentAtRef(repoDir, filePath, ref = 'HEAD') {
|
|
|
190
175
|
}
|
|
191
176
|
return result.stdout;
|
|
192
177
|
}
|
|
193
|
-
/**
|
|
194
|
-
* Gets the content of a file from HEAD commit.
|
|
195
|
-
* @param repoDir - Repository directory
|
|
196
|
-
* @param filePath - Path to the file (relative to repo)
|
|
197
|
-
* @returns File content or null if file doesn't exist in HEAD
|
|
198
|
-
*/
|
|
199
|
-
export async function getFileContentFromHead(repoDir, filePath) {
|
|
200
|
-
return getFileContentAtRef(repoDir, filePath, 'HEAD');
|
|
201
|
-
}
|
|
202
178
|
/**
|
|
203
179
|
* Checks if a file is binary by looking for NUL bytes in the first 8KB.
|
|
204
180
|
* Uses the same heuristic as git.
|
|
@@ -43,8 +43,6 @@
|
|
|
43
43
|
* @returns `{ GIT_INDEX_FILE }` when scoped, otherwise undefined
|
|
44
44
|
*/
|
|
45
45
|
export declare function readOnlyGitIndexEnv(cwd: string): Record<string, string> | undefined;
|
|
46
|
-
/** True while a private-index scope is installed (diagnostics and tests). */
|
|
47
|
-
export declare function hasReadOnlyGitIndexScope(): boolean;
|
|
48
46
|
/**
|
|
49
47
|
* Runs `operation` with FireForge's git plumbing pointed at a private
|
|
50
48
|
* index seeded from `repoDir`'s own, so the primary checkout's
|
|
@@ -64,10 +64,6 @@ export function readOnlyGitIndexEnv(cwd) {
|
|
|
64
64
|
return undefined;
|
|
65
65
|
return { GIT_INDEX_FILE: activeScope.indexFile };
|
|
66
66
|
}
|
|
67
|
-
/** True while a private-index scope is installed (diagnostics and tests). */
|
|
68
|
-
export function hasReadOnlyGitIndexScope() {
|
|
69
|
-
return activeScope !== undefined;
|
|
70
|
-
}
|
|
71
67
|
/**
|
|
72
68
|
* Resolves a repository's git directory. Returns undefined for anything
|
|
73
69
|
* that is not a readable git checkout — `engine/` is not always one
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { GitStatusEntry } from './git-base.js';
|
|
2
2
|
/**
|
|
3
3
|
* Parses NUL-delimited porcelain status output.
|
|
4
|
+
*
|
|
5
|
+
* A rename or copy occupies TWO NUL records (`XY new\0old\0`), and the `R`/`C`
|
|
6
|
+
* may sit in EITHER column: git >= 2.18 detects renames in the worktree too
|
|
7
|
+
* (`status.renames`), emitting ` R new\0old\0` for an unstaged rename and
|
|
8
|
+
* `RM`/`R ` for a staged one. Only consuming the second record for an index
|
|
9
|
+
* `R`/`C` turned the worktree form's `old` field into a bogus extra entry
|
|
10
|
+
* whose status was whatever its first two path characters happened to be.
|
|
4
11
|
* @param output - Raw git status output
|
|
5
12
|
* @returns Parsed entries
|
|
6
13
|
*/
|
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
import { warn } from '../utils/logger.js';
|
|
3
|
-
import {
|
|
3
|
+
import { git } from './git-base.js';
|
|
4
|
+
/**
|
|
5
|
+
* True when a porcelain status column carries a rename or copy, i.e. the
|
|
6
|
+
* record is followed by a second NUL-terminated field holding the original
|
|
7
|
+
* path.
|
|
8
|
+
* @param column - A single status column character
|
|
9
|
+
* @returns Whether the column is `R` or `C`
|
|
10
|
+
*/
|
|
11
|
+
function isRenameOrCopyColumn(column) {
|
|
12
|
+
return column === 'R' || column === 'C';
|
|
13
|
+
}
|
|
4
14
|
/**
|
|
5
15
|
* Parses NUL-delimited porcelain status output.
|
|
16
|
+
*
|
|
17
|
+
* A rename or copy occupies TWO NUL records (`XY new\0old\0`), and the `R`/`C`
|
|
18
|
+
* may sit in EITHER column: git >= 2.18 detects renames in the worktree too
|
|
19
|
+
* (`status.renames`), emitting ` R new\0old\0` for an unstaged rename and
|
|
20
|
+
* `RM`/`R ` for a staged one. Only consuming the second record for an index
|
|
21
|
+
* `R`/`C` turned the worktree form's `old` field into a bogus extra entry
|
|
22
|
+
* whose status was whatever its first two path characters happened to be.
|
|
6
23
|
* @param output - Raw git status output
|
|
7
24
|
* @returns Parsed entries
|
|
8
25
|
*/
|
|
@@ -18,7 +35,7 @@ export function parsePorcelainStatus(output) {
|
|
|
18
35
|
const worktreeStatus = record[1] ?? ' ';
|
|
19
36
|
const status = `${indexStatus}${worktreeStatus}`;
|
|
20
37
|
const pathField = record.slice(3);
|
|
21
|
-
const isRenameOrCopy = indexStatus
|
|
38
|
+
const isRenameOrCopy = isRenameOrCopyColumn(indexStatus) || isRenameOrCopyColumn(worktreeStatus);
|
|
22
39
|
const originalPath = isRenameOrCopy ? records[i + 1] : undefined;
|
|
23
40
|
if (isRenameOrCopy) {
|
|
24
41
|
i++;
|
|
@@ -42,7 +59,6 @@ export function parsePorcelainStatus(output) {
|
|
|
42
59
|
* @returns Parsed git status entries
|
|
43
60
|
*/
|
|
44
61
|
export async function getWorkingTreeStatus(repoDir) {
|
|
45
|
-
await ensureGit();
|
|
46
62
|
return parsePorcelainStatus(await git(['status', '--porcelain=v1', '-z'], repoDir));
|
|
47
63
|
}
|
|
48
64
|
/** Default per-directory cap for untracked-directory expansion. */
|
|
@@ -57,7 +73,7 @@ export function resolveMaxUntrackedFilesPerDir() {
|
|
|
57
73
|
return DEFAULT_MAX_UNTRACKED_FILES_PER_DIR;
|
|
58
74
|
const parsed = Number.parseInt(raw, 10);
|
|
59
75
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
60
|
-
warn(`Ignoring FIREFORGE_MAX_UNTRACKED_FILES="${raw}" — expected a positive integer. Falling back to ${
|
|
76
|
+
warn(`Ignoring FIREFORGE_MAX_UNTRACKED_FILES="${raw}" — expected a positive integer. Falling back to ${DEFAULT_MAX_UNTRACKED_FILES_PER_DIR}.`);
|
|
61
77
|
return DEFAULT_MAX_UNTRACKED_FILES_PER_DIR;
|
|
62
78
|
}
|
|
63
79
|
return parsed;
|
|
@@ -86,7 +102,7 @@ export async function expandUntrackedDirectoryEntries(repoDir, entries) {
|
|
|
86
102
|
}
|
|
87
103
|
const individualFiles = await getUntrackedFilesInDir(repoDir, entry.file);
|
|
88
104
|
if (individualFiles.length > maxPerDir) {
|
|
89
|
-
warn(`Untracked directory ${entry.file} contains ${
|
|
105
|
+
warn(`Untracked directory ${entry.file} contains ${individualFiles.length} files — only the first ${maxPerDir} are listed. Add a .gitignore entry or clean the directory.`);
|
|
90
106
|
}
|
|
91
107
|
for (const file of individualFiles.slice(0, maxPerDir)) {
|
|
92
108
|
expanded.push({
|
|
@@ -108,7 +124,6 @@ export async function expandUntrackedDirectoryEntries(repoDir, entries) {
|
|
|
108
124
|
* @returns List of untracked file paths
|
|
109
125
|
*/
|
|
110
126
|
export async function getUntrackedFiles(repoDir) {
|
|
111
|
-
await ensureGit();
|
|
112
127
|
// Use git ls-files to get all untracked files, which properly expands directories
|
|
113
128
|
const output = await git(['ls-files', '--others', '--exclude-standard'], repoDir);
|
|
114
129
|
return output.split('\n').filter((line) => line.trim().length > 0);
|
|
@@ -121,7 +136,6 @@ export async function getUntrackedFiles(repoDir) {
|
|
|
121
136
|
* @returns List of untracked file paths relative to repo root
|
|
122
137
|
*/
|
|
123
138
|
export async function getUntrackedFilesInDir(repoDir, dir) {
|
|
124
|
-
await ensureGit();
|
|
125
139
|
const output = await git(['ls-files', '--others', '--exclude-standard', '--', dir], repoDir);
|
|
126
140
|
return output.split('\n').filter((line) => line.trim().length > 0);
|
|
127
141
|
}
|
|
@@ -133,7 +147,6 @@ export async function getUntrackedFilesInDir(repoDir, dir) {
|
|
|
133
147
|
* @returns List of modified file paths relative to repo root
|
|
134
148
|
*/
|
|
135
149
|
export async function getModifiedFilesInDir(repoDir, dir) {
|
|
136
|
-
await ensureGit();
|
|
137
150
|
const output = await git(['diff', '--name-only', 'HEAD', '--', dir], repoDir);
|
|
138
151
|
return output.split('\n').filter((line) => line.trim().length > 0);
|
|
139
152
|
}
|
|
@@ -146,7 +159,6 @@ export async function getModifiedFilesInDir(repoDir, dir) {
|
|
|
146
159
|
export async function getDirtyFiles(repoDir, files) {
|
|
147
160
|
if (files.length === 0)
|
|
148
161
|
return [];
|
|
149
|
-
await ensureGit();
|
|
150
162
|
// Check both staged and unstaged changes for the given files
|
|
151
163
|
const trackedOutput = await git(['diff', '--name-only', 'HEAD', '--', ...files], repoDir);
|
|
152
164
|
const tracked = trackedOutput.split('\n').filter((line) => line.trim().length > 0);
|
package/dist/src/core/git.d.ts
CHANGED
|
@@ -103,12 +103,6 @@ export declare function getCurrentBranch(repoDir: string): Promise<string>;
|
|
|
103
103
|
* @param repoDir - Repository directory
|
|
104
104
|
*/
|
|
105
105
|
export declare function resetChanges(repoDir: string): Promise<void>;
|
|
106
|
-
/**
|
|
107
|
-
* Creates a commit with all current changes.
|
|
108
|
-
* @param repoDir - Repository directory
|
|
109
|
-
* @param message - Commit message
|
|
110
|
-
*/
|
|
111
|
-
export declare function commit(repoDir: string, message: string): Promise<void>;
|
|
112
106
|
/**
|
|
113
107
|
* Gets the status of files with their status codes.
|
|
114
108
|
* @param repoDir - Repository directory
|