@camerontaylor/paseo-server 0.8.0-fork.1
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/.env.example +31 -0
- package/README.md +111 -0
- package/dist/scripts/dev-runner.js +37 -0
- package/dist/scripts/mcp-stdio-socket-bridge-cli.mjs +62 -0
- package/dist/scripts/supervisor-entrypoint.js +160 -0
- package/dist/scripts/supervisor-log-config.js +31 -0
- package/dist/scripts/supervisor.js +313 -0
- package/dist/server/executable-resolution/executable-resolution.d.ts +10 -0
- package/dist/server/executable-resolution/executable-resolution.js +121 -0
- package/dist/server/executable-resolution/windows.d.ts +18 -0
- package/dist/server/executable-resolution/windows.js +65 -0
- package/dist/server/server/agent/activity-curator.d.ts +40 -0
- package/dist/server/server/agent/activity-curator.js +243 -0
- package/dist/server/server/agent/agent-archive.d.ts +11 -0
- package/dist/server/server/agent/agent-archive.js +16 -0
- package/dist/server/server/agent/agent-configuration-validator.d.ts +18 -0
- package/dist/server/server/agent/agent-configuration-validator.js +42 -0
- package/dist/server/server/agent/agent-loading.d.ts +17 -0
- package/dist/server/server/agent/agent-loading.js +88 -0
- package/dist/server/server/agent/agent-manager.d.ts +555 -0
- package/dist/server/server/agent/agent-manager.js +3930 -0
- package/dist/server/server/agent/agent-owner.d.ts +12 -0
- package/dist/server/server/agent/agent-owner.js +12 -0
- package/dist/server/server/agent/agent-projections.d.ts +26 -0
- package/dist/server/server/agent/agent-projections.js +414 -0
- package/dist/server/server/agent/agent-prompt.d.ts +91 -0
- package/dist/server/server/agent/agent-prompt.js +359 -0
- package/dist/server/server/agent/agent-response-loop.d.ts +72 -0
- package/dist/server/server/agent/agent-response-loop.js +336 -0
- package/dist/server/server/agent/agent-run-state.d.ts +76 -0
- package/dist/server/server/agent/agent-run-state.js +212 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts +742 -0
- package/dist/server/server/agent/agent-sdk-types.js +25 -0
- package/dist/server/server/agent/agent-storage.d.ts +148 -0
- package/dist/server/server/agent/agent-storage.js +401 -0
- package/dist/server/server/agent/agent-stream-coalescer.d.ts +43 -0
- package/dist/server/server/agent/agent-stream-coalescer.js +187 -0
- package/dist/server/server/agent/agent-timeline-content.d.ts +6 -0
- package/dist/server/server/agent/agent-timeline-content.js +62 -0
- package/dist/server/server/agent/agent-timeline-store-types.d.ts +54 -0
- package/dist/server/server/agent/agent-timeline-store-types.js +2 -0
- package/dist/server/server/agent/agent-timeline-store.d.ts +31 -0
- package/dist/server/server/agent/agent-timeline-store.js +249 -0
- package/dist/server/server/agent/audio-utils.d.ts +3 -0
- package/dist/server/server/agent/audio-utils.js +19 -0
- package/dist/server/server/agent/create-agent/create.d.ts +102 -0
- package/dist/server/server/agent/create-agent/create.js +362 -0
- package/dist/server/server/agent/create-agent/intent.d.ts +25 -0
- package/dist/server/server/agent/create-agent/intent.js +30 -0
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.d.ts +65 -0
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.js +166 -0
- package/dist/server/server/agent/create-agent-mode.d.ts +13 -0
- package/dist/server/server/agent/create-agent-mode.js +65 -0
- package/dist/server/server/agent/create-agent-title.d.ts +10 -0
- package/dist/server/server/agent/create-agent-title.js +34 -0
- package/dist/server/server/agent/dictation-debug.d.ts +13 -0
- package/dist/server/server/agent/dictation-debug.js +50 -0
- package/dist/server/server/agent/import-sessions.d.ts +57 -0
- package/dist/server/server/agent/import-sessions.js +240 -0
- package/dist/server/server/agent/lifecycle-command.d.ts +73 -0
- package/dist/server/server/agent/lifecycle-command.js +99 -0
- package/dist/server/server/agent/mcp-server.d.ts +5 -0
- package/dist/server/server/agent/mcp-server.js +31 -0
- package/dist/server/server/agent/mcp-shared.d.ts +181 -0
- package/dist/server/server/agent/mcp-shared.js +199 -0
- package/dist/server/server/agent/mutable-provider-config-owner.d.ts +8 -0
- package/dist/server/server/agent/mutable-provider-config-owner.js +35 -0
- package/dist/server/server/agent/paseo-tool-policy.d.ts +9 -0
- package/dist/server/server/agent/paseo-tool-policy.js +16 -0
- package/dist/server/server/agent/pcm16-resampler.d.ts +14 -0
- package/dist/server/server/agent/pcm16-resampler.js +63 -0
- package/dist/server/server/agent/permission-response.d.ts +15 -0
- package/dist/server/server/agent/permission-response.js +14 -0
- package/dist/server/server/agent/plugin-provider.d.ts +16 -0
- package/dist/server/server/agent/plugin-provider.js +1356 -0
- package/dist/server/server/agent/prompt-attachments.d.ts +12 -0
- package/dist/server/server/agent/prompt-attachments.js +182 -0
- package/dist/server/server/agent/provider-history-timestamps.d.ts +2 -0
- package/dist/server/server/agent/provider-history-timestamps.js +16 -0
- package/dist/server/server/agent/provider-launch-config.d.ts +43 -0
- package/dist/server/server/agent/provider-launch-config.js +167 -0
- package/dist/server/server/agent/provider-notices.d.ts +4 -0
- package/dist/server/server/agent/provider-notices.js +9 -0
- package/dist/server/server/agent/provider-options.d.ts +19 -0
- package/dist/server/server/agent/provider-options.js +53 -0
- package/dist/server/server/agent/provider-refresh-deadline.d.ts +11 -0
- package/dist/server/server/agent/provider-refresh-deadline.js +65 -0
- package/dist/server/server/agent/provider-registry.d.ts +72 -0
- package/dist/server/server/agent/provider-registry.js +604 -0
- package/dist/server/server/agent/provider-runtime.d.ts +16 -0
- package/dist/server/server/agent/provider-runtime.js +35 -0
- package/dist/server/server/agent/provider-session-import.d.ts +10 -0
- package/dist/server/server/agent/provider-session-import.js +55 -0
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +166 -0
- package/dist/server/server/agent/provider-snapshot-manager.js +843 -0
- package/dist/server/server/agent/provider-subagents/store.d.ts +68 -0
- package/dist/server/server/agent/provider-subagents/store.js +111 -0
- package/dist/server/server/agent/providers/acp-agent.d.ts +703 -0
- package/dist/server/server/agent/providers/acp-agent.js +3412 -0
- package/dist/server/server/agent/providers/claude/agent.d.ts +74 -0
- package/dist/server/server/agent/providers/claude/agent.js +5236 -0
- package/dist/server/server/agent/providers/claude/feature-definitions.d.ts +8 -0
- package/dist/server/server/agent/providers/claude/feature-definitions.js +24 -0
- package/dist/server/server/agent/providers/claude/hooks.d.ts +12 -0
- package/dist/server/server/agent/providers/claude/hooks.js +20 -0
- package/dist/server/server/agent/providers/claude/model-manifest.d.ts +147 -0
- package/dist/server/server/agent/providers/claude/model-manifest.js +342 -0
- package/dist/server/server/agent/providers/claude/models.d.ts +28 -0
- package/dist/server/server/agent/providers/claude/models.js +141 -0
- package/dist/server/server/agent/providers/claude/options.d.ts +85 -0
- package/dist/server/server/agent/providers/claude/options.js +93 -0
- package/dist/server/server/agent/providers/claude/partial-json.d.ts +5 -0
- package/dist/server/server/agent/providers/claude/partial-json.js +306 -0
- package/dist/server/server/agent/providers/claude/project-dir.d.ts +6 -0
- package/dist/server/server/agent/providers/claude/project-dir.js +57 -0
- package/dist/server/server/agent/providers/claude/query.d.ts +17 -0
- package/dist/server/server/agent/providers/claude/query.js +86 -0
- package/dist/server/server/agent/providers/claude/rewind.d.ts +30 -0
- package/dist/server/server/agent/providers/claude/rewind.js +30 -0
- package/dist/server/server/agent/providers/claude/side-question.d.ts +16 -0
- package/dist/server/server/agent/providers/claude/side-question.js +57 -0
- package/dist/server/server/agent/providers/claude/sidechain-tracker.d.ts +35 -0
- package/dist/server/server/agent/providers/claude/sidechain-tracker.js +392 -0
- package/dist/server/server/agent/providers/claude/subagents/live-source.d.ts +161 -0
- package/dist/server/server/agent/providers/claude/subagents/live-source.js +415 -0
- package/dist/server/server/agent/providers/claude/subagents/observation.d.ts +53 -0
- package/dist/server/server/agent/providers/claude/subagents/observation.js +57 -0
- package/dist/server/server/agent/providers/claude/subagents/presentation.d.ts +12 -0
- package/dist/server/server/agent/providers/claude/subagents/presentation.js +41 -0
- package/dist/server/server/agent/providers/claude/subagents/replay-source.d.ts +70 -0
- package/dist/server/server/agent/providers/claude/subagents/replay-source.js +305 -0
- package/dist/server/server/agent/providers/claude/subagents/workflow-output.d.ts +4 -0
- package/dist/server/server/agent/providers/claude/subagents/workflow-output.js +86 -0
- package/dist/server/server/agent/providers/claude/subagents/workflow-replay-source.d.ts +38 -0
- package/dist/server/server/agent/providers/claude/subagents/workflow-replay-source.js +225 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +37 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +296 -0
- package/dist/server/server/agent/providers/claude/task-state.d.ts +18 -0
- package/dist/server/server/agent/providers/claude/task-state.js +185 -0
- package/dist/server/server/agent/providers/claude/test-rewind-claude-sdk.d.ts +24 -0
- package/dist/server/server/agent/providers/claude/test-rewind-claude-sdk.js +23 -0
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts +3 -0
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +130 -0
- package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts +16 -0
- package/dist/server/server/agent/providers/claude/tool-call-mapper.js +129 -0
- package/dist/server/server/agent/providers/codex/app-server-transport.d.ts +101 -0
- package/dist/server/server/agent/providers/codex/app-server-transport.js +290 -0
- package/dist/server/server/agent/providers/codex/async-questions.d.ts +18 -0
- package/dist/server/server/agent/providers/codex/async-questions.js +135 -0
- package/dist/server/server/agent/providers/codex/options.d.ts +59 -0
- package/dist/server/server/agent/providers/codex/options.js +82 -0
- package/dist/server/server/agent/providers/codex/rewind.d.ts +24 -0
- package/dist/server/server/agent/providers/codex/rewind.js +77 -0
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts +12 -0
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.js +104 -0
- package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts +24 -0
- package/dist/server/server/agent/providers/codex/tool-call-mapper.js +909 -0
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +384 -0
- package/dist/server/server/agent/providers/codex-app-server-agent.js +5877 -0
- package/dist/server/server/agent/providers/codex-feature-definitions.d.ts +11 -0
- package/dist/server/server/agent/providers/codex-feature-definitions.js +55 -0
- package/dist/server/server/agent/providers/copilot-acp-agent.d.ts +26 -0
- package/dist/server/server/agent/providers/copilot-acp-agent.js +187 -0
- package/dist/server/server/agent/providers/cursor-acp-agent.d.ts +17 -0
- package/dist/server/server/agent/providers/cursor-acp-agent.js +31 -0
- package/dist/server/server/agent/providers/diagnostic-utils.d.ts +36 -0
- package/dist/server/server/agent/providers/diagnostic-utils.js +356 -0
- package/dist/server/server/agent/providers/generic-acp-agent.d.ts +55 -0
- package/dist/server/server/agent/providers/generic-acp-agent.js +178 -0
- package/dist/server/server/agent/providers/gjc-acp-agent.d.ts +155 -0
- package/dist/server/server/agent/providers/gjc-acp-agent.js +588 -0
- package/dist/server/server/agent/providers/jsonl-frame-decoder.d.ts +17 -0
- package/dist/server/server/agent/providers/jsonl-frame-decoder.js +149 -0
- package/dist/server/server/agent/providers/jsonl-rpc-process.d.ts +63 -0
- package/dist/server/server/agent/providers/jsonl-rpc-process.js +207 -0
- package/dist/server/server/agent/providers/kimi-acp-agent.d.ts +35 -0
- package/dist/server/server/agent/providers/kimi-acp-agent.js +65 -0
- package/dist/server/server/agent/providers/kiro-acp-agent.d.ts +17 -0
- package/dist/server/server/agent/providers/kiro-acp-agent.js +78 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +111 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.js +1340 -0
- package/dist/server/server/agent/providers/mock-slow-provider.d.ts +14 -0
- package/dist/server/server/agent/providers/mock-slow-provider.js +40 -0
- package/dist/server/server/agent/providers/omp/advisor-message.d.ts +11 -0
- package/dist/server/server/agent/providers/omp/advisor-message.js +80 -0
- package/dist/server/server/agent/providers/omp/agent.d.ts +178 -0
- package/dist/server/server/agent/providers/omp/agent.js +1952 -0
- package/dist/server/server/agent/providers/omp/cli-runtime.d.ts +22 -0
- package/dist/server/server/agent/providers/omp/cli-runtime.js +208 -0
- package/dist/server/server/agent/providers/omp/commands.d.ts +8 -0
- package/dist/server/server/agent/providers/omp/commands.js +62 -0
- package/dist/server/server/agent/providers/omp/custom-message.d.ts +7 -0
- package/dist/server/server/agent/providers/omp/custom-message.js +4 -0
- package/dist/server/server/agent/providers/omp/event-mapper.d.ts +13 -0
- package/dist/server/server/agent/providers/omp/event-mapper.js +267 -0
- package/dist/server/server/agent/providers/omp/history-hooks.d.ts +3 -0
- package/dist/server/server/agent/providers/omp/history-hooks.js +13 -0
- package/dist/server/server/agent/providers/omp/history.d.ts +19 -0
- package/dist/server/server/agent/providers/omp/history.js +306 -0
- package/dist/server/server/agent/providers/omp/host-tools.d.ts +14 -0
- package/dist/server/server/agent/providers/omp/host-tools.js +196 -0
- package/dist/server/server/agent/providers/omp/map-omp-model.d.ts +11 -0
- package/dist/server/server/agent/providers/omp/map-omp-model.js +67 -0
- package/dist/server/server/agent/providers/omp/message-history.d.ts +38 -0
- package/dist/server/server/agent/providers/omp/message-history.js +216 -0
- package/dist/server/server/agent/providers/omp/protocol-session.d.ts +13 -0
- package/dist/server/server/agent/providers/omp/protocol-session.js +39 -0
- package/dist/server/server/agent/providers/omp/provider-config.d.ts +43 -0
- package/dist/server/server/agent/providers/omp/provider-config.js +120 -0
- package/dist/server/server/agent/providers/omp/rpc-types.d.ts +2040 -0
- package/dist/server/server/agent/providers/omp/rpc-types.js +551 -0
- package/dist/server/server/agent/providers/omp/rpc-ui-permission-mapper.d.ts +24 -0
- package/dist/server/server/agent/providers/omp/rpc-ui-permission-mapper.js +156 -0
- package/dist/server/server/agent/providers/omp/runtime.d.ts +85 -0
- package/dist/server/server/agent/providers/omp/runtime.js +62 -0
- package/dist/server/server/agent/providers/omp/session-descriptor.d.ts +16 -0
- package/dist/server/server/agent/providers/omp/session-descriptor.js +389 -0
- package/dist/server/server/agent/providers/omp/subagent-card-tracker.d.ts +33 -0
- package/dist/server/server/agent/providers/omp/subagent-card-tracker.js +264 -0
- package/dist/server/server/agent/providers/omp/subagent-index.d.ts +13 -0
- package/dist/server/server/agent/providers/omp/subagent-index.js +111 -0
- package/dist/server/server/agent/providers/omp/subagent-title.d.ts +2 -0
- package/dist/server/server/agent/providers/omp/subagent-title.js +14 -0
- package/dist/server/server/agent/providers/omp/system-notice.d.ts +8 -0
- package/dist/server/server/agent/providers/omp/system-notice.js +68 -0
- package/dist/server/server/agent/providers/omp/todo-mapper.d.ts +9 -0
- package/dist/server/server/agent/providers/omp/todo-mapper.js +52 -0
- package/dist/server/server/agent/providers/omp/tool-call-detail.d.ts +110 -0
- package/dist/server/server/agent/providers/omp/tool-call-detail.js +288 -0
- package/dist/server/server/agent/providers/omp/tool-call-id.d.ts +4 -0
- package/dist/server/server/agent/providers/omp/tool-call-id.js +34 -0
- package/dist/server/server/agent/providers/omp/tool-call-mapper.d.ts +7 -0
- package/dist/server/server/agent/providers/omp/tool-call-mapper.js +98 -0
- package/dist/server/server/agent/providers/omp/usage-poller.d.ts +32 -0
- package/dist/server/server/agent/providers/omp/usage-poller.js +141 -0
- package/dist/server/server/agent/providers/opencode/bridge-plugin.bundle.mjs +14678 -0
- package/dist/server/server/agent/providers/opencode/bridge.d.ts +39 -0
- package/dist/server/server/agent/providers/opencode/bridge.js +249 -0
- package/dist/server/server/agent/providers/opencode/event-consumer.d.ts +60 -0
- package/dist/server/server/agent/providers/opencode/event-consumer.js +221 -0
- package/dist/server/server/agent/providers/opencode/options.d.ts +156 -0
- package/dist/server/server/agent/providers/opencode/options.js +62 -0
- package/dist/server/server/agent/providers/opencode/paths.d.ts +2 -0
- package/dist/server/server/agent/providers/opencode/paths.js +7 -0
- package/dist/server/server/agent/providers/opencode/rewind.d.ts +18 -0
- package/dist/server/server/agent/providers/opencode/rewind.js +14 -0
- package/dist/server/server/agent/providers/opencode/server-manager.d.ts +101 -0
- package/dist/server/server/agent/providers/opencode/server-manager.js +513 -0
- package/dist/server/server/agent/providers/opencode/side-question.d.ts +78 -0
- package/dist/server/server/agent/providers/opencode/side-question.js +172 -0
- package/dist/server/server/agent/providers/opencode/subagent-presentation.d.ts +36 -0
- package/dist/server/server/agent/providers/opencode/subagent-presentation.js +62 -0
- package/dist/server/server/agent/providers/opencode/test-server-manager.d.ts +22 -0
- package/dist/server/server/agent/providers/opencode/test-server-manager.js +43 -0
- package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts +3 -0
- package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js +256 -0
- package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts +13 -0
- package/dist/server/server/agent/providers/opencode/tool-call-mapper.js +53 -0
- package/dist/server/server/agent/providers/opencode-agent.d.ts +393 -0
- package/dist/server/server/agent/providers/opencode-agent.js +4106 -0
- package/dist/server/server/agent/providers/pi/agent.d.ts +164 -0
- package/dist/server/server/agent/providers/pi/agent.js +2236 -0
- package/dist/server/server/agent/providers/pi/cli-runtime.d.ts +21 -0
- package/dist/server/server/agent/providers/pi/cli-runtime.js +177 -0
- package/dist/server/server/agent/providers/pi/history-mapper.d.ts +36 -0
- package/dist/server/server/agent/providers/pi/history-mapper.js +212 -0
- package/dist/server/server/agent/providers/pi/rewind.d.ts +8 -0
- package/dist/server/server/agent/providers/pi/rewind.js +8 -0
- package/dist/server/server/agent/providers/pi/rpc-types.d.ts +252 -0
- package/dist/server/server/agent/providers/pi/rpc-types.js +2 -0
- package/dist/server/server/agent/providers/pi/runtime.d.ts +77 -0
- package/dist/server/server/agent/providers/pi/runtime.js +65 -0
- package/dist/server/server/agent/providers/pi/session-descriptor.d.ts +16 -0
- package/dist/server/server/agent/providers/pi/session-descriptor.js +355 -0
- package/dist/server/server/agent/providers/pi/tool-call-mapper.d.ts +113 -0
- package/dist/server/server/agent/providers/pi/tool-call-mapper.js +340 -0
- package/dist/server/server/agent/providers/pi/usage-poller.d.ts +32 -0
- package/dist/server/server/agent/providers/pi/usage-poller.js +141 -0
- package/dist/server/server/agent/providers/provider-image-output.d.ts +25 -0
- package/dist/server/server/agent/providers/provider-image-output.js +165 -0
- package/dist/server/server/agent/providers/provider-runner.d.ts +27 -0
- package/dist/server/server/agent/providers/provider-runner.js +81 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +884 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.js +778 -0
- package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts +25 -0
- package/dist/server/server/agent/providers/tool-call-mapper-utils.js +208 -0
- package/dist/server/server/agent/providers/trae-acp-agent.d.ts +15 -0
- package/dist/server/server/agent/providers/trae-acp-agent.js +18 -0
- package/dist/server/server/agent/recordings-debug.d.ts +3 -0
- package/dist/server/server/agent/recordings-debug.js +19 -0
- package/dist/server/server/agent/rewind/rewind.d.ts +10 -0
- package/dist/server/server/agent/rewind/rewind.js +29 -0
- package/dist/server/server/agent/rewind/test-rewind-session.d.ts +48 -0
- package/dist/server/server/agent/rewind/test-rewind-session.js +88 -0
- package/dist/server/server/agent/runtime-mcp-config.d.ts +14 -0
- package/dist/server/server/agent/runtime-mcp-config.js +53 -0
- package/dist/server/server/agent/side-conversations/store.d.ts +33 -0
- package/dist/server/server/agent/side-conversations/store.js +62 -0
- package/dist/server/server/agent/stale-provider-session-error.d.ts +6 -0
- package/dist/server/server/agent/stale-provider-session-error.js +11 -0
- package/dist/server/server/agent/structured-generation-providers.d.ts +29 -0
- package/dist/server/server/agent/structured-generation-providers.js +193 -0
- package/dist/server/server/agent/stt-debug.d.ts +10 -0
- package/dist/server/server/agent/stt-debug.js +33 -0
- package/dist/server/server/agent/stt-manager.d.ts +38 -0
- package/dist/server/server/agent/stt-manager.js +246 -0
- package/dist/server/server/agent/system-prompt.d.ts +2 -0
- package/dist/server/server/agent/system-prompt.js +8 -0
- package/dist/server/server/agent/timeline-append.d.ts +10 -0
- package/dist/server/server/agent/timeline-append.js +27 -0
- package/dist/server/server/agent/timeline-page-bounds.d.ts +19 -0
- package/dist/server/server/agent/timeline-page-bounds.js +37 -0
- package/dist/server/server/agent/timeline-projection.d.ts +74 -0
- package/dist/server/server/agent/timeline-projection.js +467 -0
- package/dist/server/server/agent/timeline-prompt-index.d.ts +12 -0
- package/dist/server/server/agent/timeline-prompt-index.js +23 -0
- package/dist/server/server/agent/tools/paseo-tool-serialization.d.ts +4 -0
- package/dist/server/server/agent/tools/paseo-tool-serialization.js +58 -0
- package/dist/server/server/agent/tools/paseo-tools.d.ts +62 -0
- package/dist/server/server/agent/tools/paseo-tools.js +2445 -0
- package/dist/server/server/agent/tools/types.d.ts +39 -0
- package/dist/server/server/agent/tools/types.js +2 -0
- package/dist/server/server/agent/tts-debug.d.ts +8 -0
- package/dist/server/server/agent/tts-debug.js +24 -0
- package/dist/server/server/agent/tts-manager.d.ts +41 -0
- package/dist/server/server/agent/tts-manager.js +375 -0
- package/dist/server/server/agent-attention-policy.d.ts +29 -0
- package/dist/server/server/agent-attention-policy.js +36 -0
- package/dist/server/server/agent-history-search.d.ts +48 -0
- package/dist/server/server/agent-history-search.js +129 -0
- package/dist/server/server/atomic-file.d.ts +3 -0
- package/dist/server/server/atomic-file.js +19 -0
- package/dist/server/server/auth.d.ts +38 -0
- package/dist/server/server/auth.js +122 -0
- package/dist/server/server/authorization/index.d.ts +18 -0
- package/dist/server/server/authorization/index.js +47 -0
- package/dist/server/server/authorization/operation-permissions.d.ts +8 -0
- package/dist/server/server/authorization/operation-permissions.js +438 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +40 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +64 -0
- package/dist/server/server/auto-archive-on-merge/index.d.ts +13 -0
- package/dist/server/server/auto-archive-on-merge/index.js +77 -0
- package/dist/server/server/bootstrap.d.ts +166 -0
- package/dist/server/server/bootstrap.js +1388 -0
- package/dist/server/server/browser-tools/broker.d.ts +45 -0
- package/dist/server/server/browser-tools/broker.js +390 -0
- package/dist/server/server/browser-tools/errors.d.ts +22 -0
- package/dist/server/server/browser-tools/errors.js +27 -0
- package/dist/server/server/browser-tools/policy.d.ts +10 -0
- package/dist/server/server/browser-tools/policy.js +16 -0
- package/dist/server/server/browser-tools/tools.d.ts +16 -0
- package/dist/server/server/browser-tools/tools.js +796 -0
- package/dist/server/server/checkout/status-projection.d.ts +19 -0
- package/dist/server/server/checkout/status-projection.js +127 -0
- package/dist/server/server/checkout-diff-manager.d.ts +48 -0
- package/dist/server/server/checkout-diff-manager.js +284 -0
- package/dist/server/server/checkout-git-utils.d.ts +11 -0
- package/dist/server/server/checkout-git-utils.js +34 -0
- package/dist/server/server/client-message-id.d.ts +3 -0
- package/dist/server/server/client-message-id.js +12 -0
- package/dist/server/server/config.d.ts +26 -0
- package/dist/server/server/config.js +594 -0
- package/dist/server/server/connection-offer.d.ts +20 -0
- package/dist/server/server/connection-offer.js +59 -0
- package/dist/server/server/creation/index.d.ts +41 -0
- package/dist/server/server/creation/index.js +250 -0
- package/dist/server/server/daemon-config-store.d.ts +63 -0
- package/dist/server/server/daemon-config-store.js +524 -0
- package/dist/server/server/daemon-keypair.d.ts +8 -0
- package/dist/server/server/daemon-keypair.js +42 -0
- package/dist/server/server/daemon-version.d.ts +5 -0
- package/dist/server/server/daemon-version.js +22 -0
- package/dist/server/server/daemon-worker.d.ts +2 -0
- package/dist/server/server/daemon-worker.js +287 -0
- package/dist/server/server/dictation/dictation-stream-manager.d.ts +88 -0
- package/dist/server/server/dictation/dictation-stream-manager.js +578 -0
- package/dist/server/server/directory-sync/index.d.ts +45 -0
- package/dist/server/server/directory-sync/index.js +98 -0
- package/dist/server/server/directory-sync/internal/versioned-collection.d.ts +56 -0
- package/dist/server/server/directory-sync/internal/versioned-collection.js +94 -0
- package/dist/server/server/exports.d.ts +24 -0
- package/dist/server/server/exports.js +24 -0
- package/dist/server/server/file-download/token-store.d.ts +24 -0
- package/dist/server/server/file-download/token-store.js +40 -0
- package/dist/server/server/file-explorer/observer.d.ts +32 -0
- package/dist/server/server/file-explorer/observer.js +130 -0
- package/dist/server/server/file-explorer/service.d.ts +125 -0
- package/dist/server/server/file-explorer/service.js +663 -0
- package/dist/server/server/file-observer/index.d.ts +43 -0
- package/dist/server/server/file-observer/index.js +114 -0
- package/dist/server/server/file-observer/internal/contracts.d.ts +26 -0
- package/dist/server/server/file-observer/internal/contracts.js +2 -0
- package/dist/server/server/file-observer/internal/linux.d.ts +4 -0
- package/dist/server/server/file-observer/internal/linux.js +260 -0
- package/dist/server/server/file-observer/internal/native-recursive.d.ts +9 -0
- package/dist/server/server/file-observer/internal/native-recursive.js +577 -0
- package/dist/server/server/file-observer/internal/observation.d.ts +21 -0
- package/dist/server/server/file-observer/internal/observation.js +129 -0
- package/dist/server/server/file-observer/internal/paths.d.ts +10 -0
- package/dist/server/server/file-observer/internal/paths.js +43 -0
- package/dist/server/server/file-upload/index.d.ts +27 -0
- package/dist/server/server/file-upload/index.js +158 -0
- package/dist/server/server/git-metadata-event-rules.d.ts +123 -0
- package/dist/server/server/git-metadata-event-rules.js +152 -0
- package/dist/server/server/hostnames.d.ts +13 -0
- package/dist/server/server/hostnames.js +94 -0
- package/dist/server/server/hub/daemon-executions.d.ts +89 -0
- package/dist/server/server/hub/daemon-executions.js +287 -0
- package/dist/server/server/hub/execution-controller.d.ts +30 -0
- package/dist/server/server/hub/execution-controller.js +220 -0
- package/dist/server/server/hub/relationship-controller.d.ts +116 -0
- package/dist/server/server/hub/relationship-controller.js +534 -0
- package/dist/server/server/hub/relationship-remote.d.ts +68 -0
- package/dist/server/server/hub/relationship-remote.js +159 -0
- package/dist/server/server/hub/relationship-retry.d.ts +7 -0
- package/dist/server/server/hub/relationship-retry.js +15 -0
- package/dist/server/server/json-utils.d.ts +11 -0
- package/dist/server/server/json-utils.js +45 -0
- package/dist/server/server/lifecycle-reasons.d.ts +4 -0
- package/dist/server/server/lifecycle-reasons.js +6 -0
- package/dist/server/server/logger.d.ts +29 -0
- package/dist/server/server/logger.js +127 -0
- package/dist/server/server/managed-processes/managed-processes.d.ts +76 -0
- package/dist/server/server/managed-processes/managed-processes.js +326 -0
- package/dist/server/server/messages.d.ts +9 -0
- package/dist/server/server/messages.js +29 -0
- package/dist/server/server/migrations/backfill-workspace-id.migration.d.ts +9 -0
- package/dist/server/server/migrations/backfill-workspace-id.migration.js +64 -0
- package/dist/server/server/orchestration-skills/index.d.ts +18 -0
- package/dist/server/server/orchestration-skills/index.js +18 -0
- package/dist/server/server/orchestration-skills/internal/controller.d.ts +39 -0
- package/dist/server/server/orchestration-skills/internal/controller.js +108 -0
- package/dist/server/server/orchestration-skills/internal/operations.d.ts +43 -0
- package/dist/server/server/orchestration-skills/internal/operations.js +178 -0
- package/dist/server/server/orchestration-skills/internal/paths.d.ts +4 -0
- package/dist/server/server/orchestration-skills/internal/paths.js +21 -0
- package/dist/server/server/orchestration-skills/internal/selection-store.d.ts +12 -0
- package/dist/server/server/orchestration-skills/internal/selection-store.js +39 -0
- package/dist/server/server/orchestration-skills/internal/sync.d.ts +21 -0
- package/dist/server/server/orchestration-skills/internal/sync.js +177 -0
- package/dist/server/server/orchestration-skills/internal/transaction.d.ts +22 -0
- package/dist/server/server/orchestration-skills/internal/transaction.js +483 -0
- package/dist/server/server/package-version.d.ts +19 -0
- package/dist/server/server/package-version.js +60 -0
- package/dist/server/server/pagination/cursor.d.ts +16 -0
- package/dist/server/server/pagination/cursor.js +62 -0
- package/dist/server/server/pagination/sortable-pager.d.ts +24 -0
- package/dist/server/server/pagination/sortable-pager.js +68 -0
- package/dist/server/server/pairing-offer.d.ts +18 -0
- package/dist/server/server/pairing-offer.js +47 -0
- package/dist/server/server/pairing-qr.d.ts +2 -0
- package/dist/server/server/pairing-qr.js +15 -0
- package/dist/server/server/paseo-env.d.ts +13 -0
- package/dist/server/server/paseo-env.js +54 -0
- package/dist/server/server/paseo-home.d.ts +2 -0
- package/dist/server/server/paseo-home.js +19 -0
- package/dist/server/server/paseo-worktree-service.d.ts +45 -0
- package/dist/server/server/paseo-worktree-service.js +191 -0
- package/dist/server/server/path-utils.d.ts +5 -0
- package/dist/server/server/path-utils.js +34 -0
- package/dist/server/server/persisted-config.d.ts +397 -0
- package/dist/server/server/persisted-config.js +411 -0
- package/dist/server/server/persistence-hooks.d.ts +32 -0
- package/dist/server/server/persistence-hooks.js +96 -0
- package/dist/server/server/pid-lock.d.ts +44 -0
- package/dist/server/server/pid-lock.js +285 -0
- package/dist/server/server/plugins/compiler-imports.d.ts +13 -0
- package/dist/server/server/plugins/compiler-imports.js +90 -0
- package/dist/server/server/plugins/compiler.d.ts +10 -0
- package/dist/server/server/plugins/compiler.js +354 -0
- package/dist/server/server/plugins/daemon-transport.d.ts +9 -0
- package/dist/server/server/plugins/daemon-transport.js +68 -0
- package/dist/server/server/plugins/index.d.ts +102 -0
- package/dist/server/server/plugins/index.js +575 -0
- package/dist/server/server/plugins/lifecycle/index.d.ts +40 -0
- package/dist/server/server/plugins/lifecycle/index.js +200 -0
- package/dist/server/server/plugins/managed-source.d.ts +50 -0
- package/dist/server/server/plugins/managed-source.js +327 -0
- package/dist/server/server/plugins/manifest.d.ts +12 -0
- package/dist/server/server/plugins/manifest.js +26 -0
- package/dist/server/server/plugins/plugin-process-protocol.d.ts +120 -0
- package/dist/server/server/plugins/plugin-process-protocol.js +154 -0
- package/dist/server/server/plugins/plugin-process.d.ts +2 -0
- package/dist/server/server/plugins/plugin-process.js +419 -0
- package/dist/server/server/plugins/plugin-sdk-specifiers.d.ts +5 -0
- package/dist/server/server/plugins/plugin-sdk-specifiers.js +23 -0
- package/dist/server/server/plugins/plugin-session-identity.d.ts +4 -0
- package/dist/server/server/plugins/plugin-session-identity.js +11 -0
- package/dist/server/server/plugins/preparation.d.ts +4 -0
- package/dist/server/server/plugins/preparation.js +49 -0
- package/dist/server/server/plugins/provider-icon.d.ts +2 -0
- package/dist/server/server/plugins/provider-icon.js +55 -0
- package/dist/server/server/plugins/runtime.d.ts +83 -0
- package/dist/server/server/plugins/runtime.js +836 -0
- package/dist/server/server/plugins/session-socket.d.ts +23 -0
- package/dist/server/server/plugins/session-socket.js +82 -0
- package/dist/server/server/plugins/settings/index.d.ts +96 -0
- package/dist/server/server/plugins/settings/index.js +115 -0
- package/dist/server/server/private-files.d.ts +6 -0
- package/dist/server/server/private-files.js +37 -0
- package/dist/server/server/process-diagnostics.d.ts +17 -0
- package/dist/server/server/process-diagnostics.js +22 -0
- package/dist/server/server/project-directory-service.d.ts +30 -0
- package/dist/server/server/project-directory-service.js +120 -0
- package/dist/server/server/project-key.d.ts +14 -0
- package/dist/server/server/project-key.js +44 -0
- package/dist/server/server/push/index.d.ts +16 -0
- package/dist/server/server/push/index.js +26 -0
- package/dist/server/server/push/push-service.d.ts +19 -0
- package/dist/server/server/push/push-service.js +66 -0
- package/dist/server/server/push/token-store.d.ts +22 -0
- package/dist/server/server/push/token-store.js +125 -0
- package/dist/server/server/relay-runtime.d.ts +27 -0
- package/dist/server/server/relay-runtime.js +46 -0
- package/dist/server/server/relay-transport.d.ts +33 -0
- package/dist/server/server/relay-transport.js +481 -0
- package/dist/server/server/request-receipts/index.d.ts +33 -0
- package/dist/server/server/request-receipts/index.js +131 -0
- package/dist/server/server/resolve-workspace-id-for-path.d.ts +3 -0
- package/dist/server/server/resolve-workspace-id-for-path.js +41 -0
- package/dist/server/server/resolve-worktree-creation-intent.d.ts +44 -0
- package/dist/server/server/resolve-worktree-creation-intent.js +186 -0
- package/dist/server/server/schedule/cron.d.ts +4 -0
- package/dist/server/server/schedule/cron.js +83 -0
- package/dist/server/server/schedule/service.d.ts +75 -0
- package/dist/server/server/schedule/service.js +810 -0
- package/dist/server/server/schedule/store.d.ts +27 -0
- package/dist/server/server/schedule/store.js +190 -0
- package/dist/server/server/script-health-monitor.d.ts +39 -0
- package/dist/server/server/script-health-monitor.js +159 -0
- package/dist/server/server/script-proxy.d.ts +3 -0
- package/dist/server/server/script-proxy.js +2 -0
- package/dist/server/server/script-route-branch-handler.d.ts +10 -0
- package/dist/server/server/script-route-branch-handler.js +11 -0
- package/dist/server/server/script-status-projection.d.ts +36 -0
- package/dist/server/server/script-status-projection.js +200 -0
- package/dist/server/server/server-id.d.ts +17 -0
- package/dist/server/server/server-id.js +68 -0
- package/dist/server/server/service-proxy.d.ts +237 -0
- package/dist/server/server/service-proxy.js +679 -0
- package/dist/server/server/session/agent-config/agent-config-session.d.ts +75 -0
- package/dist/server/server/session/agent-config/agent-config-session.js +151 -0
- package/dist/server/server/session/agent-updates/agent-updates-service.d.ts +65 -0
- package/dist/server/server/session/agent-updates/agent-updates-service.js +266 -0
- package/dist/server/server/session/checkout/checkout-session.d.ts +150 -0
- package/dist/server/server/session/checkout/checkout-session.js +1187 -0
- package/dist/server/server/session/checkout/git-metadata-generator.d.ts +53 -0
- package/dist/server/server/session/checkout/git-metadata-generator.js +159 -0
- package/dist/server/server/session/daemon/daemon-self-update-session-controller.d.ts +34 -0
- package/dist/server/server/session/daemon/daemon-self-update-session-controller.js +90 -0
- package/dist/server/server/session/daemon/daemon-self-updater.d.ts +33 -0
- package/dist/server/server/session/daemon/daemon-self-updater.js +60 -0
- package/dist/server/server/session/daemon/daemon-session.d.ts +90 -0
- package/dist/server/server/session/daemon/daemon-session.js +224 -0
- package/dist/server/server/session/daemon/diagnostics.d.ts +41 -0
- package/dist/server/server/session/daemon/diagnostics.js +431 -0
- package/dist/server/server/session/daemon/install-origin.d.ts +7 -0
- package/dist/server/server/session/daemon/install-origin.js +64 -0
- package/dist/server/server/session/daemon/npm-global-cli.d.ts +29 -0
- package/dist/server/server/session/daemon/npm-global-cli.js +98 -0
- package/dist/server/server/session/files/workspace-files-session.d.ts +55 -0
- package/dist/server/server/session/files/workspace-files-session.js +359 -0
- package/dist/server/server/session/git-mutation/git-mutation-service.d.ts +32 -0
- package/dist/server/server/session/git-mutation/git-mutation-service.js +74 -0
- package/dist/server/server/session/project-config/project-config-session.d.ts +35 -0
- package/dist/server/server/session/project-config/project-config-session.js +143 -0
- package/dist/server/server/session/project-config/worktree-setup-commit-status.d.ts +6 -0
- package/dist/server/server/session/project-config/worktree-setup-commit-status.js +38 -0
- package/dist/server/server/session/provider/provider-catalog-session.d.ts +78 -0
- package/dist/server/server/session/provider/provider-catalog-session.js +388 -0
- package/dist/server/server/session/schedule/schedule-session.d.ts +47 -0
- package/dist/server/server/session/schedule/schedule-session.js +188 -0
- package/dist/server/server/session/voice/voice-session.d.ts +166 -0
- package/dist/server/server/session/voice/voice-session.js +904 -0
- package/dist/server/server/session/voice/voice-turn-controller.d.ts +38 -0
- package/dist/server/server/session/voice/voice-turn-controller.js +393 -0
- package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.d.ts +42 -0
- package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.js +170 -0
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.d.ts +59 -0
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.js +326 -0
- package/dist/server/server/session/workspace-recovery/workspace-recovery-service.d.ts +30 -0
- package/dist/server/server/session/workspace-recovery/workspace-recovery-service.js +178 -0
- package/dist/server/server/session/workspace-scripts/workspace-scripts-service.d.ts +54 -0
- package/dist/server/server/session/workspace-scripts/workspace-scripts-service.js +156 -0
- package/dist/server/server/session.d.ts +555 -0
- package/dist/server/server/session.js +6540 -0
- package/dist/server/server/speech/audio.d.ts +10 -0
- package/dist/server/server/speech/audio.js +101 -0
- package/dist/server/server/speech/provider-resolver.d.ts +3 -0
- package/dist/server/server/speech/provider-resolver.js +7 -0
- package/dist/server/server/speech/providers/local/config.d.ts +30 -0
- package/dist/server/server/speech/providers/local/config.js +134 -0
- package/dist/server/server/speech/providers/local/models.d.ts +12 -0
- package/dist/server/server/speech/providers/local/models.js +18 -0
- package/dist/server/server/speech/providers/local/runtime.d.ts +31 -0
- package/dist/server/server/speech/providers/local/runtime.js +135 -0
- package/dist/server/server/speech/providers/local/sherpa/assets/silero_vad.onnx +0 -0
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts +56 -0
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.js +74 -0
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.d.ts +17 -0
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.js +145 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.d.ts +49 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +73 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.d.ts +9 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +107 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.d.ts +28 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +137 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.d.ts +21 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +133 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.d.ts +18 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.js +84 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.d.ts +21 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.js +101 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.d.ts +19 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.js +49 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.d.ts +38 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.js +176 -0
- package/dist/server/server/speech/providers/local/worker-bytes.d.ts +4 -0
- package/dist/server/server/speech/providers/local/worker-bytes.js +9 -0
- package/dist/server/server/speech/providers/local/worker-client.d.ts +93 -0
- package/dist/server/server/speech/providers/local/worker-client.js +590 -0
- package/dist/server/server/speech/providers/local/worker-process.d.ts +2 -0
- package/dist/server/server/speech/providers/local/worker-process.js +270 -0
- package/dist/server/server/speech/providers/local/worker-protocol.d.ts +95 -0
- package/dist/server/server/speech/providers/local/worker-protocol.js +2 -0
- package/dist/server/server/speech/providers/openai/config.d.ts +19 -0
- package/dist/server/server/speech/providers/openai/config.js +148 -0
- package/dist/server/server/speech/providers/openai/runtime.d.ts +29 -0
- package/dist/server/server/speech/providers/openai/runtime.js +102 -0
- package/dist/server/server/speech/providers/openai/stt.d.ts +23 -0
- package/dist/server/server/speech/providers/openai/stt.js +209 -0
- package/dist/server/server/speech/providers/openai/tts.d.ts +19 -0
- package/dist/server/server/speech/providers/openai/tts.js +48 -0
- package/dist/server/server/speech/speech-config-resolver.d.ts +11 -0
- package/dist/server/server/speech/speech-config-resolver.js +132 -0
- package/dist/server/server/speech/speech-provider.d.ts +59 -0
- package/dist/server/server/speech/speech-provider.js +2 -0
- package/dist/server/server/speech/speech-runtime.d.ts +45 -0
- package/dist/server/server/speech/speech-runtime.js +571 -0
- package/dist/server/server/speech/speech-types.d.ts +22 -0
- package/dist/server/server/speech/speech-types.js +8 -0
- package/dist/server/server/speech/turn-detection-provider.d.ts +23 -0
- package/dist/server/server/speech/turn-detection-provider.js +2 -0
- package/dist/server/server/utils/diff-highlighter.d.ts +57 -0
- package/dist/server/server/utils/diff-highlighter.js +304 -0
- package/dist/server/server/voice-config.d.ts +15 -0
- package/dist/server/server/voice-config.js +54 -0
- package/dist/server/server/voice-permission-policy.d.ts +4 -0
- package/dist/server/server/voice-permission-policy.js +13 -0
- package/dist/server/server/voice-types.d.ts +12 -0
- package/dist/server/server/voice-types.js +2 -0
- package/dist/server/server/watcher-liveness-canary.d.ts +11 -0
- package/dist/server/server/watcher-liveness-canary.js +53 -0
- package/dist/server/server/web-ui.d.ts +10 -0
- package/dist/server/server/web-ui.js +205 -0
- package/dist/server/server/websocket/encrypted-relay-socket.d.ts +23 -0
- package/dist/server/server/websocket/encrypted-relay-socket.js +64 -0
- package/dist/server/server/websocket/physical-socket.d.ts +36 -0
- package/dist/server/server/websocket/physical-socket.js +95 -0
- package/dist/server/server/websocket/runtime-metrics.d.ts +95 -0
- package/dist/server/server/websocket/runtime-metrics.js +148 -0
- package/dist/server/server/websocket-server.d.ts +214 -0
- package/dist/server/server/websocket-server.js +2258 -0
- package/dist/server/server/workspace-archive-service.d.ts +63 -0
- package/dist/server/server/workspace-archive-service.js +355 -0
- package/dist/server/server/workspace-auto-name.d.ts +55 -0
- package/dist/server/server/workspace-auto-name.js +127 -0
- package/dist/server/server/workspace-automation-gate.d.ts +16 -0
- package/dist/server/server/workspace-automation-gate.js +35 -0
- package/dist/server/server/workspace-bootstrap-dedupe.d.ts +34 -0
- package/dist/server/server/workspace-bootstrap-dedupe.js +23 -0
- package/dist/server/server/workspace-directory.d.ts +99 -0
- package/dist/server/server/workspace-directory.js +467 -0
- package/dist/server/server/workspace-git-fetch.d.ts +33 -0
- package/dist/server/server/workspace-git-fetch.js +108 -0
- package/dist/server/server/workspace-git-metadata.d.ts +8 -0
- package/dist/server/server/workspace-git-metadata.js +24 -0
- package/dist/server/server/workspace-git-service.d.ts +348 -0
- package/dist/server/server/workspace-git-service.js +2534 -0
- package/dist/server/server/workspace-labels/index.d.ts +14 -0
- package/dist/server/server/workspace-labels/index.js +10 -0
- package/dist/server/server/workspace-labels/internal/catalog-store.d.ts +50 -0
- package/dist/server/server/workspace-labels/internal/catalog-store.js +203 -0
- package/dist/server/server/workspace-labels/internal/sequence.d.ts +43 -0
- package/dist/server/server/workspace-labels/internal/sequence.js +80 -0
- package/dist/server/server/workspace-labels/internal/service.d.ts +57 -0
- package/dist/server/server/workspace-labels/internal/service.js +224 -0
- package/dist/server/server/workspace-reconciliation-service.d.ts +103 -0
- package/dist/server/server/workspace-reconciliation-service.js +379 -0
- package/dist/server/server/workspace-registry-bootstrap-legacy.d.ts +21 -0
- package/dist/server/server/workspace-registry-bootstrap-legacy.js +57 -0
- package/dist/server/server/workspace-registry-bootstrap.d.ts +14 -0
- package/dist/server/server/workspace-registry-bootstrap.js +155 -0
- package/dist/server/server/workspace-registry-model.d.ts +56 -0
- package/dist/server/server/workspace-registry-model.js +150 -0
- package/dist/server/server/workspace-registry.d.ts +228 -0
- package/dist/server/server/workspace-registry.js +468 -0
- package/dist/server/server/workspace-script-runtime-store.d.ts +28 -0
- package/dist/server/server/workspace-script-runtime-store.js +78 -0
- package/dist/server/server/workspace-service-env.d.ts +18 -0
- package/dist/server/server/workspace-service-env.js +85 -0
- package/dist/server/server/workspace-service-port-allocator.d.ts +11 -0
- package/dist/server/server/workspace-service-port-allocator.js +89 -0
- package/dist/server/server/workspace-service-port-registry.d.ts +24 -0
- package/dist/server/server/workspace-service-port-registry.js +172 -0
- package/dist/server/server/workspace-setup-runtime.d.ts +7 -0
- package/dist/server/server/workspace-setup-runtime.js +30 -0
- package/dist/server/server/workspace-source.d.ts +7 -0
- package/dist/server/server/workspace-source.js +15 -0
- package/dist/server/server/worktree/commands.d.ts +55 -0
- package/dist/server/server/worktree/commands.js +99 -0
- package/dist/server/server/worktree-bootstrap.d.ts +86 -0
- package/dist/server/server/worktree-bootstrap.js +753 -0
- package/dist/server/server/worktree-branch-name-generator.d.ts +35 -0
- package/dist/server/server/worktree-branch-name-generator.js +89 -0
- package/dist/server/server/worktree-core.d.ts +32 -0
- package/dist/server/server/worktree-core.js +114 -0
- package/dist/server/server/worktree-errors.d.ts +12 -0
- package/dist/server/server/worktree-errors.js +28 -0
- package/dist/server/server/worktree-session.d.ts +165 -0
- package/dist/server/server/worktree-session.js +594 -0
- package/dist/server/services/forge-cli-command.d.ts +128 -0
- package/dist/server/services/forge-cli-command.js +212 -0
- package/dist/server/services/forge-registry.d.ts +25 -0
- package/dist/server/services/forge-registry.js +155 -0
- package/dist/server/services/forge-resolver.d.ts +38 -0
- package/dist/server/services/forge-resolver.js +191 -0
- package/dist/server/services/forge-service.d.ts +430 -0
- package/dist/server/services/forge-service.js +50 -0
- package/dist/server/services/gitea-facts.d.ts +11 -0
- package/dist/server/services/gitea-facts.js +4 -0
- package/dist/server/services/gitea-service.d.ts +62 -0
- package/dist/server/services/gitea-service.js +1844 -0
- package/dist/server/services/github-facts.d.ts +27 -0
- package/dist/server/services/github-facts.js +4 -0
- package/dist/server/services/github-service.d.ts +76 -0
- package/dist/server/services/github-service.js +3297 -0
- package/dist/server/services/gitlab-facts.d.ts +26 -0
- package/dist/server/services/gitlab-facts.js +4 -0
- package/dist/server/services/gitlab-service.d.ts +43 -0
- package/dist/server/services/gitlab-service.js +1092 -0
- package/dist/server/services/quota-fetcher/manifest.d.ts +5 -0
- package/dist/server/services/quota-fetcher/manifest.js +118 -0
- package/dist/server/services/quota-fetcher/provider.d.ts +18 -0
- package/dist/server/services/quota-fetcher/provider.js +2 -0
- package/dist/server/services/quota-fetcher/providers/claude.d.ts +40 -0
- package/dist/server/services/quota-fetcher/providers/claude.js +389 -0
- package/dist/server/services/quota-fetcher/providers/codex.d.ts +23 -0
- package/dist/server/services/quota-fetcher/providers/codex.js +167 -0
- package/dist/server/services/quota-fetcher/providers/copilot.d.ts +21 -0
- package/dist/server/services/quota-fetcher/providers/copilot.js +77 -0
- package/dist/server/services/quota-fetcher/providers/cursor.d.ts +19 -0
- package/dist/server/services/quota-fetcher/providers/cursor.js +185 -0
- package/dist/server/services/quota-fetcher/providers/grok.d.ts +23 -0
- package/dist/server/services/quota-fetcher/providers/grok.js +137 -0
- package/dist/server/services/quota-fetcher/providers/kimi.d.ts +23 -0
- package/dist/server/services/quota-fetcher/providers/kimi.js +260 -0
- package/dist/server/services/quota-fetcher/providers/minimax.d.ts +29 -0
- package/dist/server/services/quota-fetcher/providers/minimax.js +227 -0
- package/dist/server/services/quota-fetcher/providers/zai.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/zai.js +58 -0
- package/dist/server/services/quota-fetcher/service.d.ts +34 -0
- package/dist/server/services/quota-fetcher/service.js +74 -0
- package/dist/server/services/quota-fetcher/usage.d.ts +39 -0
- package/dist/server/services/quota-fetcher/usage.js +78 -0
- package/dist/server/skills/paseo/SKILL.md +129 -0
- package/dist/server/skills/paseo-advisor/SKILL.md +63 -0
- package/dist/server/skills/paseo-committee/SKILL.md +46 -0
- package/dist/server/skills/paseo-handoff/SKILL.md +65 -0
- package/dist/server/skills/paseo-help/SKILL.md +92 -0
- package/dist/server/skills/paseo-plugin/SKILL.md +639 -0
- package/dist/server/terminal/activity/terminal-activity-tracker.d.ts +21 -0
- package/dist/server/terminal/activity/terminal-activity-tracker.js +65 -0
- package/dist/server/terminal/agent-hooks/agent-hook-installer.d.ts +62 -0
- package/dist/server/terminal/agent-hooks/agent-hook-installer.js +117 -0
- package/dist/server/terminal/agent-hooks/claude/claude-settings.d.ts +7 -0
- package/dist/server/terminal/agent-hooks/claude/claude-settings.js +88 -0
- package/dist/server/terminal/agent-hooks/claude/claude.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/claude/claude.js +47 -0
- package/dist/server/terminal/agent-hooks/codex/codex-settings.d.ts +7 -0
- package/dist/server/terminal/agent-hooks/codex/codex-settings.js +99 -0
- package/dist/server/terminal/agent-hooks/codex/codex.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/codex/codex.js +30 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.js +81 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode.d.ts +3 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode.js +23 -0
- package/dist/server/terminal/agent-hooks/provider-registry.d.ts +24 -0
- package/dist/server/terminal/agent-hooks/provider-registry.js +36 -0
- package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.d.ts +10 -0
- package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.js +26 -0
- package/dist/server/terminal/shell-integration/zsh/.zshenv +17 -0
- package/dist/server/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
- package/dist/server/terminal/terminal-capture.d.ts +12 -0
- package/dist/server/terminal/terminal-capture.js +43 -0
- package/dist/server/terminal/terminal-manager-factory.d.ts +6 -0
- package/dist/server/terminal/terminal-manager-factory.js +5 -0
- package/dist/server/terminal/terminal-manager.d.ts +80 -0
- package/dist/server/terminal/terminal-manager.js +321 -0
- package/dist/server/terminal/terminal-output-coalescer.d.ts +34 -0
- package/dist/server/terminal/terminal-output-coalescer.js +73 -0
- package/dist/server/terminal/terminal-restore.d.ts +18 -0
- package/dist/server/terminal/terminal-restore.js +52 -0
- package/dist/server/terminal/terminal-session-controller.d.ts +90 -0
- package/dist/server/terminal/terminal-session-controller.js +849 -0
- package/dist/server/terminal/terminal-size-ownership.d.ts +9 -0
- package/dist/server/terminal/terminal-size-ownership.js +23 -0
- package/dist/server/terminal/terminal-ts-loader.mjs +20 -0
- package/dist/server/terminal/terminal-worker-process.d.ts +2 -0
- package/dist/server/terminal/terminal-worker-process.js +308 -0
- package/dist/server/terminal/terminal-worker-protocol.d.ts +121 -0
- package/dist/server/terminal/terminal-worker-protocol.js +2 -0
- package/dist/server/terminal/terminal.d.ts +151 -0
- package/dist/server/terminal/terminal.js +1234 -0
- package/dist/server/terminal/worker-terminal-manager.d.ts +20 -0
- package/dist/server/terminal/worker-terminal-manager.js +688 -0
- package/dist/server/utils/build-metadata-prompt.d.ts +19 -0
- package/dist/server/utils/build-metadata-prompt.js +29 -0
- package/dist/server/utils/checkout-git.d.ts +317 -0
- package/dist/server/utils/checkout-git.js +2886 -0
- package/dist/server/utils/directory-suggestions.d.ts +29 -0
- package/dist/server/utils/directory-suggestions.js +596 -0
- package/dist/server/utils/git-command-runtime-metrics.d.ts +67 -0
- package/dist/server/utils/git-command-runtime-metrics.js +126 -0
- package/dist/server/utils/git-command-trace.d.ts +29 -0
- package/dist/server/utils/git-command-trace.js +74 -0
- package/dist/server/utils/git-process-scheduler.d.ts +34 -0
- package/dist/server/utils/git-process-scheduler.js +112 -0
- package/dist/server/utils/git-rev-parse-path.d.ts +3 -0
- package/dist/server/utils/git-rev-parse-path.js +21 -0
- package/dist/server/utils/paseo-config-file.d.ts +30 -0
- package/dist/server/utils/paseo-config-file.js +90 -0
- package/dist/server/utils/path.d.ts +27 -0
- package/dist/server/utils/path.js +156 -0
- package/dist/server/utils/project-custom-icon.d.ts +40 -0
- package/dist/server/utils/project-custom-icon.js +144 -0
- package/dist/server/utils/project-icon.d.ts +54 -0
- package/dist/server/utils/project-icon.js +445 -0
- package/dist/server/utils/promise-timeout.d.ts +9 -0
- package/dist/server/utils/promise-timeout.js +25 -0
- package/dist/server/utils/run-git-command.d.ts +59 -0
- package/dist/server/utils/run-git-command.js +393 -0
- package/dist/server/utils/spawn.d.ts +26 -0
- package/dist/server/utils/spawn.js +60 -0
- package/dist/server/utils/ssh-hostname.d.ts +8 -0
- package/dist/server/utils/ssh-hostname.js +60 -0
- package/dist/server/utils/string-command-shell.d.ts +13 -0
- package/dist/server/utils/string-command-shell.js +37 -0
- package/dist/server/utils/tree-kill.d.ts +19 -0
- package/dist/server/utils/tree-kill.js +77 -0
- package/dist/server/utils/windows-command.d.ts +15 -0
- package/dist/server/utils/windows-command.js +41 -0
- package/dist/server/utils/worktree-metadata.d.ts +77 -0
- package/dist/server/utils/worktree-metadata.js +280 -0
- package/dist/server/utils/worktree.d.ts +232 -0
- package/dist/server/utils/worktree.js +1193 -0
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css +1 -0
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.br +0 -0
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-29f33408b1a3c5b435c5202d62a36719.js +1 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-29f33408b1a3c5b435c5202d62a36719.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-29f33408b1a3c5b435c5202d62a36719.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-41547ee520dfff946caa727824742a5c.js +1 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-41547ee520dfff946caa727824742a5c.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-41547ee520dfff946caa727824742a5c.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-03b99be4265ac4a2bc5a6d602fd59cc4.js +16819 -0
- package/dist/server/web-ui/_expo/static/js/web/index-03b99be4265ac4a2bc5a6d602fd59cc4.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-03b99be4265ac4a2bc5a6d602fd59cc4.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-5444610146b0794f1e7d9edad4522d41.js +1 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-5444610146b0794f1e7d9edad4522d41.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-5444610146b0794f1e7d9edad4522d41.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-88b083190031a44788be59103814f511.js +3 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-88b083190031a44788be59103814f511.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-88b083190031a44788be59103814f511.js.gz +0 -0
- package/dist/server/web-ui/apple-touch-icon.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon-mask.0a328cd9c1afd0afe8e3b1ec5165b1b4.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon.35ba0eaec5a4f5ed12ca16fabeae451d.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@2x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@3x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@4x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@2x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@3x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@4x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/search-icon.286d67d3f74808a60a78d3ebf1a5fb57.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/arrow_down.017bc6ba3fc25503e5eb5e53826d48a8.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/error.d1ea1496f9057eb392d5bbf3732a61b7.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/file.19eeb73b9593a38f8e9f418337fc7d10.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/forward.d8b800c443b8972542883e0b9de2bdc6.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/pkg.ab19f4cbc543357183a20571f68380a3.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/sitemap.412dd9275b6b48ad28f5e3d81bb1f626.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/unmatched.20e71bdf79e3a97bf55fd9e164041578.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
- package/dist/server/web-ui/assets/assets/images/notification-icon.3bf81d33ddbf380606bdd248ba83e158.png +0 -0
- package/dist/server/web-ui/favicon.ico +0 -0
- package/dist/server/web-ui/index.html +90 -0
- package/dist/server/web-ui/index.html.br +0 -0
- package/dist/server/web-ui/index.html.gz +0 -0
- package/dist/server/web-ui/manifest.json +27 -0
- package/dist/server/web-ui/manifest.json.br +0 -0
- package/dist/server/web-ui/manifest.json.gz +0 -0
- package/dist/server/web-ui/metadata.json +1 -0
- package/dist/server/web-ui/metadata.json.br +1 -0
- package/dist/server/web-ui/metadata.json.gz +0 -0
- package/dist/server/web-ui/pwa-icon-192.png +0 -0
- package/dist/server/web-ui/pwa-icon-512.png +0 -0
- package/dist/server/web-ui/robots.txt +2 -0
- package/dist/src/executable-resolution/executable-resolution.js +121 -0
- package/dist/src/executable-resolution/windows.js +65 -0
- package/dist/src/server/agent/provider-launch-config.js +167 -0
- package/dist/src/server/paseo-env.js +54 -0
- package/dist/src/server/paseo-home.js +19 -0
- package/dist/src/server/persisted-config.js +411 -0
- package/dist/src/server/pid-lock.js +285 -0
- package/dist/src/server/private-files.js +37 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-runtime-env.js +84 -0
- package/dist/src/terminal/shell-integration/zsh/.zshenv +17 -0
- package/dist/src/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
- package/dist/src/utils/spawn.js +60 -0
- package/dist/src/utils/tree-kill.js +77 -0
- package/dist/src/utils/windows-command.js +41 -0
- package/package.json +125 -0
|
@@ -0,0 +1,2886 @@
|
|
|
1
|
+
import { resolve, dirname, basename } from "path";
|
|
2
|
+
import { existsSync, realpathSync } from "fs";
|
|
3
|
+
import { open as openFile, readFile, stat as statFile } from "fs/promises";
|
|
4
|
+
import { TTLCache } from "@isaacs/ttlcache";
|
|
5
|
+
import { parseGitHubRemoteIdentity, parseGitRemoteLocation } from "@camerontaylor/paseo-protocol/git-remote";
|
|
6
|
+
import { maxBase64EncryptedPlaintextByteLength } from "@camerontaylor/paseo-relay";
|
|
7
|
+
import { highlightDiffWithFileContent, parseAndHighlightDiff, parseDiff, } from "../server/utils/diff-highlighter.js";
|
|
8
|
+
import { parseGitHubRepoFromRemote } from "../server/workspace-git-metadata.js";
|
|
9
|
+
import { createGitHubService } from "../services/github-service.js";
|
|
10
|
+
import { ForgeAuthenticationError, ForgeCliMissingError, ForgeCommandError, } from "../services/forge-cli-command.js";
|
|
11
|
+
import { parseGitRevParsePath, resolveGitRevParsePath } from "./git-rev-parse-path.js";
|
|
12
|
+
import { runGitCommand } from "./run-git-command.js";
|
|
13
|
+
import { isPaseoOwnedWorktreeCwd, resolvePaseoWorktreesBaseRoot } from "./worktree.js";
|
|
14
|
+
import { branchNameFromRef, getPaseoWorktreeChangeRequestHintForBranch, readPaseoWorktreeMetadata, rebindPaseoWorktreeChangeRequestHint, } from "./worktree-metadata.js";
|
|
15
|
+
const READ_ONLY_GIT_ENV = {
|
|
16
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
17
|
+
LC_ALL: "C",
|
|
18
|
+
};
|
|
19
|
+
const DISCARD_CHANGES_TIMEOUT_MS = 120000;
|
|
20
|
+
const DEFAULT_PULL_REQUEST_STATUS_CACHE_TTL_MS = 30000;
|
|
21
|
+
const PULL_REQUEST_STATUS_CACHE_MAX = 1000;
|
|
22
|
+
const DEFAULT_SHORTSTAT_CACHE_TTL_MS = 15000;
|
|
23
|
+
const SHORTSTAT_CACHE_MAX = 1000;
|
|
24
|
+
let pullRequestStatusCacheTtlMs = DEFAULT_PULL_REQUEST_STATUS_CACHE_TTL_MS;
|
|
25
|
+
let pullRequestStatusCache = createPullRequestStatusCache(pullRequestStatusCacheTtlMs);
|
|
26
|
+
const pullRequestStatusInFlight = new Map();
|
|
27
|
+
const lastSuccessfulPullRequestStatus = new Map();
|
|
28
|
+
let shortstatCacheTtlMs = DEFAULT_SHORTSTAT_CACHE_TTL_MS;
|
|
29
|
+
let shortstatCache = createShortstatCache(shortstatCacheTtlMs);
|
|
30
|
+
const shortstatInFlight = new Map();
|
|
31
|
+
function getErrorStderr(error) {
|
|
32
|
+
return "stderr" in error && typeof error.stderr === "string" ? error.stderr : "";
|
|
33
|
+
}
|
|
34
|
+
function getErrorStdout(error) {
|
|
35
|
+
return "stdout" in error && typeof error.stdout === "string" ? error.stdout : "";
|
|
36
|
+
}
|
|
37
|
+
function throwBranchNotFound(branch) {
|
|
38
|
+
throw new Error(`Branch not found: ${branch ?? "unknown"}`);
|
|
39
|
+
}
|
|
40
|
+
function createPullRequestStatusCache(ttlMs) {
|
|
41
|
+
return new TTLCache({
|
|
42
|
+
ttl: ttlMs,
|
|
43
|
+
max: PULL_REQUEST_STATUS_CACHE_MAX,
|
|
44
|
+
checkAgeOnGet: true,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function createShortstatCache(ttlMs) {
|
|
48
|
+
return new TTLCache({
|
|
49
|
+
ttl: ttlMs,
|
|
50
|
+
max: SHORTSTAT_CACHE_MAX,
|
|
51
|
+
checkAgeOnGet: true,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function getPullRequestStatusCacheKey(cwd, headSha) {
|
|
55
|
+
return `${resolve(cwd)}\u0000${headSha ?? ""}`;
|
|
56
|
+
}
|
|
57
|
+
function rememberPullRequestStatus(cacheKey, status) {
|
|
58
|
+
lastSuccessfulPullRequestStatus.set(cacheKey, status);
|
|
59
|
+
if (lastSuccessfulPullRequestStatus.size <= PULL_REQUEST_STATUS_CACHE_MAX) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const oldest = lastSuccessfulPullRequestStatus.keys().next();
|
|
63
|
+
if (!oldest.done) {
|
|
64
|
+
lastSuccessfulPullRequestStatus.delete(oldest.value);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function getShortstatCacheKey(cwd) {
|
|
68
|
+
return resolve(cwd);
|
|
69
|
+
}
|
|
70
|
+
export function __resetPullRequestStatusCacheForTests() {
|
|
71
|
+
pullRequestStatusCache.clear();
|
|
72
|
+
pullRequestStatusCache.cancelTimer();
|
|
73
|
+
pullRequestStatusCacheTtlMs = DEFAULT_PULL_REQUEST_STATUS_CACHE_TTL_MS;
|
|
74
|
+
pullRequestStatusCache = createPullRequestStatusCache(pullRequestStatusCacheTtlMs);
|
|
75
|
+
pullRequestStatusInFlight.clear();
|
|
76
|
+
lastSuccessfulPullRequestStatus.clear();
|
|
77
|
+
}
|
|
78
|
+
export function __setPullRequestStatusCacheTtlForTests(ttlMs) {
|
|
79
|
+
pullRequestStatusCache.clear();
|
|
80
|
+
pullRequestStatusCache.cancelTimer();
|
|
81
|
+
pullRequestStatusCacheTtlMs = ttlMs;
|
|
82
|
+
pullRequestStatusCache = createPullRequestStatusCache(ttlMs);
|
|
83
|
+
pullRequestStatusInFlight.clear();
|
|
84
|
+
lastSuccessfulPullRequestStatus.clear();
|
|
85
|
+
}
|
|
86
|
+
export function __resetCheckoutShortstatCacheForTests() {
|
|
87
|
+
shortstatCache.clear();
|
|
88
|
+
shortstatCache.cancelTimer();
|
|
89
|
+
shortstatCacheTtlMs = DEFAULT_SHORTSTAT_CACHE_TTL_MS;
|
|
90
|
+
shortstatCache = createShortstatCache(shortstatCacheTtlMs);
|
|
91
|
+
shortstatInFlight.clear();
|
|
92
|
+
}
|
|
93
|
+
export function __setCheckoutShortstatCacheTtlForTests(ttlMs) {
|
|
94
|
+
shortstatCache.clear();
|
|
95
|
+
shortstatCache.cancelTimer();
|
|
96
|
+
shortstatCacheTtlMs = ttlMs;
|
|
97
|
+
shortstatCache = createShortstatCache(ttlMs);
|
|
98
|
+
shortstatInFlight.clear();
|
|
99
|
+
}
|
|
100
|
+
function getCheckoutDiffRefArgs(refs) {
|
|
101
|
+
return [refs.baseRef, ...(refs.targetRef ? [refs.targetRef] : [])];
|
|
102
|
+
}
|
|
103
|
+
function normalizeBranchSuggestionName(raw) {
|
|
104
|
+
const trimmed = raw.trim();
|
|
105
|
+
if (!trimmed) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
let normalized = trimmed;
|
|
109
|
+
if (normalized.startsWith("refs/heads/")) {
|
|
110
|
+
normalized = normalized.slice("refs/heads/".length);
|
|
111
|
+
}
|
|
112
|
+
else if (normalized.startsWith("refs/remotes/")) {
|
|
113
|
+
normalized = normalized.slice("refs/remotes/".length);
|
|
114
|
+
}
|
|
115
|
+
if (normalized.startsWith("origin/")) {
|
|
116
|
+
normalized = normalized.slice("origin/".length);
|
|
117
|
+
}
|
|
118
|
+
if (!normalized || normalized === "HEAD" || normalized === "origin") {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
return normalized;
|
|
122
|
+
}
|
|
123
|
+
async function listGitRefs(cwd, refPrefix) {
|
|
124
|
+
const { stdout } = await runGitCommand([
|
|
125
|
+
"for-each-ref",
|
|
126
|
+
"--sort=-committerdate",
|
|
127
|
+
"--format=%(refname)%09%(committerdate:unix)%09%(objectname)",
|
|
128
|
+
refPrefix,
|
|
129
|
+
], { cwd, envOverlay: READ_ONLY_GIT_ENV });
|
|
130
|
+
return stdout
|
|
131
|
+
.split("\n")
|
|
132
|
+
.map((line) => {
|
|
133
|
+
const trimmed = line.trim();
|
|
134
|
+
if (!trimmed)
|
|
135
|
+
return null;
|
|
136
|
+
const [name, dateStr, oid] = trimmed.split("\t");
|
|
137
|
+
if (!name || !oid)
|
|
138
|
+
return null;
|
|
139
|
+
return { name, committerDate: Number(dateStr) || 0, oid };
|
|
140
|
+
})
|
|
141
|
+
.filter((ref) => ref !== null);
|
|
142
|
+
}
|
|
143
|
+
function sortBranchSuggestions(branchNames, branchMeta, query) {
|
|
144
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
145
|
+
const hasQuery = normalizedQuery.length > 0;
|
|
146
|
+
return branchNames.sort((a, b) => {
|
|
147
|
+
if (hasQuery) {
|
|
148
|
+
const aPrefix = a.toLowerCase().startsWith(normalizedQuery);
|
|
149
|
+
const bPrefix = b.toLowerCase().startsWith(normalizedQuery);
|
|
150
|
+
if (aPrefix !== bPrefix) {
|
|
151
|
+
return aPrefix ? -1 : 1;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const aMeta = branchMeta.get(a);
|
|
155
|
+
const bMeta = branchMeta.get(b);
|
|
156
|
+
const aDate = aMeta?.committerDate ?? 0;
|
|
157
|
+
const bDate = bMeta?.committerDate ?? 0;
|
|
158
|
+
if (aDate !== bDate) {
|
|
159
|
+
return bDate - aDate;
|
|
160
|
+
}
|
|
161
|
+
return a.localeCompare(b);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
export async function listBranchSuggestions(cwd, options) {
|
|
165
|
+
await requireGitRepo(cwd);
|
|
166
|
+
const requestedLimit = options?.limit ?? 50;
|
|
167
|
+
const limit = Math.max(1, Math.min(200, requestedLimit));
|
|
168
|
+
const rawQuery = options?.query?.trim().toLowerCase() ?? "";
|
|
169
|
+
const query = normalizeBranchSuggestionName(rawQuery) ?? rawQuery;
|
|
170
|
+
const [localRefs, remoteRefs] = await Promise.all([
|
|
171
|
+
listGitRefs(cwd, "refs/heads"),
|
|
172
|
+
listGitRefs(cwd, "refs/remotes/origin"),
|
|
173
|
+
]);
|
|
174
|
+
const branchMeta = new Map();
|
|
175
|
+
for (const ref of localRefs) {
|
|
176
|
+
const normalized = normalizeBranchSuggestionName(ref.name);
|
|
177
|
+
if (!normalized)
|
|
178
|
+
continue;
|
|
179
|
+
const existing = branchMeta.get(normalized);
|
|
180
|
+
branchMeta.set(normalized, {
|
|
181
|
+
hasLocal: true,
|
|
182
|
+
hasRemote: existing?.hasRemote ?? false,
|
|
183
|
+
localOid: ref.oid,
|
|
184
|
+
...(existing?.remoteOid ? { remoteOid: existing.remoteOid } : {}),
|
|
185
|
+
committerDate: Math.max(ref.committerDate, existing?.committerDate ?? 0),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
for (const ref of remoteRefs) {
|
|
189
|
+
const normalized = normalizeBranchSuggestionName(ref.name);
|
|
190
|
+
if (!normalized)
|
|
191
|
+
continue;
|
|
192
|
+
const existing = branchMeta.get(normalized);
|
|
193
|
+
if (!existing) {
|
|
194
|
+
branchMeta.set(normalized, {
|
|
195
|
+
hasLocal: false,
|
|
196
|
+
hasRemote: true,
|
|
197
|
+
remoteOid: ref.oid,
|
|
198
|
+
committerDate: ref.committerDate,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
branchMeta.set(normalized, {
|
|
203
|
+
...existing,
|
|
204
|
+
hasRemote: true,
|
|
205
|
+
remoteOid: ref.oid,
|
|
206
|
+
committerDate: Math.max(ref.committerDate, existing.committerDate),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const filteredNames = Array.from(branchMeta.keys()).filter((name) => query ? name.toLowerCase().includes(query) : true);
|
|
211
|
+
if (filteredNames.length === 0) {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
const ordered = sortBranchSuggestions(filteredNames, branchMeta, query);
|
|
215
|
+
return Promise.all(ordered.slice(0, limit).map(async (name) => {
|
|
216
|
+
const meta = branchMeta.get(name);
|
|
217
|
+
const suggestion = {
|
|
218
|
+
name,
|
|
219
|
+
committerDate: meta?.committerDate ?? 0,
|
|
220
|
+
hasLocal: meta?.hasLocal ?? false,
|
|
221
|
+
hasRemote: meta?.hasRemote ?? false,
|
|
222
|
+
};
|
|
223
|
+
if (!suggestion.hasLocal || !suggestion.hasRemote) {
|
|
224
|
+
return suggestion;
|
|
225
|
+
}
|
|
226
|
+
if (meta?.localOid && meta.localOid === meta.remoteOid) {
|
|
227
|
+
suggestion.localAhead = 0;
|
|
228
|
+
suggestion.localBehind = 0;
|
|
229
|
+
return suggestion;
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
const { stdout } = await runGitCommand([
|
|
233
|
+
"rev-list",
|
|
234
|
+
"--left-right",
|
|
235
|
+
"--count",
|
|
236
|
+
`refs/heads/${name}...refs/remotes/origin/${name}`,
|
|
237
|
+
], { cwd, envOverlay: READ_ONLY_GIT_ENV });
|
|
238
|
+
const [localAhead, localBehind] = stdout.trim().split(/\s+/).map(Number);
|
|
239
|
+
if (Number.isFinite(localAhead) && Number.isFinite(localBehind)) {
|
|
240
|
+
suggestion.localAhead = localAhead;
|
|
241
|
+
suggestion.localBehind = localBehind;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
// A ref may disappear between listing and comparison. Keep the branch
|
|
246
|
+
// available without divergence metadata and let creation re-resolve it.
|
|
247
|
+
}
|
|
248
|
+
return suggestion;
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
export async function resolveBranchCheckout(cwd, name) {
|
|
252
|
+
await requireGitRepo(cwd);
|
|
253
|
+
const normalized = normalizeBranchSuggestionName(name);
|
|
254
|
+
if (!normalized) {
|
|
255
|
+
return { kind: "not-found" };
|
|
256
|
+
}
|
|
257
|
+
const localRef = `refs/heads/${normalized}`;
|
|
258
|
+
const localResult = await runGitCommand(["rev-parse", "--verify", "--quiet", localRef], {
|
|
259
|
+
cwd,
|
|
260
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
261
|
+
acceptExitCodes: [0, 1],
|
|
262
|
+
});
|
|
263
|
+
const hasLocal = localResult.exitCode === 0;
|
|
264
|
+
if (hasLocal) {
|
|
265
|
+
return { kind: "local", name: normalized };
|
|
266
|
+
}
|
|
267
|
+
const remoteRef = `origin/${normalized}`;
|
|
268
|
+
const remoteRefPath = `refs/remotes/${remoteRef}`;
|
|
269
|
+
const remoteResult = await runGitCommand(["rev-parse", "--verify", "--quiet", remoteRefPath], {
|
|
270
|
+
cwd,
|
|
271
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
272
|
+
acceptExitCodes: [0, 1],
|
|
273
|
+
});
|
|
274
|
+
const hasRemote = remoteResult.exitCode === 0;
|
|
275
|
+
if (hasRemote) {
|
|
276
|
+
return { kind: "remote-only", name: normalized, remoteRef };
|
|
277
|
+
}
|
|
278
|
+
return { kind: "not-found" };
|
|
279
|
+
}
|
|
280
|
+
export async function checkoutResolvedBranch(input) {
|
|
281
|
+
const { cwd, resolution } = input;
|
|
282
|
+
switch (resolution.kind) {
|
|
283
|
+
case "local": {
|
|
284
|
+
const { stdout } = await runGitCommand(["rev-parse", "--abbrev-ref", "HEAD"], { cwd });
|
|
285
|
+
const current = stdout.trim();
|
|
286
|
+
if (current === resolution.name) {
|
|
287
|
+
return { source: "local" };
|
|
288
|
+
}
|
|
289
|
+
await runGitCommand(["checkout", resolution.name], { cwd });
|
|
290
|
+
return { source: "local" };
|
|
291
|
+
}
|
|
292
|
+
case "remote-only":
|
|
293
|
+
await runGitCommand(["checkout", "-b", resolution.name, "--track", resolution.remoteRef], {
|
|
294
|
+
cwd,
|
|
295
|
+
});
|
|
296
|
+
return { source: "remote" };
|
|
297
|
+
default:
|
|
298
|
+
return throwBranchNotFound(input.requestedBranch);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async function listCheckoutFileChanges(cwd, refs, ignoreWhitespace = false) {
|
|
302
|
+
const changes = [];
|
|
303
|
+
const { stdout: nameStatusOut } = await runGitCommand(buildGitDiffArgs({
|
|
304
|
+
ignoreWhitespace,
|
|
305
|
+
extra: ["--name-status", ...getCheckoutDiffRefArgs(refs)],
|
|
306
|
+
}), { cwd, envOverlay: READ_ONLY_GIT_ENV });
|
|
307
|
+
for (const line of nameStatusOut
|
|
308
|
+
.split("\n")
|
|
309
|
+
.map((l) => l.trim())
|
|
310
|
+
.filter(Boolean)) {
|
|
311
|
+
// `--name-status` uses TAB separators, which preserves filenames with spaces.
|
|
312
|
+
const tabParts = line.split("\t");
|
|
313
|
+
const rawStatus = (tabParts[0] ?? "").trim();
|
|
314
|
+
if (!rawStatus)
|
|
315
|
+
continue;
|
|
316
|
+
if (rawStatus.startsWith("R") || rawStatus.startsWith("C")) {
|
|
317
|
+
const oldPath = tabParts[1];
|
|
318
|
+
const newPath = tabParts[2];
|
|
319
|
+
if (newPath) {
|
|
320
|
+
changes.push({
|
|
321
|
+
path: newPath,
|
|
322
|
+
...(oldPath ? { oldPath } : {}),
|
|
323
|
+
status: rawStatus,
|
|
324
|
+
isNew: false,
|
|
325
|
+
isDeleted: false,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const path = tabParts[1];
|
|
331
|
+
if (!path)
|
|
332
|
+
continue;
|
|
333
|
+
const code = rawStatus[0];
|
|
334
|
+
changes.push({
|
|
335
|
+
path,
|
|
336
|
+
status: rawStatus,
|
|
337
|
+
isNew: code === "A",
|
|
338
|
+
isDeleted: code === "D",
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
if (refs.includeUntracked) {
|
|
342
|
+
const { stdout: untrackedOut } = await runGitCommand(["ls-files", "--others", "--exclude-standard"], {
|
|
343
|
+
cwd,
|
|
344
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
345
|
+
});
|
|
346
|
+
for (const file of untrackedOut
|
|
347
|
+
.split("\n")
|
|
348
|
+
.map((l) => l.trim())
|
|
349
|
+
.filter(Boolean)) {
|
|
350
|
+
changes.push({
|
|
351
|
+
path: file,
|
|
352
|
+
status: "U",
|
|
353
|
+
isNew: true,
|
|
354
|
+
isDeleted: false,
|
|
355
|
+
isUntracked: true,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// Deduplicate by path (prefer tracked status over untracked marker if both appear).
|
|
360
|
+
const byPath = new Map();
|
|
361
|
+
for (const change of changes) {
|
|
362
|
+
const existing = byPath.get(change.path);
|
|
363
|
+
if (!existing) {
|
|
364
|
+
byPath.set(change.path, change);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (existing.isUntracked && !change.isUntracked) {
|
|
368
|
+
byPath.set(change.path, change);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return Array.from(byPath.values());
|
|
372
|
+
}
|
|
373
|
+
async function readGitFileContentAtRef(cwd, ref, path) {
|
|
374
|
+
try {
|
|
375
|
+
const { stdout } = await runGitCommand(["show", `${ref}:${path}`], {
|
|
376
|
+
cwd,
|
|
377
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
378
|
+
});
|
|
379
|
+
return stdout;
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
async function tryResolveMergeBase(cwd, baseRef) {
|
|
386
|
+
try {
|
|
387
|
+
const { stdout } = await runGitCommand(["merge-base", baseRef, "HEAD"], {
|
|
388
|
+
cwd,
|
|
389
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
390
|
+
});
|
|
391
|
+
const sha = stdout.trim();
|
|
392
|
+
return sha.length > 0 ? sha : null;
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function normalizeNumstatPath(pathField) {
|
|
399
|
+
const braceRenameMatch = pathField.match(/^(.*)\{(.*) => (.*)\}(.*)$/);
|
|
400
|
+
if (braceRenameMatch) {
|
|
401
|
+
const [, prefix, , renamed, suffix] = braceRenameMatch;
|
|
402
|
+
return `${prefix}${renamed}${suffix}`;
|
|
403
|
+
}
|
|
404
|
+
const inlineRenameMatch = pathField.match(/^(.*) => (.*)$/);
|
|
405
|
+
if (inlineRenameMatch) {
|
|
406
|
+
return inlineRenameMatch[2] ?? pathField;
|
|
407
|
+
}
|
|
408
|
+
return pathField;
|
|
409
|
+
}
|
|
410
|
+
function buildGitDiffArgs(args) {
|
|
411
|
+
return ["diff", ...(args.ignoreWhitespace ? ["-w"] : []), ...args.extra];
|
|
412
|
+
}
|
|
413
|
+
const TRACKED_DIFF_NUMSTAT_MAX_BYTES = 2 * 1024 * 1024; // 2MB
|
|
414
|
+
const TRACKED_DIFF_BATCH_SIZE = 8;
|
|
415
|
+
const EMPTY_TREE_OBJECT_ID = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
416
|
+
function isUnbornHeadDiffError(error) {
|
|
417
|
+
return (error instanceof Error &&
|
|
418
|
+
error.message.includes("--name-status HEAD") &&
|
|
419
|
+
error.message.includes("ambiguous argument 'HEAD'"));
|
|
420
|
+
}
|
|
421
|
+
async function getTrackedNumstatByPath(cwd, refs, ignoreWhitespace = false) {
|
|
422
|
+
const result = await runGitCommand(buildGitDiffArgs({
|
|
423
|
+
ignoreWhitespace,
|
|
424
|
+
extra: ["--numstat", ...getCheckoutDiffRefArgs(refs)],
|
|
425
|
+
}), {
|
|
426
|
+
cwd,
|
|
427
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
428
|
+
maxOutputBytes: TRACKED_DIFF_NUMSTAT_MAX_BYTES,
|
|
429
|
+
acceptExitCodes: [0],
|
|
430
|
+
});
|
|
431
|
+
const stats = new Map();
|
|
432
|
+
const lines = result.stdout
|
|
433
|
+
.split("\n")
|
|
434
|
+
.map((line) => line.trim())
|
|
435
|
+
.filter(Boolean);
|
|
436
|
+
for (const line of lines) {
|
|
437
|
+
const parts = line.split("\t");
|
|
438
|
+
if (parts.length < 3) {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const additionsField = parts[0] ?? "";
|
|
442
|
+
const deletionsField = parts[1] ?? "";
|
|
443
|
+
const rawPath = parts.slice(2).join("\t");
|
|
444
|
+
const path = normalizeNumstatPath(rawPath);
|
|
445
|
+
if (!path) {
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
if (additionsField === "-" || deletionsField === "-") {
|
|
449
|
+
stats.set(path, { additions: 0, deletions: 0, isBinary: true });
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
const additions = Number.parseInt(additionsField, 10);
|
|
453
|
+
const deletions = Number.parseInt(deletionsField, 10);
|
|
454
|
+
if (Number.isNaN(additions) || Number.isNaN(deletions)) {
|
|
455
|
+
stats.set(path, null);
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
stats.set(path, { additions, deletions, isBinary: false });
|
|
459
|
+
}
|
|
460
|
+
return stats;
|
|
461
|
+
}
|
|
462
|
+
async function getTrackedDiffTextForPath(input) {
|
|
463
|
+
const result = await runGitCommand(buildGitDiffArgs({
|
|
464
|
+
ignoreWhitespace: input.ignoreWhitespace,
|
|
465
|
+
extra: [...getCheckoutDiffRefArgs(input.refsForDiff), "--", input.path],
|
|
466
|
+
}), {
|
|
467
|
+
cwd: input.cwd,
|
|
468
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
469
|
+
maxOutputBytes: PER_FILE_DIFF_MAX_BYTES,
|
|
470
|
+
});
|
|
471
|
+
return {
|
|
472
|
+
path: input.path,
|
|
473
|
+
text: result.stdout,
|
|
474
|
+
truncated: result.truncated,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
export class NotGitRepoError extends Error {
|
|
478
|
+
constructor(cwd) {
|
|
479
|
+
super(`Not a git repository: ${cwd}`);
|
|
480
|
+
this.code = "NOT_GIT_REPO";
|
|
481
|
+
this.name = "NotGitRepoError";
|
|
482
|
+
this.cwd = cwd;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
export class MergeConflictError extends Error {
|
|
486
|
+
constructor(options) {
|
|
487
|
+
super(`Merge conflict while merging ${options.currentBranch} into ${options.baseRef}`);
|
|
488
|
+
this.name = "MergeConflictError";
|
|
489
|
+
this.baseRef = options.baseRef;
|
|
490
|
+
this.currentBranch = options.currentBranch;
|
|
491
|
+
this.conflictFiles = options.conflictFiles;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
export class MergeFromBaseConflictError extends Error {
|
|
495
|
+
constructor(options) {
|
|
496
|
+
super(`Merge conflict while merging ${options.baseRef} into ${options.currentBranch}. Please merge manually.`);
|
|
497
|
+
this.name = "MergeFromBaseConflictError";
|
|
498
|
+
this.baseRef = options.baseRef;
|
|
499
|
+
this.currentBranch = options.currentBranch;
|
|
500
|
+
this.conflictFiles = options.conflictFiles;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
function isNotGitRepositoryError(error) {
|
|
504
|
+
return error instanceof Error && /not a git repository/i.test(error.message);
|
|
505
|
+
}
|
|
506
|
+
function getRunGitCommand(context) {
|
|
507
|
+
return context?.runGitCommand ?? runGitCommand;
|
|
508
|
+
}
|
|
509
|
+
async function requireGitRepo(cwd, context) {
|
|
510
|
+
try {
|
|
511
|
+
await getRunGitCommand(context)(["rev-parse", "--git-dir"], {
|
|
512
|
+
cwd,
|
|
513
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
catch {
|
|
517
|
+
throw new NotGitRepoError(cwd);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
async function requireGitWorktreeRoot(cwd) {
|
|
521
|
+
try {
|
|
522
|
+
const { stdout } = await runGitCommand(["rev-parse", "--show-toplevel"], {
|
|
523
|
+
cwd,
|
|
524
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
525
|
+
});
|
|
526
|
+
const worktreeRoot = parseGitRevParsePath(stdout);
|
|
527
|
+
if (!worktreeRoot) {
|
|
528
|
+
throw new Error("Git returned no worktree root");
|
|
529
|
+
}
|
|
530
|
+
return worktreeRoot;
|
|
531
|
+
}
|
|
532
|
+
catch {
|
|
533
|
+
throw new NotGitRepoError(cwd);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
export async function getCurrentBranch(cwd, context) {
|
|
537
|
+
try {
|
|
538
|
+
const { stdout } = await getRunGitCommand(context)(["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
539
|
+
cwd,
|
|
540
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
541
|
+
});
|
|
542
|
+
const branch = stdout.trim();
|
|
543
|
+
if (branch === "HEAD") {
|
|
544
|
+
return await getRebaseHeadBranch(cwd, context);
|
|
545
|
+
}
|
|
546
|
+
return branch.length > 0 ? branch : null;
|
|
547
|
+
}
|
|
548
|
+
catch {
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
async function getCurrentHeadSha(cwd, context) {
|
|
553
|
+
const knownSha = context?.facts?.isGit
|
|
554
|
+
? context.facts.pullRequestLookupTarget?.headSha
|
|
555
|
+
: undefined;
|
|
556
|
+
if (knownSha) {
|
|
557
|
+
return knownSha;
|
|
558
|
+
}
|
|
559
|
+
try {
|
|
560
|
+
const { stdout } = await getRunGitCommand(context)(["rev-parse", "HEAD"], {
|
|
561
|
+
cwd,
|
|
562
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
563
|
+
logger: context?.logger,
|
|
564
|
+
});
|
|
565
|
+
const sha = stdout.trim();
|
|
566
|
+
return sha.length > 0 ? sha : null;
|
|
567
|
+
}
|
|
568
|
+
catch {
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
async function addHeadShaToPullRequestLookupTarget(cwd, target, context) {
|
|
573
|
+
if (!target) {
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
const headSha = await getCurrentHeadSha(cwd, context);
|
|
577
|
+
return headSha ? { ...target, headSha } : target;
|
|
578
|
+
}
|
|
579
|
+
async function getRebaseHeadBranch(cwd, context) {
|
|
580
|
+
const paths = ["rebase-merge/head-name", "rebase-apply/head-name"];
|
|
581
|
+
const results = await Promise.all(paths.map(async (path) => {
|
|
582
|
+
try {
|
|
583
|
+
const { stdout } = await getRunGitCommand(context)(["rev-parse", "--git-path", path], {
|
|
584
|
+
cwd,
|
|
585
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
586
|
+
});
|
|
587
|
+
const headName = (await readFile(resolve(cwd, stdout.trim()), "utf8")).trim();
|
|
588
|
+
if (headName.startsWith("refs/heads/")) {
|
|
589
|
+
return headName.slice("refs/heads/".length) || null;
|
|
590
|
+
}
|
|
591
|
+
return headName || null;
|
|
592
|
+
}
|
|
593
|
+
catch {
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
}));
|
|
597
|
+
return results.find((result) => result !== null) ?? null;
|
|
598
|
+
}
|
|
599
|
+
async function getWorktreeRoot(cwd, context) {
|
|
600
|
+
try {
|
|
601
|
+
const { stdout } = await getRunGitCommand(context)(["rev-parse", "--show-toplevel"], {
|
|
602
|
+
cwd,
|
|
603
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
604
|
+
logger: context?.logger,
|
|
605
|
+
});
|
|
606
|
+
return parseGitRevParsePath(stdout);
|
|
607
|
+
}
|
|
608
|
+
catch (error) {
|
|
609
|
+
if (!isNotGitRepositoryError(error)) {
|
|
610
|
+
context?.logger?.warn({ err: error, cwd }, "Git worktree discovery failed; treating directory as non-Git");
|
|
611
|
+
}
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
export async function getMainRepoRoot(cwd, context) {
|
|
616
|
+
const { stdout: commonDirOut } = await getRunGitCommand(context)(["rev-parse", "--git-common-dir"], {
|
|
617
|
+
cwd,
|
|
618
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
619
|
+
});
|
|
620
|
+
return getMainRepoRootFromCommonDir(cwd, resolveGitRevParsePath(cwd, commonDirOut), context);
|
|
621
|
+
}
|
|
622
|
+
async function getMainRepoRootFromCommonDir(cwd, commonDir, context) {
|
|
623
|
+
if (!commonDir) {
|
|
624
|
+
throw new Error("Not in a git repository");
|
|
625
|
+
}
|
|
626
|
+
const normalized = realpathSync(commonDir);
|
|
627
|
+
if (basename(normalized) === ".git") {
|
|
628
|
+
return dirname(normalized);
|
|
629
|
+
}
|
|
630
|
+
const { stdout: worktreeOut } = await getRunGitCommand(context)(["worktree", "list", "--porcelain"], {
|
|
631
|
+
cwd,
|
|
632
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
633
|
+
});
|
|
634
|
+
const worktrees = parseWorktreeList(worktreeOut);
|
|
635
|
+
const nonBareNonPaseo = worktrees.filter((wt) => !wt.isBare &&
|
|
636
|
+
!isPaseoWorktreePath(wt.path, {
|
|
637
|
+
paseoHome: context?.paseoHome,
|
|
638
|
+
worktreesRoot: context?.worktreesRoot,
|
|
639
|
+
}));
|
|
640
|
+
const childrenOfBareRepo = nonBareNonPaseo.filter((wt) => isDescendantPath(wt.path, normalized));
|
|
641
|
+
const mainChild = childrenOfBareRepo.find((wt) => basename(wt.path) === "main");
|
|
642
|
+
return mainChild?.path ?? childrenOfBareRepo[0]?.path ?? nonBareNonPaseo[0]?.path ?? normalized;
|
|
643
|
+
}
|
|
644
|
+
/** Check whether a path is under Paseo's worktree root. */
|
|
645
|
+
export function isPaseoWorktreePath(p, options) {
|
|
646
|
+
if (options?.worktreesRoot || options?.paseoHome) {
|
|
647
|
+
return isDescendantPath(p, resolvePaseoWorktreesBaseRoot(options));
|
|
648
|
+
}
|
|
649
|
+
return /[/\\]\.paseo[/\\]worktrees[/\\]/.test(p);
|
|
650
|
+
}
|
|
651
|
+
/** True when `child` is strictly inside `parent` (handles both `/` and `\`). */
|
|
652
|
+
export function isDescendantPath(child, parent) {
|
|
653
|
+
let c = child.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
654
|
+
let p = parent.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
655
|
+
// Case-insensitive on Windows (drive letter like C: or D:)
|
|
656
|
+
if (/^[A-Za-z]:/.test(c) || /^[A-Za-z]:/.test(p)) {
|
|
657
|
+
c = c.toLowerCase();
|
|
658
|
+
p = p.toLowerCase();
|
|
659
|
+
}
|
|
660
|
+
if (!c.startsWith(p))
|
|
661
|
+
return false;
|
|
662
|
+
if (c.length === p.length)
|
|
663
|
+
return false;
|
|
664
|
+
return c[p.length] === "/";
|
|
665
|
+
}
|
|
666
|
+
export function parseWorktreeList(output) {
|
|
667
|
+
const entries = [];
|
|
668
|
+
let current = null;
|
|
669
|
+
for (const line of output.split("\n")) {
|
|
670
|
+
const trimmed = line.trim();
|
|
671
|
+
if (!trimmed) {
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (trimmed.startsWith("worktree ")) {
|
|
675
|
+
if (current) {
|
|
676
|
+
entries.push(current);
|
|
677
|
+
}
|
|
678
|
+
current = { path: trimmed.slice("worktree ".length).trim() };
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
if (current && trimmed.startsWith("branch ")) {
|
|
682
|
+
current.branchRef = trimmed.slice("branch ".length).trim();
|
|
683
|
+
}
|
|
684
|
+
if (current && trimmed === "bare") {
|
|
685
|
+
current.isBare = true;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (current) {
|
|
689
|
+
entries.push(current);
|
|
690
|
+
}
|
|
691
|
+
return entries;
|
|
692
|
+
}
|
|
693
|
+
async function getWorktreePathForBranch(cwd, branchName) {
|
|
694
|
+
try {
|
|
695
|
+
const { stdout } = await runGitCommand(["worktree", "list", "--porcelain"], {
|
|
696
|
+
cwd,
|
|
697
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
698
|
+
});
|
|
699
|
+
const entries = parseWorktreeList(stdout);
|
|
700
|
+
const ref = branchName.startsWith("refs/heads/") ? branchName : `refs/heads/${branchName}`;
|
|
701
|
+
return entries.find((entry) => entry.branchRef === ref)?.path ?? null;
|
|
702
|
+
}
|
|
703
|
+
catch {
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
export async function localBranchExists(cwd, branchName) {
|
|
708
|
+
return doesGitRefExist(cwd, `refs/heads/${branchName}`);
|
|
709
|
+
}
|
|
710
|
+
export async function renameCurrentBranch(cwd, newName) {
|
|
711
|
+
const worktreeRoot = await requireGitWorktreeRoot(cwd);
|
|
712
|
+
const previousBranch = await getCurrentBranch(cwd);
|
|
713
|
+
if (!previousBranch || previousBranch === "HEAD") {
|
|
714
|
+
throw new Error("Cannot rename branch in detached HEAD state");
|
|
715
|
+
}
|
|
716
|
+
await runGitCommand(["branch", "-m", newName], {
|
|
717
|
+
cwd,
|
|
718
|
+
timeout: 120000,
|
|
719
|
+
});
|
|
720
|
+
const currentBranch = await getCurrentBranch(cwd);
|
|
721
|
+
if (currentBranch) {
|
|
722
|
+
rebindPaseoWorktreeChangeRequestHint(worktreeRoot, previousBranch, currentBranch);
|
|
723
|
+
}
|
|
724
|
+
return { previousBranch, currentBranch };
|
|
725
|
+
}
|
|
726
|
+
async function getPaseoWorktreeForCwd(cwd, options = {}) {
|
|
727
|
+
// Fast-path reject: non-worktree paths do not need expensive ownership checks.
|
|
728
|
+
if (!/[\\/]worktrees[\\/]/.test(cwd)) {
|
|
729
|
+
return { isPaseoOwnedWorktree: false };
|
|
730
|
+
}
|
|
731
|
+
const ownership = await isPaseoOwnedWorktreeCwd(cwd, {
|
|
732
|
+
paseoHome: options.context?.paseoHome,
|
|
733
|
+
worktreesRoot: options.context?.worktreesRoot,
|
|
734
|
+
knownGitCommonDir: options.knownGitCommonDir,
|
|
735
|
+
});
|
|
736
|
+
if (!ownership.allowed) {
|
|
737
|
+
return { isPaseoOwnedWorktree: false };
|
|
738
|
+
}
|
|
739
|
+
return {
|
|
740
|
+
isPaseoOwnedWorktree: true,
|
|
741
|
+
worktreeRoot: options.knownWorktreeRoot ?? (await getWorktreeRoot(cwd, options.context)) ?? cwd,
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
// Worktrees created before baseRef existed only stored the stripped name; it resolves
|
|
745
|
+
// local-first, which is the base they were actually cut from.
|
|
746
|
+
function storedBaseRefFromMetadata(metadata) {
|
|
747
|
+
return metadata?.baseRef ?? metadata?.baseRefName ?? null;
|
|
748
|
+
}
|
|
749
|
+
function readPaseoWorktreeBaseRef(worktreeRoot) {
|
|
750
|
+
return storedBaseRefFromMetadata(readPaseoWorktreeMetadata(worktreeRoot));
|
|
751
|
+
}
|
|
752
|
+
async function getStoredBaseRefForCwd(cwd, context) {
|
|
753
|
+
if (context?.facts?.isGit) {
|
|
754
|
+
return context.facts.storedBaseRef;
|
|
755
|
+
}
|
|
756
|
+
const paseoWorktree = await getPaseoWorktreeForCwd(cwd, { context });
|
|
757
|
+
if (!paseoWorktree.isPaseoOwnedWorktree) {
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
760
|
+
return readPaseoWorktreeBaseRef(paseoWorktree.worktreeRoot);
|
|
761
|
+
}
|
|
762
|
+
async function getResolvedBaseRefForCwd(cwd, context) {
|
|
763
|
+
if (context?.facts?.isGit) {
|
|
764
|
+
return context.facts.resolvedBaseRef;
|
|
765
|
+
}
|
|
766
|
+
const { resolvedBaseRef } = await resolveBaseRefForCwd(cwd, context);
|
|
767
|
+
return resolvedBaseRef;
|
|
768
|
+
}
|
|
769
|
+
async function resolveBaseRefForCwd(cwd, context) {
|
|
770
|
+
if (context?.facts?.isGit) {
|
|
771
|
+
return {
|
|
772
|
+
storedBaseRef: context.facts.storedBaseRef,
|
|
773
|
+
resolvedBaseRef: context.facts.resolvedBaseRef,
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
const storedBaseRef = await getStoredBaseRefForCwd(cwd, context);
|
|
777
|
+
return {
|
|
778
|
+
storedBaseRef,
|
|
779
|
+
resolvedBaseRef: storedBaseRef ?? (await resolveBaseRef(cwd, context)),
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
// The worktree stores the exact ref it was cut from ("refs/remotes/upstream/main") while
|
|
783
|
+
// callers still send its display name ("main"). A different qualified ref retains its own
|
|
784
|
+
// identity even when it has the same branch name.
|
|
785
|
+
function isSameBaseRef(stored, requested) {
|
|
786
|
+
return stored === requested || branchNameFromRef(stored) === requested;
|
|
787
|
+
}
|
|
788
|
+
// Names both refs rather than labelling either one correct: a caller's ref can be stale, but the
|
|
789
|
+
// stored ref can equally be wrong, so the message states the two facts and leaves the diagnosis open.
|
|
790
|
+
function baseRefMismatchError(refs) {
|
|
791
|
+
return new Error(`Base ref mismatch: stored ${refs.stored}, requested ${refs.requested}`);
|
|
792
|
+
}
|
|
793
|
+
function resolveOperationBaseRef(input) {
|
|
794
|
+
if (input.storedBaseRef &&
|
|
795
|
+
input.requestedBaseRef &&
|
|
796
|
+
!isSameBaseRef(input.storedBaseRef, input.requestedBaseRef)) {
|
|
797
|
+
throw baseRefMismatchError({
|
|
798
|
+
stored: input.storedBaseRef,
|
|
799
|
+
requested: input.requestedBaseRef,
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
return input.storedBaseRef ?? input.requestedBaseRef ?? input.resolvedBaseRef;
|
|
803
|
+
}
|
|
804
|
+
async function isWorkingTreeDirty(cwd, context) {
|
|
805
|
+
const { stdout } = await getRunGitCommand(context)(["status", "--porcelain"], {
|
|
806
|
+
cwd,
|
|
807
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
808
|
+
logger: context?.logger,
|
|
809
|
+
});
|
|
810
|
+
return stdout.trim().length > 0;
|
|
811
|
+
}
|
|
812
|
+
export async function getOriginRemoteUrl(cwd, context) {
|
|
813
|
+
try {
|
|
814
|
+
const { stdout } = await getRunGitCommand(context)(["config", "--get", "remote.origin.url"], {
|
|
815
|
+
cwd,
|
|
816
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
817
|
+
});
|
|
818
|
+
const url = stdout.trim();
|
|
819
|
+
return url.length > 0 ? url : null;
|
|
820
|
+
}
|
|
821
|
+
catch {
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
export async function hasOriginRemote(cwd) {
|
|
826
|
+
const url = await getOriginRemoteUrl(cwd);
|
|
827
|
+
return url !== null;
|
|
828
|
+
}
|
|
829
|
+
async function getGitConfigValue(cwd, key, context) {
|
|
830
|
+
try {
|
|
831
|
+
const { stdout } = await getRunGitCommand(context)(["config", "--get", key], {
|
|
832
|
+
cwd,
|
|
833
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
834
|
+
logger: context?.logger,
|
|
835
|
+
});
|
|
836
|
+
const value = stdout.trim();
|
|
837
|
+
return value.length > 0 ? value : null;
|
|
838
|
+
}
|
|
839
|
+
catch {
|
|
840
|
+
return null;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
async function getGitRemotePushUrl(cwd, remoteName, context) {
|
|
844
|
+
try {
|
|
845
|
+
const { stdout } = await getRunGitCommand(context)(["remote", "get-url", "--push", remoteName], {
|
|
846
|
+
cwd,
|
|
847
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
848
|
+
logger: context?.logger,
|
|
849
|
+
});
|
|
850
|
+
const value = stdout.trim();
|
|
851
|
+
return value.length > 0 ? value : null;
|
|
852
|
+
}
|
|
853
|
+
catch {
|
|
854
|
+
return null;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
function parseBranchMergeHeadRef(mergeRef) {
|
|
858
|
+
const prefix = "refs/heads/";
|
|
859
|
+
if (!mergeRef?.startsWith(prefix)) {
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
const headRef = mergeRef.slice(prefix.length).trim();
|
|
863
|
+
return headRef.length > 0 ? headRef : null;
|
|
864
|
+
}
|
|
865
|
+
async function resolvePullRequestStatusLookupTarget(cwd, currentBranch, context) {
|
|
866
|
+
if (context?.facts?.isGit && context.facts.pullRequestLookupTarget) {
|
|
867
|
+
return context.facts.pullRequestLookupTarget;
|
|
868
|
+
}
|
|
869
|
+
const branchRemoteName = await getGitConfigValue(cwd, `branch.${currentBranch}.remote`, context);
|
|
870
|
+
let branchMergeRef = null;
|
|
871
|
+
if (branchRemoteName) {
|
|
872
|
+
branchMergeRef = await getGitConfigValue(cwd, `branch.${currentBranch}.merge`, context);
|
|
873
|
+
}
|
|
874
|
+
const localBranchTarget = buildPullRequestLookupTargetFromBranchConfig({
|
|
875
|
+
currentBranch,
|
|
876
|
+
branchRemoteName,
|
|
877
|
+
branchMergeRef,
|
|
878
|
+
branchRemoteUrl: null,
|
|
879
|
+
originRemoteUrl: null,
|
|
880
|
+
resolvedBaseRef: null,
|
|
881
|
+
});
|
|
882
|
+
if (localBranchTarget.headRef === currentBranch) {
|
|
883
|
+
const pushTarget = await resolvePullRequestLookupTargetFromPushConfig(cwd, currentBranch, null, null, context);
|
|
884
|
+
return pushTarget ?? localBranchTarget;
|
|
885
|
+
}
|
|
886
|
+
const [branchRemoteUrl, originRemoteUrl, resolvedBaseRef] = await Promise.all([
|
|
887
|
+
branchRemoteName ? getGitConfigValue(cwd, `remote.${branchRemoteName}.url`, context) : null,
|
|
888
|
+
getGitConfigValue(cwd, "remote.origin.url", context),
|
|
889
|
+
getResolvedBaseRefForCwd(cwd, context),
|
|
890
|
+
]);
|
|
891
|
+
const branchTarget = buildPullRequestLookupTargetFromBranchConfig({
|
|
892
|
+
currentBranch,
|
|
893
|
+
branchRemoteName,
|
|
894
|
+
branchMergeRef,
|
|
895
|
+
branchRemoteUrl,
|
|
896
|
+
originRemoteUrl,
|
|
897
|
+
resolvedBaseRef,
|
|
898
|
+
});
|
|
899
|
+
if (branchTarget.headRef !== currentBranch || branchTarget.headRepositoryOwner) {
|
|
900
|
+
return branchTarget;
|
|
901
|
+
}
|
|
902
|
+
const pushTarget = await resolvePullRequestLookupTargetFromPushConfig(cwd, currentBranch, originRemoteUrl, resolvedBaseRef, context);
|
|
903
|
+
return pushTarget ?? branchTarget;
|
|
904
|
+
}
|
|
905
|
+
export async function resolveAbsoluteGitDir(cwd, context) {
|
|
906
|
+
try {
|
|
907
|
+
const { stdout } = await getRunGitCommand(context)(["rev-parse", "--absolute-git-dir"], {
|
|
908
|
+
cwd,
|
|
909
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
910
|
+
});
|
|
911
|
+
const gitDir = stdout.trim();
|
|
912
|
+
return gitDir.length > 0 ? gitDir : null;
|
|
913
|
+
}
|
|
914
|
+
catch {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
async function resolveGitCommonDir(cwd, context) {
|
|
919
|
+
try {
|
|
920
|
+
const { stdout } = await getRunGitCommand(context)(["rev-parse", "--git-common-dir"], {
|
|
921
|
+
cwd,
|
|
922
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
923
|
+
});
|
|
924
|
+
return resolveGitRevParsePath(cwd, stdout);
|
|
925
|
+
}
|
|
926
|
+
catch {
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
async function abortGitPullConflictState(cwd) {
|
|
931
|
+
const gitDir = await resolveAbsoluteGitDir(cwd);
|
|
932
|
+
if (!gitDir) {
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
const mergeHeadPath = resolve(gitDir, "MERGE_HEAD");
|
|
936
|
+
const rebaseMergePath = resolve(gitDir, "rebase-merge");
|
|
937
|
+
const rebaseApplyPath = resolve(gitDir, "rebase-apply");
|
|
938
|
+
if (existsSync(mergeHeadPath)) {
|
|
939
|
+
try {
|
|
940
|
+
await runGitCommand(["merge", "--abort"], { cwd, timeout: 120000 });
|
|
941
|
+
}
|
|
942
|
+
catch {
|
|
943
|
+
// ignore
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
if (existsSync(rebaseMergePath) || existsSync(rebaseApplyPath)) {
|
|
947
|
+
try {
|
|
948
|
+
await runGitCommand(["rebase", "--abort"], { cwd, timeout: 120000 });
|
|
949
|
+
}
|
|
950
|
+
catch {
|
|
951
|
+
// ignore
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
export async function resolveRepositoryDefaultBranch(repoRoot, context) {
|
|
956
|
+
try {
|
|
957
|
+
const { stdout } = await getRunGitCommand(context)(["symbolic-ref", "--quiet", "refs/remotes/origin/HEAD"], {
|
|
958
|
+
cwd: repoRoot,
|
|
959
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
960
|
+
});
|
|
961
|
+
const ref = stdout.trim();
|
|
962
|
+
if (ref) {
|
|
963
|
+
// Prefer a local branch name (e.g. "main") over the remote-tracking ref (e.g. "origin/main")
|
|
964
|
+
// so that status/diff/merge all operate against the same base ref.
|
|
965
|
+
const remoteShort = ref.replace(/^refs\/remotes\//, "");
|
|
966
|
+
const localName = remoteShort.startsWith("origin/")
|
|
967
|
+
? remoteShort.slice("origin/".length)
|
|
968
|
+
: remoteShort;
|
|
969
|
+
try {
|
|
970
|
+
await getRunGitCommand(context)(["show-ref", "--verify", "--quiet", `refs/heads/${localName}`], {
|
|
971
|
+
cwd: repoRoot,
|
|
972
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
973
|
+
});
|
|
974
|
+
return localName;
|
|
975
|
+
}
|
|
976
|
+
catch {
|
|
977
|
+
return remoteShort;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
catch {
|
|
982
|
+
// ignore
|
|
983
|
+
}
|
|
984
|
+
const { stdout } = await getRunGitCommand(context)(["branch", "--format=%(refname:short)"], {
|
|
985
|
+
cwd: repoRoot,
|
|
986
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
987
|
+
});
|
|
988
|
+
const branches = new Set(stdout
|
|
989
|
+
.split("\n")
|
|
990
|
+
.map((line) => line.trim())
|
|
991
|
+
.filter((line) => line.length > 0));
|
|
992
|
+
if (branches.has("main")) {
|
|
993
|
+
return "main";
|
|
994
|
+
}
|
|
995
|
+
if (branches.has("master")) {
|
|
996
|
+
return "master";
|
|
997
|
+
}
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
1000
|
+
async function resolveBaseRef(repoRoot, context) {
|
|
1001
|
+
return resolveRepositoryDefaultBranch(repoRoot, context);
|
|
1002
|
+
}
|
|
1003
|
+
function normalizeComparisonBaseRefName(input) {
|
|
1004
|
+
const localName = branchNameFromRef(input);
|
|
1005
|
+
return { localName, originRef: `origin/${localName}` };
|
|
1006
|
+
}
|
|
1007
|
+
function resolveMergeTargetBranch(baseRef) {
|
|
1008
|
+
const remotePrefix = "refs/remotes/";
|
|
1009
|
+
const originPrefix = `${remotePrefix}origin/`;
|
|
1010
|
+
if (baseRef.startsWith(remotePrefix) && !baseRef.startsWith(originPrefix)) {
|
|
1011
|
+
throw new Error(`No local merge target is recorded for base ref ${baseRef}`);
|
|
1012
|
+
}
|
|
1013
|
+
return branchNameFromRef(baseRef);
|
|
1014
|
+
}
|
|
1015
|
+
async function doesGitRefExist(cwd, fullRef, context) {
|
|
1016
|
+
const result = await getRunGitCommand(context)(["show-ref", "--verify", "--quiet", fullRef], {
|
|
1017
|
+
cwd,
|
|
1018
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
1019
|
+
acceptExitCodes: [0, 1],
|
|
1020
|
+
logger: context?.logger,
|
|
1021
|
+
});
|
|
1022
|
+
return result.exitCode === 0;
|
|
1023
|
+
}
|
|
1024
|
+
function isQualifiedBranchRef(ref) {
|
|
1025
|
+
return ref.startsWith("refs/heads/") || ref.startsWith("refs/remotes/");
|
|
1026
|
+
}
|
|
1027
|
+
async function resolveBestComparisonBaseRef(cwd, baseRef, context) {
|
|
1028
|
+
// A fully qualified branch ref names the exact commit stream to compare against. Bare names
|
|
1029
|
+
// keep going through the local-vs-origin heuristic below for legacy worktree metadata.
|
|
1030
|
+
if (isQualifiedBranchRef(baseRef)) {
|
|
1031
|
+
if (await doesGitRefExist(cwd, baseRef, context)) {
|
|
1032
|
+
return baseRef;
|
|
1033
|
+
}
|
|
1034
|
+
throw new Error(`Base ref not found: ${baseRef}`);
|
|
1035
|
+
}
|
|
1036
|
+
const normalized = normalizeComparisonBaseRefName(baseRef);
|
|
1037
|
+
const [hasLocal, hasOrigin] = await Promise.all([
|
|
1038
|
+
doesGitRefExist(cwd, `refs/heads/${normalized.localName}`, context),
|
|
1039
|
+
doesGitRefExist(cwd, `refs/remotes/origin/${normalized.localName}`, context),
|
|
1040
|
+
]);
|
|
1041
|
+
if (hasOrigin) {
|
|
1042
|
+
return normalized.originRef;
|
|
1043
|
+
}
|
|
1044
|
+
if (hasLocal) {
|
|
1045
|
+
return normalized.localName;
|
|
1046
|
+
}
|
|
1047
|
+
const refName = baseRef.startsWith("origin/") || baseRef.startsWith("refs/remotes/origin/")
|
|
1048
|
+
? normalized.originRef
|
|
1049
|
+
: normalized.localName;
|
|
1050
|
+
throw new Error(`Base branch not found locally or on origin: ${refName}`);
|
|
1051
|
+
}
|
|
1052
|
+
async function resolveMostAheadBaseRef(cwd, baseRef) {
|
|
1053
|
+
if (isQualifiedBranchRef(baseRef)) {
|
|
1054
|
+
if (await doesGitRefExist(cwd, baseRef)) {
|
|
1055
|
+
return baseRef;
|
|
1056
|
+
}
|
|
1057
|
+
throw new Error(`Base ref not found: ${baseRef}`);
|
|
1058
|
+
}
|
|
1059
|
+
const normalizedBaseRef = branchNameFromRef(baseRef);
|
|
1060
|
+
const [hasLocal, hasOrigin] = await Promise.all([
|
|
1061
|
+
doesGitRefExist(cwd, `refs/heads/${normalizedBaseRef}`),
|
|
1062
|
+
doesGitRefExist(cwd, `refs/remotes/origin/${normalizedBaseRef}`),
|
|
1063
|
+
]);
|
|
1064
|
+
if (hasLocal && !hasOrigin) {
|
|
1065
|
+
return normalizedBaseRef;
|
|
1066
|
+
}
|
|
1067
|
+
if (!hasLocal && hasOrigin) {
|
|
1068
|
+
return `origin/${normalizedBaseRef}`;
|
|
1069
|
+
}
|
|
1070
|
+
if (!hasLocal && !hasOrigin) {
|
|
1071
|
+
throw new Error(`Base branch not found locally or on origin: ${normalizedBaseRef}`);
|
|
1072
|
+
}
|
|
1073
|
+
const { stdout } = await runGitCommand(["rev-list", "--left-right", "--count", `${normalizedBaseRef}...origin/${normalizedBaseRef}`], { cwd, envOverlay: READ_ONLY_GIT_ENV });
|
|
1074
|
+
const [localOnlyRaw, originOnlyRaw] = stdout.trim().split(/\s+/);
|
|
1075
|
+
const localOnly = Number.parseInt(localOnlyRaw ?? "0", 10);
|
|
1076
|
+
const originOnly = Number.parseInt(originOnlyRaw ?? "0", 10);
|
|
1077
|
+
if (Number.isNaN(localOnly) || Number.isNaN(originOnly)) {
|
|
1078
|
+
return normalizedBaseRef;
|
|
1079
|
+
}
|
|
1080
|
+
if (originOnly > localOnly) {
|
|
1081
|
+
return `origin/${normalizedBaseRef}`;
|
|
1082
|
+
}
|
|
1083
|
+
return normalizedBaseRef;
|
|
1084
|
+
}
|
|
1085
|
+
async function getAheadBehind(cwd, baseRef, currentBranch, context) {
|
|
1086
|
+
const normalizedBaseRef = branchNameFromRef(baseRef);
|
|
1087
|
+
if (!normalizedBaseRef || !currentBranch || normalizedBaseRef === currentBranch) {
|
|
1088
|
+
return null;
|
|
1089
|
+
}
|
|
1090
|
+
const comparisonBaseRef = context?.facts?.isGit && context.facts.resolvedBaseRef === baseRef
|
|
1091
|
+
? context.facts.comparisonBaseRef
|
|
1092
|
+
: await resolveBestComparisonBaseRef(cwd, baseRef, context);
|
|
1093
|
+
if (!comparisonBaseRef) {
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
return getAheadBehindForComparisonRef(cwd, comparisonBaseRef, currentBranch, context);
|
|
1097
|
+
}
|
|
1098
|
+
async function getAheadBehindForComparisonRef(cwd, comparisonRef, currentBranch, context) {
|
|
1099
|
+
const { stdout } = await getRunGitCommand(context)(["rev-list", "--left-right", "--count", `${comparisonRef}...${currentBranch}`], { cwd, envOverlay: READ_ONLY_GIT_ENV, logger: context?.logger });
|
|
1100
|
+
const [behindRaw, aheadRaw] = stdout.trim().split(/\s+/);
|
|
1101
|
+
const behind = Number.parseInt(behindRaw ?? "0", 10);
|
|
1102
|
+
const ahead = Number.parseInt(aheadRaw ?? "0", 10);
|
|
1103
|
+
if (Number.isNaN(behind) || Number.isNaN(ahead)) {
|
|
1104
|
+
return null;
|
|
1105
|
+
}
|
|
1106
|
+
return { ahead, behind };
|
|
1107
|
+
}
|
|
1108
|
+
async function getUpstreamStatus(cwd, currentBranch, context) {
|
|
1109
|
+
try {
|
|
1110
|
+
const { stdout } = await getRunGitCommand(context)([
|
|
1111
|
+
"for-each-ref",
|
|
1112
|
+
"--format=%(upstream)%00%(upstream:track,nobracket)",
|
|
1113
|
+
`refs/heads/${currentBranch}`,
|
|
1114
|
+
], { cwd, envOverlay: READ_ONLY_GIT_ENV, logger: context?.logger });
|
|
1115
|
+
const [ref = "", track = ""] = stdout.trim().split("\0", 2);
|
|
1116
|
+
if (!ref || track === "gone") {
|
|
1117
|
+
return null;
|
|
1118
|
+
}
|
|
1119
|
+
const ahead = Number.parseInt(track.match(/ahead (\d+)/)?.[1] ?? "0", 10);
|
|
1120
|
+
const behind = Number.parseInt(track.match(/behind (\d+)/)?.[1] ?? "0", 10);
|
|
1121
|
+
return { ref, aheadBehind: { ahead, behind } };
|
|
1122
|
+
}
|
|
1123
|
+
catch {
|
|
1124
|
+
return null;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
async function inspectCheckoutContext(cwd, context) {
|
|
1128
|
+
const root = await getWorktreeRoot(cwd, context);
|
|
1129
|
+
if (!root) {
|
|
1130
|
+
return null;
|
|
1131
|
+
}
|
|
1132
|
+
const [currentBranch, remoteUrl, absoluteGitDir, gitCommonDir] = await Promise.all([
|
|
1133
|
+
getCurrentBranch(cwd, context),
|
|
1134
|
+
getOriginRemoteUrl(cwd, context),
|
|
1135
|
+
resolveAbsoluteGitDir(cwd, context),
|
|
1136
|
+
resolveGitCommonDir(cwd, context),
|
|
1137
|
+
]);
|
|
1138
|
+
const paseoWorktree = await getPaseoWorktreeForCwd(cwd, {
|
|
1139
|
+
context,
|
|
1140
|
+
knownWorktreeRoot: root,
|
|
1141
|
+
knownGitCommonDir: gitCommonDir,
|
|
1142
|
+
});
|
|
1143
|
+
return {
|
|
1144
|
+
worktreeRoot: root,
|
|
1145
|
+
currentBranch,
|
|
1146
|
+
remoteUrl,
|
|
1147
|
+
absoluteGitDir,
|
|
1148
|
+
gitCommonDir,
|
|
1149
|
+
paseoWorktree,
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
function buildPullRequestLookupTargetFromBranchConfig(input) {
|
|
1153
|
+
const trackedHeadRef = parseBranchMergeHeadRef(input.branchMergeRef);
|
|
1154
|
+
if (!input.branchRemoteName || !trackedHeadRef) {
|
|
1155
|
+
return { headRef: input.currentBranch };
|
|
1156
|
+
}
|
|
1157
|
+
const remoteRepo = parseRepositoryIdentityFromRemote(input.branchRemoteUrl);
|
|
1158
|
+
const originRepo = parseRepositoryIdentityFromRemote(input.originRemoteUrl);
|
|
1159
|
+
const isSameRepo = areSameGitHubRepository(remoteRepo, originRepo);
|
|
1160
|
+
const headRepositoryOwner = remoteRepo && !isSameRepo ? remoteRepo.split("/")[0] : null;
|
|
1161
|
+
const normalizedBaseRef = input.resolvedBaseRef ? branchNameFromRef(input.resolvedBaseRef) : null;
|
|
1162
|
+
if (trackedHeadRef === normalizedBaseRef &&
|
|
1163
|
+
!doesLocalBranchNameIdentifyTrackedHead(input.currentBranch, trackedHeadRef, headRepositoryOwner)) {
|
|
1164
|
+
return { headRef: input.currentBranch };
|
|
1165
|
+
}
|
|
1166
|
+
if (isSameRepo) {
|
|
1167
|
+
return { headRef: trackedHeadRef };
|
|
1168
|
+
}
|
|
1169
|
+
return {
|
|
1170
|
+
headRef: trackedHeadRef,
|
|
1171
|
+
...(headRepositoryOwner ? { headRepositoryOwner } : {}),
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
function parseRepositoryIdentityFromRemote(remoteUrl) {
|
|
1175
|
+
if (!remoteUrl) {
|
|
1176
|
+
return null;
|
|
1177
|
+
}
|
|
1178
|
+
const location = parseGitRemoteLocation(remoteUrl);
|
|
1179
|
+
return location ? (parseGitHubRemoteIdentity(location.path)?.repo ?? null) : null;
|
|
1180
|
+
}
|
|
1181
|
+
function areSameGitHubRepository(left, right) {
|
|
1182
|
+
return Boolean(left && right && left.toLowerCase() === right.toLowerCase());
|
|
1183
|
+
}
|
|
1184
|
+
function doesLocalBranchNameIdentifyTrackedHead(currentBranch, trackedHeadRef, headRepositoryOwner) {
|
|
1185
|
+
if (currentBranch === trackedHeadRef) {
|
|
1186
|
+
return true;
|
|
1187
|
+
}
|
|
1188
|
+
if (!headRepositoryOwner) {
|
|
1189
|
+
return false;
|
|
1190
|
+
}
|
|
1191
|
+
return [headRepositoryOwner, headRepositoryOwner.toLowerCase()].some((owner) => currentBranch === `${owner}/${trackedHeadRef}`);
|
|
1192
|
+
}
|
|
1193
|
+
function buildPullRequestLookupTargetFromPushConfig(input) {
|
|
1194
|
+
const pushedHeadRef = parseHeadPushRefspec(input.pushRefspec);
|
|
1195
|
+
if (!input.pushRemoteName || !pushedHeadRef || pushedHeadRef === input.currentBranch) {
|
|
1196
|
+
return null;
|
|
1197
|
+
}
|
|
1198
|
+
const remoteRepo = input.pushRemoteUrl ? parseGitHubRepoFromRemote(input.pushRemoteUrl) : null;
|
|
1199
|
+
const originRepo = input.originRemoteUrl
|
|
1200
|
+
? parseGitHubRepoFromRemote(input.originRemoteUrl)
|
|
1201
|
+
: null;
|
|
1202
|
+
const isSameRepo = areSameGitHubRepository(remoteRepo, originRepo);
|
|
1203
|
+
const headRepositoryOwner = remoteRepo && !isSameRepo ? remoteRepo.split("/")[0] : null;
|
|
1204
|
+
const normalizedBaseRef = input.resolvedBaseRef ? branchNameFromRef(input.resolvedBaseRef) : null;
|
|
1205
|
+
if (pushedHeadRef === normalizedBaseRef && !headRepositoryOwner) {
|
|
1206
|
+
return null;
|
|
1207
|
+
}
|
|
1208
|
+
return {
|
|
1209
|
+
headRef: pushedHeadRef,
|
|
1210
|
+
...(headRepositoryOwner ? { headRepositoryOwner } : {}),
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
function buildPullRequestLookupTargetFromMetadata(metadata, currentBranch) {
|
|
1214
|
+
const target = getPaseoWorktreeChangeRequestHintForBranch(metadata, currentBranch);
|
|
1215
|
+
if (!target) {
|
|
1216
|
+
return null;
|
|
1217
|
+
}
|
|
1218
|
+
return {
|
|
1219
|
+
headRef: target.headRef,
|
|
1220
|
+
...(target.headRepositoryOwner ? { headRepositoryOwner: target.headRepositoryOwner } : {}),
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
function buildInitialPullRequestLookupTarget(input) {
|
|
1224
|
+
if (!input.currentBranch) {
|
|
1225
|
+
return null;
|
|
1226
|
+
}
|
|
1227
|
+
const hasConfiguredBranchTarget = Boolean(input.branchRemoteName && parseBranchMergeHeadRef(input.branchMergeRef));
|
|
1228
|
+
if (hasConfiguredBranchTarget) {
|
|
1229
|
+
return buildPullRequestLookupTargetFromBranchConfig({
|
|
1230
|
+
currentBranch: input.currentBranch,
|
|
1231
|
+
branchRemoteName: input.branchRemoteName,
|
|
1232
|
+
branchMergeRef: input.branchMergeRef,
|
|
1233
|
+
branchRemoteUrl: input.branchRemoteUrl,
|
|
1234
|
+
originRemoteUrl: input.originRemoteUrl,
|
|
1235
|
+
resolvedBaseRef: input.resolvedBaseRef,
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
return buildPullRequestLookupTargetFromBranchConfig({
|
|
1239
|
+
currentBranch: input.currentBranch,
|
|
1240
|
+
branchRemoteName: input.branchRemoteName,
|
|
1241
|
+
branchMergeRef: input.branchMergeRef,
|
|
1242
|
+
branchRemoteUrl: input.branchRemoteUrl,
|
|
1243
|
+
originRemoteUrl: input.originRemoteUrl,
|
|
1244
|
+
resolvedBaseRef: input.resolvedBaseRef,
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
async function resolvePullRequestLookupTargetFromPushConfig(cwd, currentBranch, knownOriginRemoteUrl, knownResolvedBaseRef, context) {
|
|
1248
|
+
const pushRemoteName = await getGitConfigValue(cwd, `branch.${currentBranch}.pushRemote`, context);
|
|
1249
|
+
if (!pushRemoteName) {
|
|
1250
|
+
return null;
|
|
1251
|
+
}
|
|
1252
|
+
const [pushRefspec, pushRemoteUrl, originRemoteUrl, resolvedBaseRef] = await Promise.all([
|
|
1253
|
+
getGitConfigValue(cwd, `remote.${pushRemoteName}.push`, context),
|
|
1254
|
+
getGitConfigValue(cwd, `remote.${pushRemoteName}.url`, context),
|
|
1255
|
+
knownOriginRemoteUrl === null ? getGitConfigValue(cwd, "remote.origin.url", context) : null,
|
|
1256
|
+
knownResolvedBaseRef === null ? getResolvedBaseRefForCwd(cwd, context) : null,
|
|
1257
|
+
]);
|
|
1258
|
+
return buildPullRequestLookupTargetFromPushConfig({
|
|
1259
|
+
currentBranch,
|
|
1260
|
+
pushRemoteName,
|
|
1261
|
+
pushRefspec,
|
|
1262
|
+
pushRemoteUrl,
|
|
1263
|
+
originRemoteUrl: knownOriginRemoteUrl ?? originRemoteUrl,
|
|
1264
|
+
resolvedBaseRef: knownResolvedBaseRef ?? resolvedBaseRef,
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
async function resolveFactsPullRequestLookupTarget(input) {
|
|
1268
|
+
const { cwd, inspected, metadata, context } = input;
|
|
1269
|
+
const metadataTarget = inspected.currentBranch
|
|
1270
|
+
? buildPullRequestLookupTargetFromMetadata(metadata, inspected.currentBranch)
|
|
1271
|
+
: null;
|
|
1272
|
+
if (metadataTarget) {
|
|
1273
|
+
return metadataTarget;
|
|
1274
|
+
}
|
|
1275
|
+
let target = buildInitialPullRequestLookupTarget({
|
|
1276
|
+
currentBranch: inspected.currentBranch,
|
|
1277
|
+
branchRemoteName: input.branchRemoteName,
|
|
1278
|
+
branchMergeRef: input.branchMergeRef,
|
|
1279
|
+
branchRemoteUrl: input.branchRemoteUrl,
|
|
1280
|
+
originRemoteUrl: inspected.remoteUrl,
|
|
1281
|
+
resolvedBaseRef: input.resolvedBaseRef,
|
|
1282
|
+
});
|
|
1283
|
+
if (inspected.currentBranch &&
|
|
1284
|
+
target?.headRef === inspected.currentBranch &&
|
|
1285
|
+
!target.headRepositoryOwner) {
|
|
1286
|
+
target =
|
|
1287
|
+
(await resolvePullRequestLookupTargetFromPushConfig(cwd, inspected.currentBranch, inspected.remoteUrl, input.resolvedBaseRef, context)) ?? target;
|
|
1288
|
+
}
|
|
1289
|
+
return target;
|
|
1290
|
+
}
|
|
1291
|
+
export async function getCheckoutSnapshotFacts(cwd, context) {
|
|
1292
|
+
if (context?.facts) {
|
|
1293
|
+
return context.facts;
|
|
1294
|
+
}
|
|
1295
|
+
const inspected = await inspectCheckoutContext(cwd, context);
|
|
1296
|
+
if (!inspected) {
|
|
1297
|
+
return { isGit: false };
|
|
1298
|
+
}
|
|
1299
|
+
const paseoWorktreeMetadata = inspected.paseoWorktree.isPaseoOwnedWorktree
|
|
1300
|
+
? readPaseoWorktreeMetadata(inspected.paseoWorktree.worktreeRoot)
|
|
1301
|
+
: null;
|
|
1302
|
+
const storedBaseRef = storedBaseRefFromMetadata(paseoWorktreeMetadata);
|
|
1303
|
+
const resolvedBaseRef = storedBaseRef ?? (await resolveBaseRef(cwd, context));
|
|
1304
|
+
const mainRepoRoot = await getMainRepoRootFromCommonDir(cwd, inspected.gitCommonDir, context).catch(() => null);
|
|
1305
|
+
let comparisonBaseRef = null;
|
|
1306
|
+
if (resolvedBaseRef &&
|
|
1307
|
+
inspected.currentBranch &&
|
|
1308
|
+
branchNameFromRef(resolvedBaseRef) !== inspected.currentBranch) {
|
|
1309
|
+
comparisonBaseRef = await resolveBestComparisonBaseRef(cwd, resolvedBaseRef, context).catch(() => null);
|
|
1310
|
+
}
|
|
1311
|
+
let branchRemoteName = null;
|
|
1312
|
+
let branchMergeRef = null;
|
|
1313
|
+
let branchRemoteUrl = null;
|
|
1314
|
+
const upstreamStatusPromise = inspected.currentBranch
|
|
1315
|
+
? getUpstreamStatus(cwd, inspected.currentBranch, context)
|
|
1316
|
+
: Promise.resolve(null);
|
|
1317
|
+
if (inspected.currentBranch) {
|
|
1318
|
+
branchRemoteName = await getGitConfigValue(cwd, `branch.${inspected.currentBranch}.remote`, context);
|
|
1319
|
+
if (branchRemoteName) {
|
|
1320
|
+
[branchMergeRef, branchRemoteUrl] = await Promise.all([
|
|
1321
|
+
getGitConfigValue(cwd, `branch.${inspected.currentBranch}.merge`, context),
|
|
1322
|
+
branchRemoteName === "origin"
|
|
1323
|
+
? inspected.remoteUrl
|
|
1324
|
+
: getGitConfigValue(cwd, `remote.${branchRemoteName}.url`, context),
|
|
1325
|
+
]);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
let pullRequestLookupTarget = await resolveFactsPullRequestLookupTarget({
|
|
1329
|
+
cwd,
|
|
1330
|
+
inspected,
|
|
1331
|
+
metadata: paseoWorktreeMetadata,
|
|
1332
|
+
branchRemoteName,
|
|
1333
|
+
branchMergeRef,
|
|
1334
|
+
branchRemoteUrl,
|
|
1335
|
+
resolvedBaseRef,
|
|
1336
|
+
context,
|
|
1337
|
+
});
|
|
1338
|
+
pullRequestLookupTarget = await addHeadShaToPullRequestLookupTarget(cwd, pullRequestLookupTarget, context);
|
|
1339
|
+
const upstreamStatus = await upstreamStatusPromise;
|
|
1340
|
+
return {
|
|
1341
|
+
isGit: true,
|
|
1342
|
+
worktreeRoot: inspected.worktreeRoot,
|
|
1343
|
+
currentBranch: inspected.currentBranch,
|
|
1344
|
+
remoteUrl: inspected.remoteUrl,
|
|
1345
|
+
absoluteGitDir: inspected.absoluteGitDir,
|
|
1346
|
+
gitCommonDir: inspected.gitCommonDir,
|
|
1347
|
+
paseoWorktree: inspected.paseoWorktree,
|
|
1348
|
+
storedBaseRef,
|
|
1349
|
+
resolvedBaseRef,
|
|
1350
|
+
mainRepoRoot,
|
|
1351
|
+
comparisonBaseRef,
|
|
1352
|
+
branchRemoteName,
|
|
1353
|
+
branchMergeRef,
|
|
1354
|
+
upstreamStatus,
|
|
1355
|
+
pullRequestLookupTarget,
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
const PER_FILE_DIFF_MAX_BYTES = 1024 * 1024; // 1MB
|
|
1359
|
+
const TOTAL_DIFF_MAX_BYTES = 2 * 1024 * 1024; // 2MB
|
|
1360
|
+
const RELAY_MAX_FRAME_BYTES = 32 * 1024 * 1024;
|
|
1361
|
+
const CHECKOUT_DIFF_FRAME_HEADROOM_BYTES = 1024 * 1024;
|
|
1362
|
+
// Temporary until diffs load lazily per file. The Paseo relay's 32 MiB frame limit is
|
|
1363
|
+
// binding: string frames are encrypted and base64-encoded. Reserve 1 MiB plaintext for
|
|
1364
|
+
// the surrounding WebSocket JSON envelope after inverting that exact wire expansion.
|
|
1365
|
+
export const CHECKOUT_DIFF_MAX_STRUCTURED_BYTES = maxBase64EncryptedPlaintextByteLength(RELAY_MAX_FRAME_BYTES) - CHECKOUT_DIFF_FRAME_HEADROOM_BYTES;
|
|
1366
|
+
function createStructuredDiffAccumulator() {
|
|
1367
|
+
return { files: [], serializedBytes: Buffer.byteLength("[]", "utf8") };
|
|
1368
|
+
}
|
|
1369
|
+
function appendStructuredFile(structured, file) {
|
|
1370
|
+
const separatorBytes = structured.files.length > 0 ? 1 : 0;
|
|
1371
|
+
const fileBytes = Buffer.byteLength(JSON.stringify(file), "utf8");
|
|
1372
|
+
const nextBytes = structured.serializedBytes + separatorBytes + fileBytes;
|
|
1373
|
+
if (nextBytes > CHECKOUT_DIFF_MAX_STRUCTURED_BYTES) {
|
|
1374
|
+
return false;
|
|
1375
|
+
}
|
|
1376
|
+
structured.files.push(file);
|
|
1377
|
+
structured.serializedBytes = nextBytes;
|
|
1378
|
+
return true;
|
|
1379
|
+
}
|
|
1380
|
+
const UNTRACKED_BINARY_SNIFF_BYTES = 16 * 1024;
|
|
1381
|
+
async function isLikelyBinaryFile(absolutePath) {
|
|
1382
|
+
const handle = await openFile(absolutePath, "r");
|
|
1383
|
+
try {
|
|
1384
|
+
const buffer = Buffer.allocUnsafe(UNTRACKED_BINARY_SNIFF_BYTES);
|
|
1385
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
1386
|
+
if (bytesRead === 0) {
|
|
1387
|
+
return false;
|
|
1388
|
+
}
|
|
1389
|
+
let suspicious = 0;
|
|
1390
|
+
for (let i = 0; i < bytesRead; i += 1) {
|
|
1391
|
+
const byte = buffer[i];
|
|
1392
|
+
if (byte === 0) {
|
|
1393
|
+
return true;
|
|
1394
|
+
}
|
|
1395
|
+
// Treat control bytes as suspicious while allowing common whitespace.
|
|
1396
|
+
if (byte < 7 || (byte > 14 && byte < 32) || byte === 127) {
|
|
1397
|
+
suspicious += 1;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
return suspicious / bytesRead > 0.3;
|
|
1401
|
+
}
|
|
1402
|
+
finally {
|
|
1403
|
+
await handle.close();
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
async function inspectUntrackedFile(cwd, relativePath) {
|
|
1407
|
+
const absolutePath = resolve(cwd, relativePath);
|
|
1408
|
+
const metadata = await statFile(absolutePath);
|
|
1409
|
+
if (!metadata.isFile()) {
|
|
1410
|
+
return { stat: null, truncated: false };
|
|
1411
|
+
}
|
|
1412
|
+
if (await isLikelyBinaryFile(absolutePath)) {
|
|
1413
|
+
return {
|
|
1414
|
+
stat: { additions: 0, deletions: 0, isBinary: true },
|
|
1415
|
+
truncated: false,
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
if (metadata.size > PER_FILE_DIFF_MAX_BYTES) {
|
|
1419
|
+
return {
|
|
1420
|
+
stat: { additions: 0, deletions: 0, isBinary: false },
|
|
1421
|
+
truncated: true,
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
return {
|
|
1425
|
+
stat: { additions: 0, deletions: 0, isBinary: false },
|
|
1426
|
+
truncated: false,
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
function buildPlaceholderParsedDiffFile(change, options) {
|
|
1430
|
+
return {
|
|
1431
|
+
path: change.path,
|
|
1432
|
+
...(change.oldPath ? { oldPath: change.oldPath } : {}),
|
|
1433
|
+
isNew: change.isNew,
|
|
1434
|
+
isDeleted: change.isDeleted,
|
|
1435
|
+
additions: options.stat?.additions ?? 0,
|
|
1436
|
+
deletions: options.stat?.deletions ?? 0,
|
|
1437
|
+
hunks: [],
|
|
1438
|
+
status: options.status,
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
async function getUntrackedDiffText(cwd, change, ignoreWhitespace = false) {
|
|
1442
|
+
try {
|
|
1443
|
+
const inspected = await inspectUntrackedFile(cwd, change.path);
|
|
1444
|
+
if (inspected.stat?.isBinary || inspected.truncated) {
|
|
1445
|
+
return { text: "", truncated: inspected.truncated, stat: inspected.stat };
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
catch {
|
|
1449
|
+
// Fall through to git diff path if metadata probing fails.
|
|
1450
|
+
}
|
|
1451
|
+
const result = await runGitCommand(buildGitDiffArgs({
|
|
1452
|
+
ignoreWhitespace,
|
|
1453
|
+
extra: ["--no-index", "/dev/null", "--", change.path],
|
|
1454
|
+
}), {
|
|
1455
|
+
cwd,
|
|
1456
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
1457
|
+
maxOutputBytes: PER_FILE_DIFF_MAX_BYTES,
|
|
1458
|
+
acceptExitCodes: [0, 1],
|
|
1459
|
+
});
|
|
1460
|
+
return {
|
|
1461
|
+
text: result.stdout,
|
|
1462
|
+
truncated: result.truncated,
|
|
1463
|
+
stat: { additions: 0, deletions: 0, isBinary: false },
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
export async function getCheckoutStatus(cwd, context) {
|
|
1467
|
+
const facts = await getCheckoutSnapshotFacts(cwd, context);
|
|
1468
|
+
if (!facts.isGit) {
|
|
1469
|
+
return { isGit: false };
|
|
1470
|
+
}
|
|
1471
|
+
const worktreeRoot = facts.worktreeRoot;
|
|
1472
|
+
const currentBranch = facts.currentBranch;
|
|
1473
|
+
const remoteUrl = facts.remoteUrl;
|
|
1474
|
+
const paseoWorktree = facts.paseoWorktree;
|
|
1475
|
+
const isDirty = await isWorkingTreeDirty(cwd, context);
|
|
1476
|
+
const hasRemote = remoteUrl !== null;
|
|
1477
|
+
const baseRef = facts.resolvedBaseRef;
|
|
1478
|
+
const mainRepoRoot = facts.mainRepoRoot;
|
|
1479
|
+
const factsContext = { ...context, facts };
|
|
1480
|
+
const aheadBehind = baseRef && currentBranch
|
|
1481
|
+
? await getAheadBehind(cwd, baseRef, currentBranch, factsContext)
|
|
1482
|
+
: null;
|
|
1483
|
+
const upstreamStatus = facts.upstreamStatus;
|
|
1484
|
+
// The wire carries the display name: clients label the base with it and send it back to
|
|
1485
|
+
// request diffs and merges. The exact ref stays in worktree.json and in facts, where the
|
|
1486
|
+
// comparisons and actions read it.
|
|
1487
|
+
const displayBaseRef = baseRef ? branchNameFromRef(baseRef) : null;
|
|
1488
|
+
const upstreamRef = upstreamStatus?.ref ?? null;
|
|
1489
|
+
const aheadOfOrigin = upstreamStatus?.aheadBehind.ahead ?? null;
|
|
1490
|
+
const behindOfOrigin = upstreamStatus?.aheadBehind.behind ?? null;
|
|
1491
|
+
if (paseoWorktree.isPaseoOwnedWorktree && baseRef) {
|
|
1492
|
+
return {
|
|
1493
|
+
isGit: true,
|
|
1494
|
+
repoRoot: worktreeRoot,
|
|
1495
|
+
mainRepoRoot: mainRepoRoot ?? worktreeRoot,
|
|
1496
|
+
currentBranch,
|
|
1497
|
+
isDirty,
|
|
1498
|
+
baseRef: displayBaseRef ?? baseRef,
|
|
1499
|
+
aheadBehind,
|
|
1500
|
+
upstreamRef,
|
|
1501
|
+
aheadOfOrigin,
|
|
1502
|
+
behindOfOrigin,
|
|
1503
|
+
hasRemote,
|
|
1504
|
+
remoteUrl,
|
|
1505
|
+
isPaseoOwnedWorktree: true,
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
return {
|
|
1509
|
+
isGit: true,
|
|
1510
|
+
repoRoot: worktreeRoot,
|
|
1511
|
+
mainRepoRoot: mainRepoRoot && resolve(mainRepoRoot) !== resolve(worktreeRoot) ? mainRepoRoot : null,
|
|
1512
|
+
currentBranch,
|
|
1513
|
+
isDirty,
|
|
1514
|
+
baseRef: displayBaseRef,
|
|
1515
|
+
aheadBehind,
|
|
1516
|
+
upstreamRef,
|
|
1517
|
+
aheadOfOrigin,
|
|
1518
|
+
behindOfOrigin,
|
|
1519
|
+
hasRemote,
|
|
1520
|
+
remoteUrl,
|
|
1521
|
+
isPaseoOwnedWorktree: false,
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
// Workspace history stays complete; base history is bounded context until the
|
|
1525
|
+
// commits list supports paging older base commits.
|
|
1526
|
+
const CHECKOUT_BASE_COMMIT_LIMIT = 10;
|
|
1527
|
+
// Bytes git emits between fields/records. We split parsed output on these.
|
|
1528
|
+
const COMMIT_FIELD_SEPARATOR = "\x00";
|
|
1529
|
+
const COMMIT_RECORD_SEPARATOR = "\x1e";
|
|
1530
|
+
// Record-separated, NUL-field-separated so arbitrary subject text stays parseable.
|
|
1531
|
+
// `%x1e`/`%x00` are git placeholders (literal text in the arg, real bytes in the
|
|
1532
|
+
// output) — passing actual NUL bytes as a process arg is rejected by Node.
|
|
1533
|
+
const COMMIT_LOG_FORMAT = "%x1e%H%x00%h%x00%an%x00%aI%x00%s";
|
|
1534
|
+
function mapNameStatusLetter(letter) {
|
|
1535
|
+
switch (letter) {
|
|
1536
|
+
case "A":
|
|
1537
|
+
return "added";
|
|
1538
|
+
case "C":
|
|
1539
|
+
return "added";
|
|
1540
|
+
case "M":
|
|
1541
|
+
return "modified";
|
|
1542
|
+
case "T":
|
|
1543
|
+
return "modified";
|
|
1544
|
+
case "D":
|
|
1545
|
+
return "deleted";
|
|
1546
|
+
case "R":
|
|
1547
|
+
return "renamed";
|
|
1548
|
+
default:
|
|
1549
|
+
return undefined;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
// A `--raw` line: `:<srcmode> <dstmode> <srcsha> <dstsha> <STATUS>\t<path>`
|
|
1553
|
+
// (rename/copy add a second path: `R100\t<old>\t<new>`). The status token is the
|
|
1554
|
+
// last space-separated field before the first tab. Keyed on the destination path.
|
|
1555
|
+
function parseRawStatusLine(line, statuses) {
|
|
1556
|
+
const tabParts = line.split("\t");
|
|
1557
|
+
const meta = tabParts[0] ?? "";
|
|
1558
|
+
const statusToken = meta.slice(meta.lastIndexOf(" ") + 1);
|
|
1559
|
+
const letter = statusToken.charAt(0);
|
|
1560
|
+
const status = mapNameStatusLetter(letter);
|
|
1561
|
+
if (!status) {
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
const path = letter === "R" || letter === "C" ? (tabParts[tabParts.length - 1] ?? "") : (tabParts[1] ?? "");
|
|
1565
|
+
if (!path) {
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
statuses.set(path, status);
|
|
1569
|
+
}
|
|
1570
|
+
// A `--numstat` line: `<adds>\t<dels>\t<path>` (renames use `old => new`, binary
|
|
1571
|
+
// files report `-` for both counts). Keyed on the (normalized) destination path.
|
|
1572
|
+
function parseNumstatLine(line, stats) {
|
|
1573
|
+
const parts = line.split("\t");
|
|
1574
|
+
if (parts.length < 3) {
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
const additionsField = parts[0] ?? "";
|
|
1578
|
+
const deletionsField = parts[1] ?? "";
|
|
1579
|
+
const path = normalizeNumstatPath(parts.slice(2).join("\t"));
|
|
1580
|
+
if (!path) {
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
if (additionsField === "-" || deletionsField === "-") {
|
|
1584
|
+
stats.set(path, { additions: 0, deletions: 0 });
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
const additions = Number.parseInt(additionsField, 10);
|
|
1588
|
+
const deletions = Number.parseInt(deletionsField, 10);
|
|
1589
|
+
if (Number.isNaN(additions) || Number.isNaN(deletions)) {
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
stats.set(path, { additions, deletions });
|
|
1593
|
+
}
|
|
1594
|
+
// Parses the single combined `git log ... --raw --numstat -M` stream. Each record
|
|
1595
|
+
// (split on the record separator) starts with the NUL-field-separated header line,
|
|
1596
|
+
// then a blank line, then the interleaved `--raw` (status) and `--numstat` (counts)
|
|
1597
|
+
// blocks. We merge both by destination path so each file carries counts + status.
|
|
1598
|
+
function parseCheckoutCommitRecords(stdout) {
|
|
1599
|
+
const records = stdout.split(COMMIT_RECORD_SEPARATOR).filter((record) => record.length > 0);
|
|
1600
|
+
const commits = [];
|
|
1601
|
+
for (const record of records) {
|
|
1602
|
+
const lines = record.split("\n");
|
|
1603
|
+
const fields = (lines[0] ?? "").split(COMMIT_FIELD_SEPARATOR);
|
|
1604
|
+
if (fields.length < 5) {
|
|
1605
|
+
continue;
|
|
1606
|
+
}
|
|
1607
|
+
const sha = (fields[0] ?? "").trim();
|
|
1608
|
+
if (!sha) {
|
|
1609
|
+
continue;
|
|
1610
|
+
}
|
|
1611
|
+
const stats = new Map();
|
|
1612
|
+
const statuses = new Map();
|
|
1613
|
+
for (let index = 1; index < lines.length; index += 1) {
|
|
1614
|
+
const line = lines[index] ?? "";
|
|
1615
|
+
if (!line) {
|
|
1616
|
+
continue;
|
|
1617
|
+
}
|
|
1618
|
+
if (line.startsWith(":")) {
|
|
1619
|
+
parseRawStatusLine(line, statuses);
|
|
1620
|
+
}
|
|
1621
|
+
else {
|
|
1622
|
+
parseNumstatLine(line, stats);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
const files = [];
|
|
1626
|
+
for (const [path, stat] of stats) {
|
|
1627
|
+
const status = statuses.get(path);
|
|
1628
|
+
files.push({
|
|
1629
|
+
path,
|
|
1630
|
+
additions: stat.additions,
|
|
1631
|
+
deletions: stat.deletions,
|
|
1632
|
+
...(status ? { status } : {}),
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
commits.push({
|
|
1636
|
+
sha,
|
|
1637
|
+
shortSha: (fields[1] ?? "").trim(),
|
|
1638
|
+
authorName: fields[2] ?? "",
|
|
1639
|
+
authorDate: (fields[3] ?? "").trim(),
|
|
1640
|
+
subject: fields[4] ?? "",
|
|
1641
|
+
files,
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
return commits;
|
|
1645
|
+
}
|
|
1646
|
+
// Returns commits reachable from HEAD that are not reachable from any remote ref.
|
|
1647
|
+
async function getUnpushedCommitShas(cwd, context) {
|
|
1648
|
+
const { stdout } = await runGitCommand(["rev-list", "HEAD", "--not", "--remotes"], {
|
|
1649
|
+
cwd,
|
|
1650
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
1651
|
+
logger: context?.logger,
|
|
1652
|
+
});
|
|
1653
|
+
return new Set(stdout
|
|
1654
|
+
.split("\n")
|
|
1655
|
+
.map((line) => line.trim())
|
|
1656
|
+
.filter(Boolean));
|
|
1657
|
+
}
|
|
1658
|
+
async function getCheckoutCommitRecords({ cwd, revision, maxCount, }) {
|
|
1659
|
+
const args = [
|
|
1660
|
+
"log",
|
|
1661
|
+
revision,
|
|
1662
|
+
"--diff-merges=first-parent",
|
|
1663
|
+
`--format=${COMMIT_LOG_FORMAT}`,
|
|
1664
|
+
"--raw",
|
|
1665
|
+
"--numstat",
|
|
1666
|
+
"-M",
|
|
1667
|
+
];
|
|
1668
|
+
if (maxCount !== undefined) {
|
|
1669
|
+
args.splice(2, 0, `--max-count=${maxCount}`);
|
|
1670
|
+
}
|
|
1671
|
+
const result = await runGitCommand(args, { cwd, envOverlay: READ_ONLY_GIT_ENV });
|
|
1672
|
+
if (result.truncated) {
|
|
1673
|
+
throw new Error("Commit history exceeded the git output limit");
|
|
1674
|
+
}
|
|
1675
|
+
return parseCheckoutCommitRecords(result.stdout);
|
|
1676
|
+
}
|
|
1677
|
+
async function tryResolveCheckoutCommitsBaseRef(cwd, baseRef, currentBranch) {
|
|
1678
|
+
if (!baseRef) {
|
|
1679
|
+
return null;
|
|
1680
|
+
}
|
|
1681
|
+
const normalizedBaseRef = branchNameFromRef(baseRef);
|
|
1682
|
+
if (!normalizedBaseRef || normalizedBaseRef === currentBranch) {
|
|
1683
|
+
return null;
|
|
1684
|
+
}
|
|
1685
|
+
try {
|
|
1686
|
+
return await resolveMostAheadBaseRef(cwd, baseRef);
|
|
1687
|
+
}
|
|
1688
|
+
catch (error) {
|
|
1689
|
+
if (isQualifiedBranchRef(baseRef)) {
|
|
1690
|
+
throw error;
|
|
1691
|
+
}
|
|
1692
|
+
return null;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
export async function listCheckoutCommits({ cwd, context, }) {
|
|
1696
|
+
const currentBranch = await getCurrentBranch(cwd);
|
|
1697
|
+
if (!currentBranch) {
|
|
1698
|
+
return { baseRef: null, commits: [] };
|
|
1699
|
+
}
|
|
1700
|
+
const { resolvedBaseRef } = await resolveBaseRefForCwd(cwd, context);
|
|
1701
|
+
const normalizedBaseRef = resolvedBaseRef ? branchNameFromRef(resolvedBaseRef) : null;
|
|
1702
|
+
let comparisonBaseRef = await tryResolveCheckoutCommitsBaseRef(cwd, resolvedBaseRef, currentBranch);
|
|
1703
|
+
if (!comparisonBaseRef && normalizedBaseRef && normalizedBaseRef !== currentBranch) {
|
|
1704
|
+
// Saved worktree metadata can outlive a renamed or deleted base branch.
|
|
1705
|
+
comparisonBaseRef = await tryResolveCheckoutCommitsBaseRef(cwd, await resolveBaseRef(cwd), currentBranch);
|
|
1706
|
+
}
|
|
1707
|
+
let workspaceRecords = [];
|
|
1708
|
+
let baseRevision = "HEAD";
|
|
1709
|
+
if (comparisonBaseRef) {
|
|
1710
|
+
const [records, mergeBase] = await Promise.all([
|
|
1711
|
+
getCheckoutCommitRecords({ cwd, revision: `${comparisonBaseRef}..HEAD` }),
|
|
1712
|
+
tryResolveMergeBase(cwd, comparisonBaseRef),
|
|
1713
|
+
]);
|
|
1714
|
+
workspaceRecords = records;
|
|
1715
|
+
baseRevision = mergeBase ?? "";
|
|
1716
|
+
}
|
|
1717
|
+
const baseRecords = baseRevision
|
|
1718
|
+
? await getCheckoutCommitRecords({
|
|
1719
|
+
cwd,
|
|
1720
|
+
revision: baseRevision,
|
|
1721
|
+
maxCount: CHECKOUT_BASE_COMMIT_LIMIT,
|
|
1722
|
+
})
|
|
1723
|
+
: [];
|
|
1724
|
+
const records = [...workspaceRecords, ...baseRecords];
|
|
1725
|
+
if (records.length === 0) {
|
|
1726
|
+
return { baseRef: comparisonBaseRef, commits: [] };
|
|
1727
|
+
}
|
|
1728
|
+
const unpushedShas = await getUnpushedCommitShas(cwd);
|
|
1729
|
+
const workspaceShas = new Set(workspaceRecords.map((record) => record.sha));
|
|
1730
|
+
const commits = records.map((record) => ({
|
|
1731
|
+
sha: record.sha,
|
|
1732
|
+
shortSha: record.shortSha,
|
|
1733
|
+
subject: record.subject,
|
|
1734
|
+
authorName: record.authorName,
|
|
1735
|
+
authorDate: record.authorDate,
|
|
1736
|
+
isOnRemote: !unpushedShas.has(record.sha),
|
|
1737
|
+
isOnBase: !workspaceShas.has(record.sha),
|
|
1738
|
+
files: record.files,
|
|
1739
|
+
}));
|
|
1740
|
+
return { baseRef: comparisonBaseRef, commits };
|
|
1741
|
+
}
|
|
1742
|
+
/**
|
|
1743
|
+
* Fetches the unified diff of a single file as introduced by one commit and
|
|
1744
|
+
* parses it into the same {@link ParsedDiffFile} shape the diff subscription
|
|
1745
|
+
* emits (so the client can reuse its existing renderer).
|
|
1746
|
+
*
|
|
1747
|
+
* Compares merge commits to their first parent, matching the linear history shown
|
|
1748
|
+
* in the explorer. The text is parsed and highlighted by
|
|
1749
|
+
* {@link parseAndHighlightDiff} — the exact parser the diff subscription uses.
|
|
1750
|
+
* Returns `null` when the file is absent from the commit or the change is
|
|
1751
|
+
* binary-only (no textual hunks). Throws on git failure (e.g. an unknown sha),
|
|
1752
|
+
* which the caller maps to a typed checkout error.
|
|
1753
|
+
*/
|
|
1754
|
+
export async function getCommitFileDiff({ cwd, sha, path, }) {
|
|
1755
|
+
const { stdout } = await runGitCommand(["show", sha, "--format=", "--diff-merges=first-parent", "--", path], {
|
|
1756
|
+
cwd,
|
|
1757
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
1758
|
+
});
|
|
1759
|
+
if (stdout.trim().length === 0) {
|
|
1760
|
+
return null;
|
|
1761
|
+
}
|
|
1762
|
+
const parsedFiles = await parseAndHighlightDiff(stdout, cwd, {
|
|
1763
|
+
getOldFileContent: (file) => readGitFileContentAtRef(cwd, `${sha}^`, file.path),
|
|
1764
|
+
getNewFileContent: (file) => readGitFileContentAtRef(cwd, sha, file.path),
|
|
1765
|
+
});
|
|
1766
|
+
// `--` scopes the diff to a single pathspec, so there is at most one real
|
|
1767
|
+
// entry. Pick by path to drop any stray header-only section the parser emits.
|
|
1768
|
+
const file = parsedFiles.find((candidate) => candidate.path === path) ?? null;
|
|
1769
|
+
if (!file) {
|
|
1770
|
+
return null;
|
|
1771
|
+
}
|
|
1772
|
+
// Binary changes carry a "Binary files ... differ" marker and no hunks; there
|
|
1773
|
+
// is nothing textual to render, so report them as absent.
|
|
1774
|
+
if (file.hunks.length === 0 && /^Binary files .* differ$/m.test(stdout)) {
|
|
1775
|
+
return null;
|
|
1776
|
+
}
|
|
1777
|
+
return file;
|
|
1778
|
+
}
|
|
1779
|
+
function parseCheckoutShortstat(text) {
|
|
1780
|
+
const trimmed = text.trim();
|
|
1781
|
+
if (!trimmed) {
|
|
1782
|
+
return null;
|
|
1783
|
+
}
|
|
1784
|
+
let additions = 0;
|
|
1785
|
+
let deletions = 0;
|
|
1786
|
+
const addMatch = trimmed.match(/(\d+)\s+insertion/);
|
|
1787
|
+
if (addMatch) {
|
|
1788
|
+
additions = Number.parseInt(addMatch[1], 10);
|
|
1789
|
+
}
|
|
1790
|
+
const delMatch = trimmed.match(/(\d+)\s+deletion/);
|
|
1791
|
+
if (delMatch) {
|
|
1792
|
+
deletions = Number.parseInt(delMatch[1], 10);
|
|
1793
|
+
}
|
|
1794
|
+
if (additions === 0 && deletions === 0) {
|
|
1795
|
+
return null;
|
|
1796
|
+
}
|
|
1797
|
+
return { additions, deletions };
|
|
1798
|
+
}
|
|
1799
|
+
const UNTRACKED_SHORTSTAT_MAX_FILES = 500;
|
|
1800
|
+
async function countUntrackedAdditions(cwd, context, throwOnGitError = false) {
|
|
1801
|
+
try {
|
|
1802
|
+
const { stdout } = await getRunGitCommand(context)(["ls-files", "--others", "--exclude-standard"], {
|
|
1803
|
+
cwd,
|
|
1804
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
1805
|
+
});
|
|
1806
|
+
const files = stdout
|
|
1807
|
+
.split("\n")
|
|
1808
|
+
.map((l) => l.trim())
|
|
1809
|
+
.filter(Boolean);
|
|
1810
|
+
let additions = 0;
|
|
1811
|
+
for (const file of files.slice(0, UNTRACKED_SHORTSTAT_MAX_FILES)) {
|
|
1812
|
+
const absolutePath = resolve(cwd, file);
|
|
1813
|
+
try {
|
|
1814
|
+
const metadata = await statFile(absolutePath);
|
|
1815
|
+
if (metadata.size > PER_FILE_DIFF_MAX_BYTES)
|
|
1816
|
+
continue;
|
|
1817
|
+
if (await isLikelyBinaryFile(absolutePath))
|
|
1818
|
+
continue;
|
|
1819
|
+
const content = await readFile(absolutePath, "utf-8");
|
|
1820
|
+
if (content.length === 0)
|
|
1821
|
+
continue;
|
|
1822
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
1823
|
+
const lineCount = normalized.split("\n").length;
|
|
1824
|
+
additions += normalized.endsWith("\n") ? lineCount - 1 : lineCount;
|
|
1825
|
+
}
|
|
1826
|
+
catch {
|
|
1827
|
+
// Skip unreadable files.
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
return additions;
|
|
1831
|
+
}
|
|
1832
|
+
catch (error) {
|
|
1833
|
+
if (throwOnGitError) {
|
|
1834
|
+
throw error;
|
|
1835
|
+
}
|
|
1836
|
+
return 0;
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
function handleShortstatGitError(error, throwOnGitError = false) {
|
|
1840
|
+
if (throwOnGitError) {
|
|
1841
|
+
throw error;
|
|
1842
|
+
}
|
|
1843
|
+
return null;
|
|
1844
|
+
}
|
|
1845
|
+
async function getCheckoutShortstatUncached(cwd, context, options) {
|
|
1846
|
+
if (context?.facts?.isGit === false) {
|
|
1847
|
+
return null;
|
|
1848
|
+
}
|
|
1849
|
+
if (!context?.facts?.isGit) {
|
|
1850
|
+
try {
|
|
1851
|
+
await requireGitRepo(cwd, context);
|
|
1852
|
+
}
|
|
1853
|
+
catch {
|
|
1854
|
+
return null;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
const facts = context?.facts;
|
|
1858
|
+
const localBaseRef = facts?.isGit
|
|
1859
|
+
? facts.resolvedBaseRef
|
|
1860
|
+
: await getResolvedBaseRefForCwd(cwd, context);
|
|
1861
|
+
const currentBranch = facts?.isGit ? facts.currentBranch : await getCurrentBranch(cwd, context);
|
|
1862
|
+
const comparisonRef = await resolveShortstatComparisonRef({
|
|
1863
|
+
cwd,
|
|
1864
|
+
currentBranch,
|
|
1865
|
+
localBaseRef,
|
|
1866
|
+
facts,
|
|
1867
|
+
context,
|
|
1868
|
+
});
|
|
1869
|
+
if (!comparisonRef) {
|
|
1870
|
+
return null;
|
|
1871
|
+
}
|
|
1872
|
+
try {
|
|
1873
|
+
const { stdout: mergeBaseOut } = await getRunGitCommand(context)(["merge-base", "HEAD", comparisonRef], {
|
|
1874
|
+
cwd,
|
|
1875
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
1876
|
+
});
|
|
1877
|
+
const mergeBase = mergeBaseOut.trim();
|
|
1878
|
+
if (!mergeBase) {
|
|
1879
|
+
return null;
|
|
1880
|
+
}
|
|
1881
|
+
const [{ stdout }, untrackedAdditions] = await Promise.all([
|
|
1882
|
+
getRunGitCommand(context)(["diff", "--shortstat", mergeBase], {
|
|
1883
|
+
cwd,
|
|
1884
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
1885
|
+
}),
|
|
1886
|
+
countUntrackedAdditions(cwd, context, options?.throwOnGitError),
|
|
1887
|
+
]);
|
|
1888
|
+
const tracked = parseCheckoutShortstat(stdout);
|
|
1889
|
+
if (tracked) {
|
|
1890
|
+
return { additions: tracked.additions + untrackedAdditions, deletions: tracked.deletions };
|
|
1891
|
+
}
|
|
1892
|
+
if (untrackedAdditions > 0) {
|
|
1893
|
+
return { additions: untrackedAdditions, deletions: 0 };
|
|
1894
|
+
}
|
|
1895
|
+
return null;
|
|
1896
|
+
}
|
|
1897
|
+
catch (error) {
|
|
1898
|
+
return handleShortstatGitError(error, options?.throwOnGitError);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
async function resolveShortstatComparisonRef(input) {
|
|
1902
|
+
const { cwd, currentBranch, localBaseRef, facts, context } = input;
|
|
1903
|
+
if (!currentBranch) {
|
|
1904
|
+
return null;
|
|
1905
|
+
}
|
|
1906
|
+
if (localBaseRef && currentBranch !== localBaseRef) {
|
|
1907
|
+
try {
|
|
1908
|
+
return facts?.isGit && facts.resolvedBaseRef === localBaseRef && facts.comparisonBaseRef
|
|
1909
|
+
? facts.comparisonBaseRef
|
|
1910
|
+
: await resolveBestComparisonBaseRef(cwd, localBaseRef, context);
|
|
1911
|
+
}
|
|
1912
|
+
catch {
|
|
1913
|
+
return null;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
const hasOrigin = await doesGitRefExist(cwd, `refs/remotes/origin/${currentBranch}`, context);
|
|
1917
|
+
return hasOrigin ? `origin/${currentBranch}` : null;
|
|
1918
|
+
}
|
|
1919
|
+
function getOrLoadCheckoutShortstat(cwd, context, options) {
|
|
1920
|
+
const cacheKey = getShortstatCacheKey(cwd);
|
|
1921
|
+
if (!options?.force) {
|
|
1922
|
+
const cached = shortstatCache.get(cacheKey);
|
|
1923
|
+
if (cached !== undefined) {
|
|
1924
|
+
return Promise.resolve(cached);
|
|
1925
|
+
}
|
|
1926
|
+
const existing = shortstatInFlight.get(cacheKey);
|
|
1927
|
+
if (existing) {
|
|
1928
|
+
return existing;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
const load = getCheckoutShortstatUncached(cwd, context)
|
|
1932
|
+
.then((shortstat) => {
|
|
1933
|
+
shortstatCache.set(cacheKey, shortstat);
|
|
1934
|
+
return shortstat;
|
|
1935
|
+
})
|
|
1936
|
+
.finally(() => {
|
|
1937
|
+
shortstatInFlight.delete(cacheKey);
|
|
1938
|
+
});
|
|
1939
|
+
shortstatInFlight.set(cacheKey, load);
|
|
1940
|
+
return load;
|
|
1941
|
+
}
|
|
1942
|
+
export async function getCheckoutShortstat(cwd, context, options) {
|
|
1943
|
+
return getOrLoadCheckoutShortstat(cwd, context, options);
|
|
1944
|
+
}
|
|
1945
|
+
function normalizeRemoteTrackingRef(ref) {
|
|
1946
|
+
return ref.startsWith("refs/remotes/") ? ref.slice("refs/remotes/".length) : ref;
|
|
1947
|
+
}
|
|
1948
|
+
function checkoutFactsConfiguredRemoteRef(facts) {
|
|
1949
|
+
const trackedBranch = facts.branchMergeRef?.startsWith("refs/heads/")
|
|
1950
|
+
? facts.branchMergeRef.slice("refs/heads/".length)
|
|
1951
|
+
: null;
|
|
1952
|
+
return facts.branchRemoteName && facts.branchRemoteName !== "." && trackedBranch
|
|
1953
|
+
? `${facts.branchRemoteName}/${trackedBranch}`
|
|
1954
|
+
: null;
|
|
1955
|
+
}
|
|
1956
|
+
function getCheckoutRefMovement(facts, movedRemoteRefs) {
|
|
1957
|
+
const currentBranch = facts.currentBranch;
|
|
1958
|
+
const comparisonRef = facts.comparisonBaseRef;
|
|
1959
|
+
const normalizedMoves = new Set([...movedRemoteRefs].map(normalizeRemoteTrackingRef));
|
|
1960
|
+
const normalizedResolvedBase = facts.resolvedBaseRef
|
|
1961
|
+
? branchNameFromRef(facts.resolvedBaseRef)
|
|
1962
|
+
: null;
|
|
1963
|
+
const shortstatRemoteRef = currentBranch && (!normalizedResolvedBase || normalizedResolvedBase === currentBranch)
|
|
1964
|
+
? `origin/${currentBranch}`
|
|
1965
|
+
: null;
|
|
1966
|
+
const baseMoved = [facts.storedBaseRef, facts.resolvedBaseRef, comparisonRef, shortstatRemoteRef]
|
|
1967
|
+
.filter((ref) => Boolean(ref))
|
|
1968
|
+
.map(normalizeRemoteTrackingRef)
|
|
1969
|
+
.some((ref) => normalizedMoves.has(ref));
|
|
1970
|
+
const upstreamRef = facts.upstreamStatus?.ref ?? checkoutFactsConfiguredRemoteRef(facts);
|
|
1971
|
+
const upstreamMoved = upstreamRef
|
|
1972
|
+
? normalizedMoves.has(normalizeRemoteTrackingRef(upstreamRef))
|
|
1973
|
+
: false;
|
|
1974
|
+
return {
|
|
1975
|
+
baseMoved,
|
|
1976
|
+
comparisonRef,
|
|
1977
|
+
currentBranch,
|
|
1978
|
+
normalizedResolvedBase,
|
|
1979
|
+
upstreamMoved,
|
|
1980
|
+
upstreamRef,
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
export async function getCheckoutRefDerivedState(cwd, facts, current, movedRemoteRefs, context) {
|
|
1984
|
+
const { baseMoved, comparisonRef, currentBranch, normalizedResolvedBase, upstreamMoved, upstreamRef, } = getCheckoutRefMovement(facts, movedRemoteRefs);
|
|
1985
|
+
let aheadBehind = current.aheadBehind;
|
|
1986
|
+
let diffStat = current.diffStat;
|
|
1987
|
+
if (baseMoved && currentBranch && facts.resolvedBaseRef) {
|
|
1988
|
+
aheadBehind = await getAheadBehind(cwd, facts.resolvedBaseRef, currentBranch, {
|
|
1989
|
+
...context,
|
|
1990
|
+
facts,
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
if (baseMoved || (upstreamMoved && currentBranch === normalizedResolvedBase)) {
|
|
1994
|
+
diffStat = await getCheckoutShortstatUncached(cwd, { ...context, facts }, { throwOnGitError: true });
|
|
1995
|
+
}
|
|
1996
|
+
let upstreamStatus = facts.upstreamStatus;
|
|
1997
|
+
if (upstreamMoved && currentBranch && upstreamRef) {
|
|
1998
|
+
const normalizedUpstream = normalizeRemoteTrackingRef(upstreamRef);
|
|
1999
|
+
const normalizedComparison = comparisonRef ? normalizeRemoteTrackingRef(comparisonRef) : null;
|
|
2000
|
+
const upstreamAheadBehind = baseMoved && normalizedComparison === normalizedUpstream
|
|
2001
|
+
? aheadBehind
|
|
2002
|
+
: await getAheadBehindForComparisonRef(cwd, upstreamRef, currentBranch, context);
|
|
2003
|
+
upstreamStatus = upstreamAheadBehind
|
|
2004
|
+
? {
|
|
2005
|
+
ref: facts.upstreamStatus?.ref ?? `refs/remotes/${normalizedUpstream}`,
|
|
2006
|
+
aheadBehind: upstreamAheadBehind,
|
|
2007
|
+
}
|
|
2008
|
+
: null;
|
|
2009
|
+
}
|
|
2010
|
+
return { aheadBehind, diffStat, upstreamStatus };
|
|
2011
|
+
}
|
|
2012
|
+
export async function getCheckoutWorktreeState(cwd, context) {
|
|
2013
|
+
const [isDirty, diffStat] = await Promise.all([
|
|
2014
|
+
isWorkingTreeDirty(cwd, context),
|
|
2015
|
+
getCheckoutShortstat(cwd, context, { force: true }),
|
|
2016
|
+
]);
|
|
2017
|
+
return { isDirty, diffStat };
|
|
2018
|
+
}
|
|
2019
|
+
export function getCachedCheckoutShortstat(cwd) {
|
|
2020
|
+
return shortstatCache.get(getShortstatCacheKey(cwd));
|
|
2021
|
+
}
|
|
2022
|
+
export function warmCheckoutShortstatInBackground(cwd, context, onComplete) {
|
|
2023
|
+
const cacheKey = getShortstatCacheKey(cwd);
|
|
2024
|
+
if (shortstatCache.get(cacheKey) !== undefined || shortstatInFlight.has(cacheKey)) {
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
void getOrLoadCheckoutShortstat(cwd, context)
|
|
2028
|
+
.then(() => {
|
|
2029
|
+
onComplete?.();
|
|
2030
|
+
return;
|
|
2031
|
+
})
|
|
2032
|
+
.catch(() => {
|
|
2033
|
+
// Non-critical: keep listing path resilient even if git commands fail.
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
async function buildHighlightedTrackedDiffFile(input) {
|
|
2037
|
+
const { cwd, change, parsedFile, refsForDiff } = input;
|
|
2038
|
+
const refPath = change.oldPath ?? change.path;
|
|
2039
|
+
const [oldFileContent, newFileContent] = await Promise.all([
|
|
2040
|
+
change.isNew ? null : readGitFileContentAtRef(cwd, refsForDiff.baseRef, refPath),
|
|
2041
|
+
refsForDiff.targetRef ? readGitFileContentAtRef(cwd, refsForDiff.targetRef, change.path) : null,
|
|
2042
|
+
]);
|
|
2043
|
+
const highlightedFile = await highlightDiffWithFileContent(parsedFile, cwd, {
|
|
2044
|
+
oldFileContent,
|
|
2045
|
+
newFileContent,
|
|
2046
|
+
});
|
|
2047
|
+
return {
|
|
2048
|
+
...highlightedFile,
|
|
2049
|
+
path: change.path,
|
|
2050
|
+
...(change.oldPath ? { oldPath: change.oldPath } : {}),
|
|
2051
|
+
isNew: change.isNew,
|
|
2052
|
+
isDeleted: change.isDeleted,
|
|
2053
|
+
status: "ok",
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
2056
|
+
function isWhitespaceOnlyTrackedChange(input) {
|
|
2057
|
+
const { change, stat, ignoreWhitespace } = input;
|
|
2058
|
+
return (ignoreWhitespace &&
|
|
2059
|
+
change.status.startsWith("M") &&
|
|
2060
|
+
(!stat || (!stat.isBinary && stat.additions === 0 && stat.deletions === 0)));
|
|
2061
|
+
}
|
|
2062
|
+
async function appendStructuredTrackedDiffs(input) {
|
|
2063
|
+
const { cwd, trackedChanges, trackedNumstatByPath, trackedPlaceholderByPath, trackedDiffText, refsForDiff, ignoreWhitespace, structured, appendTrackedPlaceholderComment, } = input;
|
|
2064
|
+
const parsedTrackedFiles = trackedDiffText.length > 0 ? parseDiff(trackedDiffText) : [];
|
|
2065
|
+
const parsedTrackedByPath = new Map(parsedTrackedFiles.map((file) => [file.path, file]));
|
|
2066
|
+
for (const change of trackedChanges) {
|
|
2067
|
+
const placeholder = trackedPlaceholderByPath.get(change.path);
|
|
2068
|
+
if (placeholder) {
|
|
2069
|
+
const file = buildPlaceholderParsedDiffFile(change, {
|
|
2070
|
+
status: placeholder.status,
|
|
2071
|
+
stat: placeholder.stat,
|
|
2072
|
+
});
|
|
2073
|
+
if (!appendStructuredFile(structured, file)) {
|
|
2074
|
+
return false;
|
|
2075
|
+
}
|
|
2076
|
+
appendTrackedPlaceholderComment(change, placeholder.status);
|
|
2077
|
+
continue;
|
|
2078
|
+
}
|
|
2079
|
+
const stat = trackedNumstatByPath.get(change.path) ?? null;
|
|
2080
|
+
const parsedFile = parsedTrackedByPath.get(change.path);
|
|
2081
|
+
if (parsedFile) {
|
|
2082
|
+
const file = await buildHighlightedTrackedDiffFile({
|
|
2083
|
+
cwd,
|
|
2084
|
+
change,
|
|
2085
|
+
parsedFile,
|
|
2086
|
+
refsForDiff,
|
|
2087
|
+
});
|
|
2088
|
+
if (!appendStructuredFile(structured, file)) {
|
|
2089
|
+
return false;
|
|
2090
|
+
}
|
|
2091
|
+
continue;
|
|
2092
|
+
}
|
|
2093
|
+
// `git diff -w --name-status` can still report a modified path even when the
|
|
2094
|
+
// whitespace-filtered patch and numstat are both empty. Skip emitting a
|
|
2095
|
+
// structured placeholder in that case so whitespace-only edits truly disappear.
|
|
2096
|
+
if (isWhitespaceOnlyTrackedChange({ change, stat, ignoreWhitespace })) {
|
|
2097
|
+
continue;
|
|
2098
|
+
}
|
|
2099
|
+
const file = {
|
|
2100
|
+
path: change.path,
|
|
2101
|
+
...(change.oldPath ? { oldPath: change.oldPath } : {}),
|
|
2102
|
+
isNew: change.isNew,
|
|
2103
|
+
isDeleted: change.isDeleted,
|
|
2104
|
+
additions: stat?.additions ?? 0,
|
|
2105
|
+
deletions: stat?.deletions ?? 0,
|
|
2106
|
+
hunks: [],
|
|
2107
|
+
status: "ok",
|
|
2108
|
+
};
|
|
2109
|
+
if (!appendStructuredFile(structured, file)) {
|
|
2110
|
+
return false;
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
return true;
|
|
2114
|
+
}
|
|
2115
|
+
async function processUntrackedChange(input) {
|
|
2116
|
+
const { cwd, change, ignoreWhitespace, includeStructured, structured, appendDiff } = input;
|
|
2117
|
+
const { text, truncated, stat } = await getUntrackedDiffText(cwd, change, ignoreWhitespace);
|
|
2118
|
+
if (!includeStructured) {
|
|
2119
|
+
if (stat?.isBinary) {
|
|
2120
|
+
appendDiff(`# ${change.path}: binary diff omitted\n`);
|
|
2121
|
+
}
|
|
2122
|
+
else if (truncated) {
|
|
2123
|
+
appendDiff(`# ${change.path}: diff too large omitted\n`);
|
|
2124
|
+
}
|
|
2125
|
+
else {
|
|
2126
|
+
appendDiff(text);
|
|
2127
|
+
}
|
|
2128
|
+
return true;
|
|
2129
|
+
}
|
|
2130
|
+
if (stat?.isBinary) {
|
|
2131
|
+
if (!appendStructuredFile(structured, buildPlaceholderParsedDiffFile(change, { status: "binary", stat }))) {
|
|
2132
|
+
return false;
|
|
2133
|
+
}
|
|
2134
|
+
appendDiff(`# ${change.path}: binary diff omitted\n`);
|
|
2135
|
+
return true;
|
|
2136
|
+
}
|
|
2137
|
+
if (truncated) {
|
|
2138
|
+
if (!appendStructuredFile(structured, buildPlaceholderParsedDiffFile(change, { status: "too_large", stat }))) {
|
|
2139
|
+
return false;
|
|
2140
|
+
}
|
|
2141
|
+
appendDiff(`# ${change.path}: diff too large omitted\n`);
|
|
2142
|
+
return true;
|
|
2143
|
+
}
|
|
2144
|
+
appendDiff(text);
|
|
2145
|
+
const parsed = await parseAndHighlightDiff(text, cwd);
|
|
2146
|
+
const parsedFile = parsed[0] ??
|
|
2147
|
+
{
|
|
2148
|
+
path: change.path,
|
|
2149
|
+
isNew: change.isNew,
|
|
2150
|
+
isDeleted: change.isDeleted,
|
|
2151
|
+
additions: stat?.additions ?? 0,
|
|
2152
|
+
deletions: stat?.deletions ?? 0,
|
|
2153
|
+
hunks: [],
|
|
2154
|
+
};
|
|
2155
|
+
const file = {
|
|
2156
|
+
...parsedFile,
|
|
2157
|
+
path: change.path,
|
|
2158
|
+
isNew: change.isNew,
|
|
2159
|
+
isDeleted: change.isDeleted,
|
|
2160
|
+
status: "ok",
|
|
2161
|
+
};
|
|
2162
|
+
return appendStructuredFile(structured, file);
|
|
2163
|
+
}
|
|
2164
|
+
async function processTrackedChanges(input) {
|
|
2165
|
+
const { cwd, refsForDiff, trackedChanges, ignoreWhitespace, appendDiff } = input;
|
|
2166
|
+
const trackedNumstatByPath = trackedChanges.length > 0
|
|
2167
|
+
? await getTrackedNumstatByPath(cwd, refsForDiff, ignoreWhitespace)
|
|
2168
|
+
: new Map();
|
|
2169
|
+
const trackedDiffPaths = [];
|
|
2170
|
+
const trackedPlaceholderByPath = new Map();
|
|
2171
|
+
for (const change of trackedChanges) {
|
|
2172
|
+
const stat = trackedNumstatByPath.get(change.path) ?? null;
|
|
2173
|
+
if (stat?.isBinary) {
|
|
2174
|
+
trackedPlaceholderByPath.set(change.path, { status: "binary", stat });
|
|
2175
|
+
continue;
|
|
2176
|
+
}
|
|
2177
|
+
trackedDiffPaths.push(change.path);
|
|
2178
|
+
}
|
|
2179
|
+
let trackedDiffText = "";
|
|
2180
|
+
let trackedDiffBytes = 0;
|
|
2181
|
+
for (let start = 0; start < trackedDiffPaths.length; start += TRACKED_DIFF_BATCH_SIZE) {
|
|
2182
|
+
const paths = trackedDiffPaths.slice(start, start + TRACKED_DIFF_BATCH_SIZE);
|
|
2183
|
+
const trackedDiffs = await Promise.all(paths.map((path) => getTrackedDiffTextForPath({
|
|
2184
|
+
cwd,
|
|
2185
|
+
refsForDiff,
|
|
2186
|
+
path,
|
|
2187
|
+
ignoreWhitespace,
|
|
2188
|
+
})));
|
|
2189
|
+
for (const fileDiff of trackedDiffs) {
|
|
2190
|
+
if (fileDiff.truncated) {
|
|
2191
|
+
trackedPlaceholderByPath.set(fileDiff.path, {
|
|
2192
|
+
status: "too_large",
|
|
2193
|
+
stat: trackedNumstatByPath.get(fileDiff.path) ?? null,
|
|
2194
|
+
});
|
|
2195
|
+
continue;
|
|
2196
|
+
}
|
|
2197
|
+
const diffBytes = Buffer.byteLength(fileDiff.text, "utf8");
|
|
2198
|
+
if (trackedDiffBytes + diffBytes > TOTAL_DIFF_MAX_BYTES) {
|
|
2199
|
+
trackedPlaceholderByPath.set(fileDiff.path, {
|
|
2200
|
+
status: "too_large",
|
|
2201
|
+
stat: trackedNumstatByPath.get(fileDiff.path) ?? null,
|
|
2202
|
+
});
|
|
2203
|
+
continue;
|
|
2204
|
+
}
|
|
2205
|
+
trackedDiffBytes += diffBytes;
|
|
2206
|
+
trackedDiffText += fileDiff.text;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
appendDiff(trackedDiffText);
|
|
2210
|
+
return {
|
|
2211
|
+
trackedNumstatByPath,
|
|
2212
|
+
trackedPlaceholderByPath,
|
|
2213
|
+
trackedDiffText,
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
async function resolveCheckoutDiffRefs(cwd, compare, context) {
|
|
2217
|
+
if (compare.mode === "uncommitted") {
|
|
2218
|
+
return { baseRef: "HEAD", includeUntracked: true };
|
|
2219
|
+
}
|
|
2220
|
+
const { storedBaseRef, resolvedBaseRef } = await resolveBaseRefForCwd(cwd, context);
|
|
2221
|
+
const baseRef = resolveOperationBaseRef({
|
|
2222
|
+
storedBaseRef,
|
|
2223
|
+
resolvedBaseRef,
|
|
2224
|
+
requestedBaseRef: compare.baseRef,
|
|
2225
|
+
});
|
|
2226
|
+
if (!baseRef) {
|
|
2227
|
+
return null;
|
|
2228
|
+
}
|
|
2229
|
+
const bestBaseRef = await resolveBestComparisonBaseRef(cwd, baseRef);
|
|
2230
|
+
return {
|
|
2231
|
+
baseRef: (await tryResolveMergeBase(cwd, bestBaseRef)) ?? bestBaseRef,
|
|
2232
|
+
targetRef: "HEAD",
|
|
2233
|
+
includeUntracked: false,
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
export async function getCheckoutDiff(cwd, compare, context) {
|
|
2237
|
+
await requireGitRepo(cwd);
|
|
2238
|
+
const refsForDiff = await resolveCheckoutDiffRefs(cwd, compare, context);
|
|
2239
|
+
if (!refsForDiff) {
|
|
2240
|
+
return { diff: "" };
|
|
2241
|
+
}
|
|
2242
|
+
const ignoreWhitespace = compare.ignoreWhitespace === true;
|
|
2243
|
+
let effectiveRefsForDiff = refsForDiff;
|
|
2244
|
+
let changes;
|
|
2245
|
+
try {
|
|
2246
|
+
changes = await listCheckoutFileChanges(cwd, effectiveRefsForDiff, ignoreWhitespace);
|
|
2247
|
+
}
|
|
2248
|
+
catch (error) {
|
|
2249
|
+
if (!isUnbornHeadDiffError(error)) {
|
|
2250
|
+
throw error;
|
|
2251
|
+
}
|
|
2252
|
+
effectiveRefsForDiff = { ...refsForDiff, baseRef: EMPTY_TREE_OBJECT_ID };
|
|
2253
|
+
changes = await listCheckoutFileChanges(cwd, effectiveRefsForDiff, ignoreWhitespace);
|
|
2254
|
+
}
|
|
2255
|
+
changes.sort((a, b) => {
|
|
2256
|
+
if (a.path === b.path)
|
|
2257
|
+
return 0;
|
|
2258
|
+
return a.path < b.path ? -1 : 1;
|
|
2259
|
+
});
|
|
2260
|
+
const structured = createStructuredDiffAccumulator();
|
|
2261
|
+
let diffText = "";
|
|
2262
|
+
let diffBytes = 0;
|
|
2263
|
+
const appendDiff = (text) => {
|
|
2264
|
+
if (!text)
|
|
2265
|
+
return;
|
|
2266
|
+
if (diffBytes >= TOTAL_DIFF_MAX_BYTES)
|
|
2267
|
+
return;
|
|
2268
|
+
const buf = Buffer.from(text, "utf8");
|
|
2269
|
+
if (diffBytes + buf.length <= TOTAL_DIFF_MAX_BYTES) {
|
|
2270
|
+
diffText += text;
|
|
2271
|
+
diffBytes += buf.length;
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
const remaining = TOTAL_DIFF_MAX_BYTES - diffBytes;
|
|
2275
|
+
if (remaining > 0) {
|
|
2276
|
+
diffText += buf.subarray(0, remaining).toString("utf8");
|
|
2277
|
+
diffBytes = TOTAL_DIFF_MAX_BYTES;
|
|
2278
|
+
}
|
|
2279
|
+
};
|
|
2280
|
+
const trackedChanges = changes.filter((change) => !change.isUntracked);
|
|
2281
|
+
const untrackedChanges = changes.filter((change) => change.isUntracked === true);
|
|
2282
|
+
const trackedDiff = await processTrackedChanges({
|
|
2283
|
+
cwd,
|
|
2284
|
+
refsForDiff: effectiveRefsForDiff,
|
|
2285
|
+
trackedChanges,
|
|
2286
|
+
ignoreWhitespace,
|
|
2287
|
+
appendDiff,
|
|
2288
|
+
});
|
|
2289
|
+
const appendTrackedPlaceholderComment = (change, status) => {
|
|
2290
|
+
if (status === "binary") {
|
|
2291
|
+
appendDiff(`# ${change.path}: binary diff omitted\n`);
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
appendDiff(`# ${change.path}: diff too large omitted\n`);
|
|
2295
|
+
};
|
|
2296
|
+
if (compare.includeStructured) {
|
|
2297
|
+
const didAppendTrackedDiffs = await appendStructuredTrackedDiffs({
|
|
2298
|
+
cwd,
|
|
2299
|
+
trackedChanges,
|
|
2300
|
+
trackedNumstatByPath: trackedDiff.trackedNumstatByPath,
|
|
2301
|
+
trackedPlaceholderByPath: trackedDiff.trackedPlaceholderByPath,
|
|
2302
|
+
trackedDiffText: trackedDiff.trackedDiffText,
|
|
2303
|
+
refsForDiff: effectiveRefsForDiff,
|
|
2304
|
+
ignoreWhitespace,
|
|
2305
|
+
structured,
|
|
2306
|
+
appendTrackedPlaceholderComment,
|
|
2307
|
+
});
|
|
2308
|
+
if (!didAppendTrackedDiffs) {
|
|
2309
|
+
return { diff: "", structured: [], diffTooLarge: true };
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
else {
|
|
2313
|
+
for (const change of trackedChanges) {
|
|
2314
|
+
const placeholder = trackedDiff.trackedPlaceholderByPath.get(change.path);
|
|
2315
|
+
if (placeholder) {
|
|
2316
|
+
appendTrackedPlaceholderComment(change, placeholder.status);
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
for (const change of untrackedChanges) {
|
|
2321
|
+
if (diffBytes >= TOTAL_DIFF_MAX_BYTES) {
|
|
2322
|
+
break;
|
|
2323
|
+
}
|
|
2324
|
+
const didAppendUntrackedDiff = await processUntrackedChange({
|
|
2325
|
+
cwd,
|
|
2326
|
+
change,
|
|
2327
|
+
ignoreWhitespace,
|
|
2328
|
+
includeStructured: compare.includeStructured === true,
|
|
2329
|
+
structured,
|
|
2330
|
+
appendDiff,
|
|
2331
|
+
});
|
|
2332
|
+
if (!didAppendUntrackedDiff) {
|
|
2333
|
+
return { diff: "", structured: [], diffTooLarge: true };
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
if (compare.includeStructured) {
|
|
2337
|
+
return { diff: diffText, structured: structured.files };
|
|
2338
|
+
}
|
|
2339
|
+
return { diff: diffText };
|
|
2340
|
+
}
|
|
2341
|
+
export async function commitChanges(cwd, options) {
|
|
2342
|
+
await requireGitRepo(cwd);
|
|
2343
|
+
if (options.addAll ?? true) {
|
|
2344
|
+
await runGitCommand(["add", "-A"], { cwd, timeout: 120000 });
|
|
2345
|
+
}
|
|
2346
|
+
await runGitCommand(["commit", "-m", options.message], {
|
|
2347
|
+
cwd,
|
|
2348
|
+
timeout: 120000,
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
export async function commitAll(cwd, message) {
|
|
2352
|
+
await commitChanges(cwd, { message, addAll: true });
|
|
2353
|
+
}
|
|
2354
|
+
export async function discardChanges(cwd, pathspecs) {
|
|
2355
|
+
await requireGitRepo(cwd);
|
|
2356
|
+
if (pathspecs.length === 0) {
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
try {
|
|
2360
|
+
await runGitCommand(["--literal-pathspecs", "reset", "-q", "HEAD", "--", ...pathspecs], {
|
|
2361
|
+
cwd,
|
|
2362
|
+
timeout: DISCARD_CHANGES_TIMEOUT_MS,
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
catch {
|
|
2366
|
+
// Why: unborn HEAD has no commit for reset, so remove the paths directly from the index.
|
|
2367
|
+
await runGitCommand(["--literal-pathspecs", "rm", "--cached", "-r", "-q", "--ignore-unmatch", "--", ...pathspecs], {
|
|
2368
|
+
cwd,
|
|
2369
|
+
timeout: DISCARD_CHANGES_TIMEOUT_MS,
|
|
2370
|
+
});
|
|
2371
|
+
}
|
|
2372
|
+
// With everything unstaged, the remaining state is only worktree
|
|
2373
|
+
// modifications/deletions (restore from the index) and untracked files
|
|
2374
|
+
// (clean). Classify from porcelain so each path gets the command that
|
|
2375
|
+
// actually applies to it.
|
|
2376
|
+
const status = await runGitCommand(["--literal-pathspecs", "status", "--porcelain=v1", "-z", "--", ...pathspecs], {
|
|
2377
|
+
cwd,
|
|
2378
|
+
timeout: DISCARD_CHANGES_TIMEOUT_MS,
|
|
2379
|
+
});
|
|
2380
|
+
const tracked = [];
|
|
2381
|
+
const untracked = [];
|
|
2382
|
+
const tokens = status.stdout.split("\0");
|
|
2383
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
2384
|
+
const token = tokens[index];
|
|
2385
|
+
if (token.length < 4) {
|
|
2386
|
+
continue;
|
|
2387
|
+
}
|
|
2388
|
+
const state = token.slice(0, 2);
|
|
2389
|
+
const filePath = token.slice(3);
|
|
2390
|
+
if (state.startsWith("R") || state.startsWith("C")) {
|
|
2391
|
+
// Rename/copy entries carry the source path as the next NUL token.
|
|
2392
|
+
index += 1;
|
|
2393
|
+
}
|
|
2394
|
+
if (state === "??") {
|
|
2395
|
+
untracked.push(filePath);
|
|
2396
|
+
continue;
|
|
2397
|
+
}
|
|
2398
|
+
tracked.push(filePath);
|
|
2399
|
+
}
|
|
2400
|
+
if (tracked.length > 0) {
|
|
2401
|
+
await runGitCommand(["--literal-pathspecs", "checkout", "-q", "--", ...tracked], {
|
|
2402
|
+
cwd,
|
|
2403
|
+
timeout: DISCARD_CHANGES_TIMEOUT_MS,
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
2406
|
+
if (untracked.length > 0) {
|
|
2407
|
+
await runGitCommand(["--literal-pathspecs", "clean", "-fd", "-q", "--", ...untracked], {
|
|
2408
|
+
cwd,
|
|
2409
|
+
timeout: DISCARD_CHANGES_TIMEOUT_MS,
|
|
2410
|
+
});
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
async function detectAndThrowMergeToBaseConflict(input) {
|
|
2414
|
+
const { operationCwd, error, baseRef, currentBranch } = input;
|
|
2415
|
+
const errorDetails = error instanceof Error
|
|
2416
|
+
? `${error.message}\n${getErrorStderr(error)}\n${getErrorStdout(error)}`
|
|
2417
|
+
: String(error);
|
|
2418
|
+
try {
|
|
2419
|
+
const [unmergedOutput, lsFilesOutput, statusOutput] = await Promise.all([
|
|
2420
|
+
runGitCommand(["diff", "--name-only", "--diff-filter=U"], { cwd: operationCwd }),
|
|
2421
|
+
runGitCommand(["ls-files", "-u"], { cwd: operationCwd }),
|
|
2422
|
+
runGitCommand(["status", "--porcelain"], { cwd: operationCwd }),
|
|
2423
|
+
]);
|
|
2424
|
+
const statusConflicts = statusOutput.stdout
|
|
2425
|
+
.split("\n")
|
|
2426
|
+
.map((line) => line.trim())
|
|
2427
|
+
.filter(Boolean)
|
|
2428
|
+
.filter((line) => /^(UU|AA|DD|AU|UA|UD|DU)\s/.test(line))
|
|
2429
|
+
.map((line) => line.slice(3).trim());
|
|
2430
|
+
const conflicts = [
|
|
2431
|
+
...unmergedOutput.stdout
|
|
2432
|
+
.split("\n")
|
|
2433
|
+
.map((line) => line.trim())
|
|
2434
|
+
.filter(Boolean),
|
|
2435
|
+
...lsFilesOutput.stdout
|
|
2436
|
+
.split("\n")
|
|
2437
|
+
.map((line) => line.trim())
|
|
2438
|
+
.filter(Boolean)
|
|
2439
|
+
.map((line) => line.split("\t").at(-1) ?? ""),
|
|
2440
|
+
...statusConflicts,
|
|
2441
|
+
].filter(Boolean);
|
|
2442
|
+
const conflictDetected = conflicts.length > 0 || /CONFLICT|Automatic merge failed/i.test(errorDetails);
|
|
2443
|
+
if (conflictDetected) {
|
|
2444
|
+
try {
|
|
2445
|
+
await runGitCommand(["merge", "--abort"], { cwd: operationCwd, timeout: 120000 });
|
|
2446
|
+
}
|
|
2447
|
+
catch {
|
|
2448
|
+
// ignore
|
|
2449
|
+
}
|
|
2450
|
+
throw new MergeConflictError({
|
|
2451
|
+
baseRef,
|
|
2452
|
+
currentBranch,
|
|
2453
|
+
conflictFiles: conflicts.length > 0 ? conflicts : [],
|
|
2454
|
+
});
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
catch (innerError) {
|
|
2458
|
+
if (innerError instanceof MergeConflictError) {
|
|
2459
|
+
throw innerError;
|
|
2460
|
+
}
|
|
2461
|
+
// ignore detection failures
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
export async function mergeToBase(cwd, options = {}, context) {
|
|
2465
|
+
await requireGitRepo(cwd);
|
|
2466
|
+
const currentBranch = await getCurrentBranch(cwd);
|
|
2467
|
+
const { storedBaseRef, resolvedBaseRef } = await resolveBaseRefForCwd(cwd, context);
|
|
2468
|
+
const baseRef = resolveOperationBaseRef({
|
|
2469
|
+
storedBaseRef,
|
|
2470
|
+
resolvedBaseRef,
|
|
2471
|
+
requestedBaseRef: options.baseRef,
|
|
2472
|
+
});
|
|
2473
|
+
if (!baseRef) {
|
|
2474
|
+
throw new Error("Unable to determine base branch for merge");
|
|
2475
|
+
}
|
|
2476
|
+
if (!currentBranch) {
|
|
2477
|
+
throw new Error("Unable to determine current branch for merge");
|
|
2478
|
+
}
|
|
2479
|
+
const normalizedBaseRef = resolveMergeTargetBranch(baseRef);
|
|
2480
|
+
const currentWorktreeRoot = (await getWorktreeRoot(cwd, context)) ?? cwd;
|
|
2481
|
+
if (normalizedBaseRef === currentBranch) {
|
|
2482
|
+
return currentWorktreeRoot;
|
|
2483
|
+
}
|
|
2484
|
+
const baseWorktree = await getWorktreePathForBranch(cwd, normalizedBaseRef);
|
|
2485
|
+
const operationCwd = baseWorktree ?? currentWorktreeRoot;
|
|
2486
|
+
const isSameCheckout = resolve(operationCwd) === resolve(currentWorktreeRoot);
|
|
2487
|
+
const originalBranch = await getCurrentBranch(operationCwd);
|
|
2488
|
+
const mode = options.mode ?? "merge";
|
|
2489
|
+
try {
|
|
2490
|
+
await runGitCommand(["checkout", normalizedBaseRef], {
|
|
2491
|
+
cwd: operationCwd,
|
|
2492
|
+
timeout: 120000,
|
|
2493
|
+
});
|
|
2494
|
+
if (mode === "squash") {
|
|
2495
|
+
await runGitCommand(["merge", "--squash", currentBranch], {
|
|
2496
|
+
cwd: operationCwd,
|
|
2497
|
+
timeout: 120000,
|
|
2498
|
+
});
|
|
2499
|
+
const message = options.commitMessage ?? `Squash merge ${currentBranch} into ${normalizedBaseRef}`;
|
|
2500
|
+
await runGitCommand(["commit", "-m", message], {
|
|
2501
|
+
cwd: operationCwd,
|
|
2502
|
+
timeout: 120000,
|
|
2503
|
+
});
|
|
2504
|
+
}
|
|
2505
|
+
else {
|
|
2506
|
+
await runGitCommand(["merge", currentBranch], { cwd: operationCwd, timeout: 120000 });
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
catch (error) {
|
|
2510
|
+
await detectAndThrowMergeToBaseConflict({
|
|
2511
|
+
operationCwd,
|
|
2512
|
+
error,
|
|
2513
|
+
baseRef: normalizedBaseRef,
|
|
2514
|
+
currentBranch,
|
|
2515
|
+
});
|
|
2516
|
+
throw error;
|
|
2517
|
+
}
|
|
2518
|
+
finally {
|
|
2519
|
+
if (isSameCheckout && originalBranch && originalBranch !== normalizedBaseRef) {
|
|
2520
|
+
try {
|
|
2521
|
+
await runGitCommand(["checkout", originalBranch], {
|
|
2522
|
+
cwd: operationCwd,
|
|
2523
|
+
timeout: 120000,
|
|
2524
|
+
});
|
|
2525
|
+
}
|
|
2526
|
+
catch {
|
|
2527
|
+
// ignore
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
return operationCwd;
|
|
2532
|
+
}
|
|
2533
|
+
export async function mergeFromBase(cwd, options = {}, context) {
|
|
2534
|
+
await requireGitRepo(cwd);
|
|
2535
|
+
const currentBranch = await getCurrentBranch(cwd);
|
|
2536
|
+
if (!currentBranch || currentBranch === "HEAD") {
|
|
2537
|
+
throw new Error("Unable to determine current branch for merge");
|
|
2538
|
+
}
|
|
2539
|
+
const { storedBaseRef, resolvedBaseRef } = await resolveBaseRefForCwd(cwd, context);
|
|
2540
|
+
const baseRef = resolveOperationBaseRef({
|
|
2541
|
+
storedBaseRef,
|
|
2542
|
+
resolvedBaseRef,
|
|
2543
|
+
requestedBaseRef: options.baseRef,
|
|
2544
|
+
});
|
|
2545
|
+
if (!baseRef) {
|
|
2546
|
+
throw new Error("Unable to determine base branch for merge");
|
|
2547
|
+
}
|
|
2548
|
+
const requireCleanTarget = options.requireCleanTarget ?? true;
|
|
2549
|
+
if (requireCleanTarget) {
|
|
2550
|
+
const { stdout } = await runGitCommand(["status", "--porcelain"], {
|
|
2551
|
+
cwd,
|
|
2552
|
+
envOverlay: READ_ONLY_GIT_ENV,
|
|
2553
|
+
});
|
|
2554
|
+
if (stdout.trim().length > 0) {
|
|
2555
|
+
throw new Error("Working directory has uncommitted changes.");
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
const bestBaseRef = await resolveMostAheadBaseRef(cwd, baseRef);
|
|
2559
|
+
if (bestBaseRef === currentBranch) {
|
|
2560
|
+
return;
|
|
2561
|
+
}
|
|
2562
|
+
try {
|
|
2563
|
+
await runGitCommand(["merge", bestBaseRef], { cwd, timeout: 120000 });
|
|
2564
|
+
}
|
|
2565
|
+
catch (error) {
|
|
2566
|
+
await detectAndThrowMergeFromBaseConflict({
|
|
2567
|
+
cwd,
|
|
2568
|
+
error,
|
|
2569
|
+
baseRef: bestBaseRef,
|
|
2570
|
+
currentBranch,
|
|
2571
|
+
});
|
|
2572
|
+
throw error;
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
async function detectAndThrowMergeFromBaseConflict(input) {
|
|
2576
|
+
const { cwd, error, baseRef, currentBranch } = input;
|
|
2577
|
+
const errorDetails = error instanceof Error
|
|
2578
|
+
? `${error.message}\n${getErrorStderr(error)}\n${getErrorStdout(error)}`
|
|
2579
|
+
: String(error);
|
|
2580
|
+
try {
|
|
2581
|
+
const [unmergedOutput, lsFilesOutput, statusOutput] = await Promise.all([
|
|
2582
|
+
runGitCommand(["diff", "--name-only", "--diff-filter=U"], { cwd }),
|
|
2583
|
+
runGitCommand(["ls-files", "-u"], { cwd }),
|
|
2584
|
+
runGitCommand(["status", "--porcelain"], { cwd }),
|
|
2585
|
+
]);
|
|
2586
|
+
const statusConflicts = statusOutput.stdout
|
|
2587
|
+
.split("\n")
|
|
2588
|
+
.map((line) => line.trim())
|
|
2589
|
+
.filter(Boolean)
|
|
2590
|
+
.filter((line) => /^(UU|AA|DD|AU|UA|UD|DU)\s/.test(line))
|
|
2591
|
+
.map((line) => line.slice(3).trim());
|
|
2592
|
+
const conflicts = [
|
|
2593
|
+
...unmergedOutput.stdout
|
|
2594
|
+
.split("\n")
|
|
2595
|
+
.map((line) => line.trim())
|
|
2596
|
+
.filter(Boolean),
|
|
2597
|
+
...lsFilesOutput.stdout
|
|
2598
|
+
.split("\n")
|
|
2599
|
+
.map((line) => line.trim())
|
|
2600
|
+
.filter(Boolean)
|
|
2601
|
+
.map((line) => line.split("\t").at(-1) ?? ""),
|
|
2602
|
+
...statusConflicts,
|
|
2603
|
+
].filter(Boolean);
|
|
2604
|
+
const conflictDetected = conflicts.length > 0 || /CONFLICT|Automatic merge failed/i.test(errorDetails);
|
|
2605
|
+
if (conflictDetected) {
|
|
2606
|
+
try {
|
|
2607
|
+
await runGitCommand(["merge", "--abort"], { cwd, timeout: 120000 });
|
|
2608
|
+
}
|
|
2609
|
+
catch {
|
|
2610
|
+
// ignore
|
|
2611
|
+
}
|
|
2612
|
+
throw new MergeFromBaseConflictError({
|
|
2613
|
+
baseRef,
|
|
2614
|
+
currentBranch,
|
|
2615
|
+
conflictFiles: conflicts.length > 0 ? conflicts : [],
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
catch (innerError) {
|
|
2620
|
+
if (innerError instanceof MergeFromBaseConflictError) {
|
|
2621
|
+
throw innerError;
|
|
2622
|
+
}
|
|
2623
|
+
// ignore detection failures
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
export async function pullCurrentBranch(cwd, forgeService) {
|
|
2627
|
+
await requireGitRepo(cwd);
|
|
2628
|
+
const currentBranch = await getCurrentBranch(cwd);
|
|
2629
|
+
if (!currentBranch || currentBranch === "HEAD") {
|
|
2630
|
+
throw new Error("Unable to determine current branch for pull");
|
|
2631
|
+
}
|
|
2632
|
+
const hasRemote = await hasOriginRemote(cwd);
|
|
2633
|
+
if (!hasRemote) {
|
|
2634
|
+
throw new Error("Remote 'origin' is not configured.");
|
|
2635
|
+
}
|
|
2636
|
+
try {
|
|
2637
|
+
await runGitCommand(["pull"], { cwd, timeout: 120000 });
|
|
2638
|
+
forgeService?.invalidate({ cwd });
|
|
2639
|
+
}
|
|
2640
|
+
catch (error) {
|
|
2641
|
+
await abortGitPullConflictState(cwd);
|
|
2642
|
+
throw error;
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
export async function pushCurrentBranch(cwd, forgeService) {
|
|
2646
|
+
await requireGitRepo(cwd);
|
|
2647
|
+
const currentBranch = await getCurrentBranch(cwd);
|
|
2648
|
+
if (!currentBranch || currentBranch === "HEAD") {
|
|
2649
|
+
throw new Error("Unable to determine current branch for push");
|
|
2650
|
+
}
|
|
2651
|
+
const configuredPushTarget = await getCurrentBranchConfiguredPushTarget(cwd, currentBranch);
|
|
2652
|
+
if (configuredPushTarget) {
|
|
2653
|
+
await runGitCommand(["push", configuredPushTarget.remoteName, `HEAD:refs/heads/${configuredPushTarget.headRef}`], { cwd, timeout: 120000 });
|
|
2654
|
+
await refreshCurrentBranchTrackedRefAfterPush(cwd, currentBranch, configuredPushTarget);
|
|
2655
|
+
forgeService?.invalidate({ cwd });
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
const upstreamTarget = await getCurrentBranchUpstreamPushTarget(cwd, currentBranch);
|
|
2659
|
+
if (upstreamTarget) {
|
|
2660
|
+
await runGitCommand(["push", "-u", upstreamTarget.remoteName, `HEAD:refs/heads/${upstreamTarget.headRef}`], { cwd, timeout: 120000 });
|
|
2661
|
+
forgeService?.invalidate({ cwd });
|
|
2662
|
+
return;
|
|
2663
|
+
}
|
|
2664
|
+
const hasRemote = await hasOriginRemote(cwd);
|
|
2665
|
+
if (!hasRemote) {
|
|
2666
|
+
throw new Error("Remote 'origin' is not configured.");
|
|
2667
|
+
}
|
|
2668
|
+
await runGitCommand(["push", "-u", "origin", currentBranch], { cwd, timeout: 120000 });
|
|
2669
|
+
forgeService?.invalidate({ cwd });
|
|
2670
|
+
}
|
|
2671
|
+
async function getCurrentBranchConfiguredPushTarget(cwd, currentBranch) {
|
|
2672
|
+
const remoteName = await getGitConfigValue(cwd, `branch.${currentBranch}.pushRemote`);
|
|
2673
|
+
const pushRefspec = remoteName ? await getGitConfigValue(cwd, `remote.${remoteName}.push`) : null;
|
|
2674
|
+
const headRef = parseHeadPushRefspec(pushRefspec);
|
|
2675
|
+
if (!remoteName || !headRef) {
|
|
2676
|
+
return null;
|
|
2677
|
+
}
|
|
2678
|
+
const remoteUrl = await getGitConfigValue(cwd, `remote.${remoteName}.url`);
|
|
2679
|
+
return remoteUrl ? { remoteName, headRef } : null;
|
|
2680
|
+
}
|
|
2681
|
+
async function refreshCurrentBranchTrackedRefAfterPush(cwd, currentBranch, pushedTarget) {
|
|
2682
|
+
const trackingRemoteName = await getGitConfigValue(cwd, `branch.${currentBranch}.remote`);
|
|
2683
|
+
const trackingMergeRef = await getGitConfigValue(cwd, `branch.${currentBranch}.merge`);
|
|
2684
|
+
const trackingHeadRef = parseBranchMergeHeadRef(trackingMergeRef);
|
|
2685
|
+
if (!trackingRemoteName && !trackingMergeRef) {
|
|
2686
|
+
const updated = await updateRemoteTrackingRef(cwd, pushedTarget.remoteName, pushedTarget.headRef);
|
|
2687
|
+
if (!updated) {
|
|
2688
|
+
return;
|
|
2689
|
+
}
|
|
2690
|
+
await runGitCommand(["config", `branch.${currentBranch}.remote`, pushedTarget.remoteName], {
|
|
2691
|
+
cwd,
|
|
2692
|
+
});
|
|
2693
|
+
await runGitCommand(["config", `branch.${currentBranch}.merge`, `refs/heads/${pushedTarget.headRef}`], {
|
|
2694
|
+
cwd,
|
|
2695
|
+
});
|
|
2696
|
+
return;
|
|
2697
|
+
}
|
|
2698
|
+
if (!trackingRemoteName || trackingHeadRef !== pushedTarget.headRef) {
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
const [trackingRemotePushUrl, pushedRemotePushUrl] = await Promise.all([
|
|
2702
|
+
getGitRemotePushUrl(cwd, trackingRemoteName),
|
|
2703
|
+
getGitRemotePushUrl(cwd, pushedTarget.remoteName),
|
|
2704
|
+
]);
|
|
2705
|
+
if (!trackingRemotePushUrl || trackingRemotePushUrl !== pushedRemotePushUrl) {
|
|
2706
|
+
return;
|
|
2707
|
+
}
|
|
2708
|
+
await updateRemoteTrackingRef(cwd, trackingRemoteName, trackingHeadRef);
|
|
2709
|
+
}
|
|
2710
|
+
async function updateRemoteTrackingRef(cwd, remoteName, headRef) {
|
|
2711
|
+
const trackingRef = `refs/remotes/${remoteName}/${headRef}`;
|
|
2712
|
+
const checkRef = await runGitCommand(["check-ref-format", trackingRef], {
|
|
2713
|
+
cwd,
|
|
2714
|
+
acceptExitCodes: [0, 1],
|
|
2715
|
+
});
|
|
2716
|
+
if (checkRef.exitCode !== 0) {
|
|
2717
|
+
return false;
|
|
2718
|
+
}
|
|
2719
|
+
await runGitCommand(["update-ref", trackingRef, "HEAD"], { cwd, timeout: 120000 });
|
|
2720
|
+
return true;
|
|
2721
|
+
}
|
|
2722
|
+
async function getCurrentBranchUpstreamPushTarget(cwd, currentBranch) {
|
|
2723
|
+
const remoteName = await getGitConfigValue(cwd, `branch.${currentBranch}.remote`);
|
|
2724
|
+
const mergeRef = remoteName
|
|
2725
|
+
? await getGitConfigValue(cwd, `branch.${currentBranch}.merge`)
|
|
2726
|
+
: null;
|
|
2727
|
+
const headRef = parseBranchMergeHeadRef(mergeRef);
|
|
2728
|
+
if (!remoteName || !headRef) {
|
|
2729
|
+
return null;
|
|
2730
|
+
}
|
|
2731
|
+
const remoteUrl = await getGitConfigValue(cwd, `remote.${remoteName}.url`);
|
|
2732
|
+
return remoteUrl ? { remoteName, headRef } : null;
|
|
2733
|
+
}
|
|
2734
|
+
function parseHeadPushRefspec(refspec) {
|
|
2735
|
+
const prefix = "HEAD:refs/heads/";
|
|
2736
|
+
const normalized = refspec?.trim().replace(/^\+/, "");
|
|
2737
|
+
if (!normalized?.startsWith(prefix)) {
|
|
2738
|
+
return null;
|
|
2739
|
+
}
|
|
2740
|
+
const headRef = normalized.slice(prefix.length).trim();
|
|
2741
|
+
return headRef.length > 0 ? headRef : null;
|
|
2742
|
+
}
|
|
2743
|
+
function buildPullRequestStatusResult(status, authState) {
|
|
2744
|
+
return { status, authState, githubFeaturesEnabled: authState === "authenticated" };
|
|
2745
|
+
}
|
|
2746
|
+
/** True for the CLI-missing / authentication errors of any supported forge. */
|
|
2747
|
+
export function isForgeAuthError(error) {
|
|
2748
|
+
return error instanceof ForgeCliMissingError || error instanceof ForgeAuthenticationError;
|
|
2749
|
+
}
|
|
2750
|
+
/**
|
|
2751
|
+
* Map a forge CLI failure to an auth state. A missing-CLI error means the user
|
|
2752
|
+
* must install the tool; anything else surfaced as an auth probe failure means
|
|
2753
|
+
* they must sign in.
|
|
2754
|
+
*/
|
|
2755
|
+
export function forgeAuthStateFromError(error) {
|
|
2756
|
+
if (error instanceof ForgeCliMissingError) {
|
|
2757
|
+
return "cli_missing";
|
|
2758
|
+
}
|
|
2759
|
+
return "unauthenticated";
|
|
2760
|
+
}
|
|
2761
|
+
export async function createPullRequest(cwd, options, forgeService = createGitHubService(), context) {
|
|
2762
|
+
await requireGitRepo(cwd);
|
|
2763
|
+
const head = options.head ?? (await getCurrentBranch(cwd));
|
|
2764
|
+
const { storedBaseRef, resolvedBaseRef } = await resolveBaseRefForCwd(cwd, context);
|
|
2765
|
+
const base = resolveOperationBaseRef({
|
|
2766
|
+
storedBaseRef,
|
|
2767
|
+
resolvedBaseRef,
|
|
2768
|
+
requestedBaseRef: options.base,
|
|
2769
|
+
});
|
|
2770
|
+
if (!head) {
|
|
2771
|
+
throw new Error("Unable to determine head branch for PR");
|
|
2772
|
+
}
|
|
2773
|
+
if (!base) {
|
|
2774
|
+
throw new Error("Unable to determine base branch for PR");
|
|
2775
|
+
}
|
|
2776
|
+
const normalizedBase = branchNameFromRef(base);
|
|
2777
|
+
// The push deliberately happens before the adapter resolves the target
|
|
2778
|
+
// repository: slug resolution is adapter-internal (e.g. `gh repo view`, which
|
|
2779
|
+
// handles GHES and renamed repos), and the RPC path only reaches here after
|
|
2780
|
+
// the forge resolver has already matched the origin remote to a forge. If the
|
|
2781
|
+
// adapter still fails after the push, retrying is safe — the non-force push
|
|
2782
|
+
// of the head branch is idempotent.
|
|
2783
|
+
await runGitCommand(["push", "-u", "origin", head], { cwd, timeout: 120000 });
|
|
2784
|
+
const result = await forgeService.createPullRequest({
|
|
2785
|
+
cwd,
|
|
2786
|
+
title: options.title,
|
|
2787
|
+
body: options.body,
|
|
2788
|
+
head,
|
|
2789
|
+
base: normalizedBase,
|
|
2790
|
+
});
|
|
2791
|
+
forgeService.invalidate({ cwd });
|
|
2792
|
+
return result;
|
|
2793
|
+
}
|
|
2794
|
+
export async function getPullRequestStatus(cwd, forgeService = createGitHubService(), options, context) {
|
|
2795
|
+
const headSha = await getCurrentHeadSha(cwd, context);
|
|
2796
|
+
const cacheKey = getPullRequestStatusCacheKey(cwd, headSha);
|
|
2797
|
+
if (!options?.force) {
|
|
2798
|
+
const cached = pullRequestStatusCache.get(cacheKey);
|
|
2799
|
+
if (cached) {
|
|
2800
|
+
return cached;
|
|
2801
|
+
}
|
|
2802
|
+
const existing = pullRequestStatusInFlight.get(cacheKey);
|
|
2803
|
+
if (existing) {
|
|
2804
|
+
return existing;
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
const lookup = getPullRequestStatusUncached(cwd, forgeService, options, context, headSha)
|
|
2808
|
+
.then((status) => {
|
|
2809
|
+
pullRequestStatusCache.set(cacheKey, status);
|
|
2810
|
+
rememberPullRequestStatus(cacheKey, status);
|
|
2811
|
+
return status;
|
|
2812
|
+
})
|
|
2813
|
+
.catch((error) => {
|
|
2814
|
+
if (!options?.force && error instanceof ForgeCommandError) {
|
|
2815
|
+
const stale = lastSuccessfulPullRequestStatus.get(cacheKey);
|
|
2816
|
+
if (stale) {
|
|
2817
|
+
return stale;
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
throw error;
|
|
2821
|
+
})
|
|
2822
|
+
.finally(() => {
|
|
2823
|
+
pullRequestStatusInFlight.delete(cacheKey);
|
|
2824
|
+
});
|
|
2825
|
+
pullRequestStatusInFlight.set(cacheKey, lookup);
|
|
2826
|
+
return lookup;
|
|
2827
|
+
}
|
|
2828
|
+
async function getPullRequestStatusUncached(cwd, forgeService, options, context, headSha) {
|
|
2829
|
+
const unavailable = getUnavailablePullRequestStatus(context?.facts);
|
|
2830
|
+
if (unavailable)
|
|
2831
|
+
return unavailable;
|
|
2832
|
+
if (!context?.facts?.isGit) {
|
|
2833
|
+
await requireGitRepo(cwd, context);
|
|
2834
|
+
}
|
|
2835
|
+
const head = context?.facts?.isGit
|
|
2836
|
+
? context.facts.currentBranch
|
|
2837
|
+
: await getCurrentBranch(cwd, context);
|
|
2838
|
+
if (!head) {
|
|
2839
|
+
return buildPullRequestStatusResult(null, "no_remote");
|
|
2840
|
+
}
|
|
2841
|
+
try {
|
|
2842
|
+
const resolvedLookupTarget = await resolvePullRequestStatusLookupTarget(cwd, head, context);
|
|
2843
|
+
const lookupTarget = headSha && !resolvedLookupTarget.headSha
|
|
2844
|
+
? { ...resolvedLookupTarget, headSha }
|
|
2845
|
+
: resolvedLookupTarget;
|
|
2846
|
+
let status;
|
|
2847
|
+
if (options?.force) {
|
|
2848
|
+
const reason = options.reason;
|
|
2849
|
+
if (!reason) {
|
|
2850
|
+
throw new Error("Forced PR status read requires a reason");
|
|
2851
|
+
}
|
|
2852
|
+
status = await forgeService.getCurrentPullRequestStatus({
|
|
2853
|
+
cwd,
|
|
2854
|
+
...lookupTarget,
|
|
2855
|
+
force: true,
|
|
2856
|
+
reason,
|
|
2857
|
+
});
|
|
2858
|
+
}
|
|
2859
|
+
else {
|
|
2860
|
+
status = await forgeService.getCurrentPullRequestStatus({
|
|
2861
|
+
cwd,
|
|
2862
|
+
...lookupTarget,
|
|
2863
|
+
reason: options?.reason,
|
|
2864
|
+
});
|
|
2865
|
+
}
|
|
2866
|
+
return buildPullRequestStatusResult(status, "authenticated");
|
|
2867
|
+
}
|
|
2868
|
+
catch (error) {
|
|
2869
|
+
if (isForgeAuthError(error)) {
|
|
2870
|
+
return buildPullRequestStatusResult(null, forgeAuthStateFromError(error));
|
|
2871
|
+
}
|
|
2872
|
+
throw error;
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
function getUnavailablePullRequestStatus(facts) {
|
|
2876
|
+
if (facts?.isGit === false) {
|
|
2877
|
+
return buildPullRequestStatusResult(null, "no_remote");
|
|
2878
|
+
}
|
|
2879
|
+
if (facts?.isGit === true &&
|
|
2880
|
+
facts.paseoWorktree.isPaseoOwnedWorktree &&
|
|
2881
|
+
facts.pullRequestLookupTarget === null) {
|
|
2882
|
+
return buildPullRequestStatusResult(null, "authenticated");
|
|
2883
|
+
}
|
|
2884
|
+
return null;
|
|
2885
|
+
}
|
|
2886
|
+
//# sourceMappingURL=checkout-git.js.map
|