@aiworker/web 1.22.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/bin/cli.js +14 -4
- package/bin/cli.test.js +165 -73
- package/bin/lib/cli-api-target.js +2 -2
- package/bin/lib/cli-args.js +119 -119
- package/bin/lib/cli-lifecycle.js +9 -9
- package/bin/lib/cli-network.js +3 -3
- package/bin/lib/cli-paths.js +3 -3
- package/bin/lib/cli-ports.js +2 -2
- package/bin/lib/cli-process.js +4 -4
- package/bin/lib/cli-startup.js +64 -15
- package/bin/lib/cli-tunnel-profiles.js +1 -1
- package/bin/lib/cli-tunnel-utils.js +2 -2
- package/bin/lib/commands-connect-url.js +19 -19
- package/bin/lib/commands-lifecycle.js +27 -27
- package/bin/lib/commands-logs.js +5 -5
- package/bin/lib/commands-models.js +1 -1
- package/bin/lib/commands-projects.js +1 -1
- package/bin/lib/commands-schedule.js +1 -1
- package/bin/lib/commands-serve.js +29 -29
- package/bin/lib/commands-session.js +1 -1
- package/bin/lib/commands-startup.js +45 -10
- package/bin/lib/commands-status.js +1 -1
- package/bin/lib/commands-tunnel.js +40 -40
- package/bin/lib/commands-update.js +1 -1
- package/bin/lib/commands-update.test.js +6 -6
- package/dist/assets/{CodeMirrorCommentWidgets-B3u7UIcE.js → CodeMirrorCommentWidgets-B1Gizi3a.js} +1 -1
- package/dist/assets/DiffView-DXjVsEOw.js +4 -0
- package/dist/assets/DirtyBranchSwitchDialog-aig6zJjg.js +1 -0
- package/dist/assets/FilesView-CJdG0-wk.js +17 -0
- package/dist/assets/GitView-BXsS3jG6.js +6 -0
- package/dist/assets/{JsonTreeView-B0BdZEHG.js → JsonTreeView-D_hpXBnF.js} +1 -1
- package/dist/assets/LinearIssuesView-BoUiLQG1.js +1 -0
- package/dist/assets/MarkdownImageGallery-B_B_a9KS.js +1 -0
- package/dist/assets/MarkdownRendererImpl-xgyyLiBM.js +12 -0
- package/dist/assets/OnboardingScreen-DIp84OWt.js +1 -0
- package/dist/assets/{PatchDiff-DvfqDql7.js → PatchDiff-8p6nLJaU.js} +1 -1
- package/dist/assets/PierreDiffViewer-DjC8nTk9.js +161 -0
- package/dist/assets/PlanView-CJTmOF6I.js +5 -0
- package/dist/assets/SettingsView-DD-JqIJx.js +28 -0
- package/dist/assets/{SettingsWindow-i45U1Cy1.js → SettingsWindow-Cpb4hM6q.js} +1 -1
- package/dist/assets/SymbolsNerdFontMono-Regular-aK5vsLov.woff2 +0 -0
- package/dist/assets/{ToolOutputDialog-BTwyh5OF.js → ToolOutputDialog-DfaKZXNU.js} +6 -6
- package/dist/assets/ToolPartDiffPreview-DtZLzrSK.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-DD8lDVej.js → VirtualizedFileDiff-C8i9HpTB.js} +1 -1
- package/dist/assets/WalkthroughView-DeKMvZlL.js +4 -0
- package/dist/assets/{computeVirtualFileMetrics-BMIRgMHv.js → computeVirtualFileMetrics-BBI7ZwrC.js} +2 -2
- package/dist/assets/contextPanelEmbeddedChat-BdA6bdky.js +1 -0
- package/dist/assets/de-DCrsQ5zS.js +10 -0
- package/dist/assets/es-Dwv4YfH6.js +16 -0
- package/dist/assets/fr-BCWg4Cnx.js +14 -0
- package/dist/assets/ghostty-vt-DdA0Zryv.wasm +0 -0
- package/dist/assets/index-B68h5S5B.css +1 -0
- package/dist/assets/index-DVwMarHI.js +39 -0
- package/dist/assets/{index-CODwDsU_.js → index-jXSK5RrB.js} +1 -1
- package/dist/assets/{isDiffPlainText-BqayB8d3.js → isDiffPlainText-B_x_rVon.js} +1 -1
- package/dist/assets/{isFilePlainText-QfoiKJNY.js → isFilePlainText-DW_xMer1.js} +1 -1
- package/dist/assets/ja-Bruw3RYC.js +16 -0
- package/dist/assets/ko-idhXGF8j.js +16 -0
- package/dist/assets/main-BkyaQhJX.js +930 -0
- package/dist/assets/{main-C5DVOxc6.js → main-CzdPuL0s.js} +2 -2
- package/dist/assets/markdownCore-Cn4Tyejo.js +5 -0
- package/dist/assets/{miniChat-DUJ4irZc.js → miniChat-CCkqKOLp.js} +2 -2
- package/dist/assets/{mobile-CcaxB0n4.js → mobile-mvxTo055.js} +2 -2
- package/dist/assets/patchFileDiff-Csk4iNUi.js +13 -0
- package/dist/assets/pl-CGZWUqZJ.js +16 -0
- package/dist/assets/pt-BR-B8V7u7tA.js +16 -0
- package/dist/assets/renderElectronMiniChatApp-BxoyujA5.js +2 -0
- package/dist/assets/renderMobileApp-B48CsP4r.js +25 -0
- package/dist/assets/runtimeEndpointReset-Bg5gx18H.js +17 -0
- package/dist/assets/scheduledTasksApi-BW0Etzwz.js +1 -0
- package/dist/assets/{shikiHighlight-DYOWuY65.js → shikiHighlight-CRl0SUC9.js} +1 -1
- package/dist/assets/surface-oMvhsog3.js +1 -0
- package/dist/assets/tr-BK_KiO2V.js +7 -0
- package/dist/assets/uk-CG74xV8N.js +16 -0
- package/dist/assets/useAppFontEffects-CPKeOpUo.js +7284 -0
- package/dist/assets/useGitComparison-DH220WmO.js +1 -0
- package/dist/assets/useInlineCommentController-BiWz4AVb.js +2 -0
- package/dist/assets/useRootScrollLock-BS4-kr7P.js +1 -0
- package/dist/assets/{vendor-base-ui-BeMD8X_w.js → vendor-base-ui-DdV8vhHn.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-data-DFZik9R2.js → vendor-codemirror-language-data-Bg4AEeCr.js} +2 -2
- package/dist/assets/vendor-zod-BPAANqvH.js +39 -0
- package/dist/assets/{web-components-Bhg83tjf.js → web-components-z7hfnS8n.js} +1 -1
- package/dist/assets/zh-CN-T4AXofZI.js +16 -0
- package/dist/assets/zh-TW-Dte40sxp.js +16 -0
- package/dist/index.html +21 -59
- package/dist/mini-chat.html +7 -7
- package/dist/mobile.html +5 -5
- package/package.json +3 -4
- package/server/index.d.ts +15 -0
- package/server/index.js +158 -89
- package/server/lib/agent-memory/actions.js +2 -2
- package/server/lib/agent-memory/feature-flag.js +1 -1
- package/server/lib/agent-memory/feature-flag.test.js +8 -8
- package/server/lib/agent-memory/routes.js +2 -2
- package/server/lib/agent-tool/runtime.js +37 -54
- package/server/lib/agent-tool/runtime.test.js +59 -59
- package/server/lib/browser-control/broker.js +3 -3
- package/server/lib/browser-control/broker.test.js +1 -1
- package/server/lib/changelog/update-notes.js +82 -0
- package/server/lib/changelog/update-notes.test.js +59 -0
- package/server/lib/client-auth/remote-clients.test.js +5 -5
- package/server/lib/context-obligatory/runtime.js +7 -7
- package/server/lib/context-obligatory/runtime.test.js +5 -5
- package/server/lib/data-dir-migration.js +40 -0
- package/server/lib/data-dir-migration.test.js +59 -0
- package/server/lib/dev-servers/parse.js +2 -2
- package/server/lib/dev-servers/parse.test.js +2 -2
- package/server/lib/dev-servers/routes.js +1 -1
- package/server/lib/dev-tunnel/DOCUMENTATION.md +13 -4
- package/server/lib/dev-tunnel/client.js +1 -1
- package/server/lib/dev-tunnel/runtime.js +5 -5
- package/server/lib/dev-tunnel/tunnel.test.js +21 -1
- package/server/lib/dictation/local/model-catalog.js +1 -1
- package/server/lib/dictation/local/worker-process.js +2 -2
- package/server/lib/event-stream/directory-ws-bridge.js +1 -1
- package/server/lib/event-stream/global-ws-bridge.js +1 -1
- package/server/lib/event-stream/protocol.test.js +4 -4
- package/server/lib/event-stream/runtime.test.js +18 -18
- package/server/lib/fs/DOCUMENTATION.md +5 -0
- package/server/lib/fs/routes.js +68 -26
- package/server/lib/fs/routes.test.js +208 -22
- package/server/lib/git/DOCUMENTATION.md +32 -6
- package/server/lib/git/identity-storage.js +1 -1
- package/server/lib/git/issue-2746-longpaths.test.js +4 -4
- package/server/lib/git/routes.js +41 -5
- package/server/lib/git/service.js +300 -224
- package/server/lib/git/service.test.js +596 -28
- package/server/lib/github/auth.js +8 -8
- package/server/lib/github/pr-status.js +1 -1
- package/server/lib/github/routes.js +2 -2
- package/server/lib/inherited-env.js +17 -10
- package/server/lib/inherited-env.test.js +10 -11
- package/server/lib/linear/auth.js +8 -8
- package/server/lib/linear/auth.test.js +24 -24
- package/server/lib/linear/issues.test.js +6 -6
- package/server/lib/linear/mapping.test.js +4 -4
- package/server/lib/linear/oauth.js +2 -2
- package/server/lib/linear/oauth.test.js +14 -14
- package/server/lib/linear/routes.js +5 -5
- package/server/lib/linear/routes.test.js +16 -16
- package/server/lib/linear/status-runtime.test.js +9 -9
- package/server/lib/linear/status.js +1 -1
- package/server/lib/linear/status.test.js +15 -15
- package/server/lib/linear/teams.test.js +4 -4
- package/server/lib/magic-prompts/routes.js +2 -2
- package/server/lib/mcp-reconnect/DOCUMENTATION.md +55 -0
- package/server/lib/mcp-reconnect/runtime.js +122 -0
- package/server/lib/mcp-reconnect/runtime.test.js +140 -0
- package/server/lib/message-queue/DOCUMENTATION.md +161 -0
- package/server/lib/message-queue/runtime.js +870 -0
- package/server/lib/message-queue/runtime.test.js +482 -0
- package/server/lib/network-defaults.js +23 -0
- package/server/lib/network-defaults.test.js +52 -0
- package/server/lib/notifications/apns-runtime.js +17 -17
- package/server/lib/notifications/apns-runtime.test.js +11 -11
- package/server/lib/notifications/emitter-runtime.js +1 -1
- package/server/lib/notifications/emitter-runtime.test.js +1 -1
- package/server/lib/notifications/push-runtime.js +7 -7
- package/server/lib/notifications/routes.js +1 -1
- package/server/lib/notifications/runtime.js +1 -1
- package/server/lib/openchamber-control/action-resolution.test.js +10 -10
- package/server/lib/openchamber-control/actions.js +24 -24
- package/server/lib/openchamber-control/error.js +4 -4
- package/server/lib/openchamber-control/routes.js +2 -2
- package/server/lib/openchamber-control/routes.test.js +5 -5
- package/server/lib/openchamber-control/screenshots.js +1 -1
- package/server/lib/openchamber-control/screenshots.test.js +1 -1
- package/server/lib/openchamber-control/service.js +50 -50
- package/server/lib/openchamber-control/service.test.js +5 -5
- package/server/lib/openchamber-sessions/routes.js +124 -22
- package/server/lib/openchamber-sessions/routes.test.js +99 -11
- package/server/lib/opencode/DOCUMENTATION.md +30 -6
- package/server/lib/opencode/auth.js +1 -1
- package/server/lib/opencode/bootstrap-runtime.js +8 -6
- package/server/lib/opencode/cli-options.js +8 -8
- package/server/lib/opencode/config-paths.test.js +116 -0
- package/server/lib/opencode/core-routes.js +8 -7
- package/server/lib/opencode/core-routes.test.js +11 -11
- package/server/lib/opencode/env-config.js +5 -5
- package/server/lib/opencode/env-config.test.js +7 -7
- package/server/lib/opencode/env-runtime.js +10 -10
- package/server/lib/opencode/env-runtime.test.js +18 -18
- package/server/lib/opencode/feature-routes-runtime.js +19 -12
- package/server/lib/opencode/hmr-state-runtime.js +2 -2
- package/server/lib/opencode/hmr-state-runtime.test.js +1 -1
- package/server/lib/opencode/input-history-scope.js +14 -0
- package/server/lib/opencode/lifecycle.js +8 -8
- package/server/lib/opencode/lifecycle.test.js +4 -4
- package/server/lib/opencode/managed-plugin-config.js +35 -0
- package/server/lib/opencode/managed-process-registry.js +3 -3
- package/server/lib/opencode/mcp-oauth-callback.test.js +2 -2
- package/server/lib/opencode/openchamber-routes.js +79 -19
- package/server/lib/opencode/openchamber-routes.test.js +139 -12
- package/server/lib/opencode/plugins.js +2 -2
- package/server/lib/opencode/plugins.test.js +1 -1
- package/server/lib/opencode/project-icon-routes.js +2 -2
- package/server/lib/opencode/project-icon-routes.test.js +1 -1
- package/server/lib/opencode/providers.js +44 -2
- package/server/lib/opencode/providers.test.js +131 -4
- package/server/lib/opencode/proxy.js +2 -2
- package/server/lib/opencode/pwa-manifest-routes.js +1 -1
- package/server/lib/opencode/restart-session-recovery.test.js +1 -1
- package/server/lib/opencode/routes-upgrade.test.js +4 -4
- package/server/lib/opencode/routes.js +19 -17
- package/server/lib/opencode/server-startup-runtime.js +6 -6
- package/server/lib/opencode/session-runtime.js +3 -3
- package/server/lib/opencode/session-runtime.test.js +6 -6
- package/server/lib/opencode/settings-files.js +214 -0
- package/server/lib/opencode/settings-helpers.js +116 -44
- package/server/lib/opencode/settings-helpers.test.js +354 -12
- package/server/lib/opencode/settings-registry.json +747 -0
- package/server/lib/opencode/settings-runtime.js +141 -20
- package/server/lib/opencode/settings-runtime.test.js +191 -4
- package/server/lib/opencode/shared.js +5 -2
- package/server/lib/opencode/shared.test.js +7 -7
- package/server/lib/opencode/shutdown-runtime.js +2 -0
- package/server/lib/opencode/skill-routes.js +2 -1
- package/server/lib/opencode/snippets.js +1 -2
- package/server/lib/opencode/startup-performance.js +1 -1
- package/server/lib/opencode/startup-performance.test.js +6 -6
- package/server/lib/opencode/static-routes-runtime.js +8 -8
- package/server/lib/opencode/static-routes-runtime.test.js +7 -7
- package/server/lib/opencode/upgrade-capability.js +2 -2
- package/server/lib/opencode/upgrade-capability.test.js +3 -3
- package/server/lib/package-manager.js +17 -42
- package/server/lib/package-manager.test.js +19 -19
- package/server/lib/permission-auto-accept/runtime.js +1 -1
- package/server/lib/project-context/DOCUMENTATION.md +25 -1
- package/server/lib/project-context/routes.http.test.js +18 -0
- package/server/lib/project-context/routes.js +16 -1
- package/server/lib/project-context/runtime.js +248 -23
- package/server/lib/project-context/runtime.test.js +106 -3
- package/server/lib/projects/DOCUMENTATION.md +76 -0
- package/server/lib/projects/project-config.js +144 -11
- package/server/lib/projects/project-id.js +17 -0
- package/server/lib/projects/project-setup.js +409 -0
- package/server/lib/projects/project-setup.test.js +557 -0
- package/server/lib/projects/routes.js +59 -0
- package/server/lib/quota/DOCUMENTATION.md +40 -2
- package/server/lib/quota/credentials/providers.js +4 -0
- package/server/lib/quota/credentials/store.js +4 -4
- package/server/lib/quota/credentials/store.test.js +8 -8
- package/server/lib/quota/index.js +1 -0
- package/server/lib/quota/providers/cline-pass.js +125 -0
- package/server/lib/quota/providers/cline-pass.test.js +103 -0
- package/server/lib/quota/providers/exe-dev.js +75 -0
- package/server/lib/quota/providers/exe-dev.test.js +47 -0
- package/server/lib/quota/providers/hyper.js +118 -0
- package/server/lib/quota/providers/hyper.test.js +126 -0
- package/server/lib/quota/providers/index.js +22 -0
- package/server/lib/quota/providers/neuralwatt.js +3 -6
- package/server/lib/quota/providers/neuralwatt.test.js +7 -7
- package/server/lib/quota/providers/ollama-cloud.js +32 -1
- package/server/lib/quota/providers/ollama-cloud.test.js +33 -1
- package/server/lib/quota/providers/opencode-go.js +3 -2
- package/server/lib/quota/providers/opencode-go.test.js +35 -6
- package/server/lib/quota/providers/openrouter.js +119 -24
- package/server/lib/quota/providers/openrouter.test.js +330 -0
- package/server/lib/quota/providers/xai.js +1 -1
- package/server/lib/quota/routes.js +2 -0
- package/server/lib/quota/utils/auth.js +1 -1
- package/server/lib/relay/DOCUMENTATION.md +56 -1
- package/server/lib/relay/cross-compat.test.js +26 -0
- package/server/lib/relay/downstream-scheduler.js +154 -0
- package/server/lib/relay/downstream-scheduler.test.js +162 -0
- package/server/lib/relay/e2ee.js +10 -8
- package/server/lib/relay/flow-control.test.js +177 -0
- package/server/lib/relay/host-client.js +38 -9
- package/server/lib/relay/host-client.test.js +9 -1
- package/server/lib/relay/host-lock.js +1 -1
- package/server/lib/relay/service.js +5 -5
- package/server/lib/relay/tunnel-codec.js +9 -0
- package/server/lib/relay/tunnel-host.js +69 -18
- package/server/lib/relay/tunnel-host.test.js +9 -1
- package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +2 -2
- package/server/lib/scheduled-tasks/routes.js +5 -5
- package/server/lib/scheduled-tasks/service.js +25 -25
- package/server/lib/scheduled-tasks/service.test.js +1 -1
- package/server/lib/security/bind-host.js +4 -4
- package/server/lib/security/bind-host.test.js +1 -1
- package/server/lib/security/request-security.js +11 -5
- package/server/lib/security/request-security.test.js +14 -14
- package/server/lib/session-assist/runtime.js +15 -18
- package/server/lib/session-folders/routes.js +2 -2
- package/server/lib/session-folders/routes.test.js +8 -8
- package/server/lib/session-goal/DOCUMENTATION.md +20 -7
- package/server/lib/session-goal/create.js +4 -2
- package/server/lib/session-goal/create.test.js +1 -1
- package/server/lib/session-goal/objectives.js +4 -4
- package/server/lib/session-goal/routes.js +1 -1
- package/server/lib/session-goal/runtime.js +91 -30
- package/server/lib/session-goal/runtime.test.js +428 -2
- package/server/lib/session-knowledge/runtime.js +11 -11
- package/server/lib/session-knowledge/runtime.test.js +5 -5
- package/server/lib/skills-catalog/DOCUMENTATION.md +1 -1
- package/server/lib/skills-catalog/cache.test.js +2 -2
- package/server/lib/skills-catalog/disk-cache.js +5 -5
- package/server/lib/skills-catalog/github-meta.test.js +2 -2
- package/server/lib/skills-catalog/install.js +3 -2
- package/server/lib/small-model/DOCUMENTATION.md +7 -2
- package/server/lib/small-model/call.js +18 -7
- package/server/lib/small-model/call.test.js +58 -6
- package/server/lib/small-model/index.js +12 -16
- package/server/lib/small-model/index.test.js +1 -1
- package/server/lib/small-model/resolve.js +1 -1
- package/server/lib/small-model/resolve.test.js +1 -1
- package/server/lib/small-model/routes.js +2 -1
- package/server/lib/small-model/runtime-providers.js +15 -7
- package/server/lib/small-model/runtime-providers.test.js +7 -1
- package/server/lib/system-prompt/runtime.js +3 -22
- package/server/lib/system-prompt/runtime.test.js +3 -3
- package/server/lib/terminal/DOCUMENTATION.md +14 -10
- package/server/lib/terminal/runtime.js +190 -31
- package/server/lib/terminal/runtime.test.js +572 -38
- package/server/lib/terminal/shells.js +20 -3
- package/server/lib/terminal/shells.test.js +33 -2
- package/server/lib/tts/base-url.js +3 -3
- package/server/lib/tts/routes.test.js +20 -20
- package/server/lib/ui-auth/ui-auth.js +12 -9
- package/server/lib/ui-auth/ui-auth.test.js +19 -3
- package/server/lib/ui-auth/ui-passkeys.js +7 -7
- package/server/lib/walkthrough/DOCUMENTATION.md +33 -14
- package/server/lib/walkthrough/index.js +1 -0
- package/server/lib/walkthrough/jobs.test.js +1 -1
- package/server/lib/walkthrough/language.test.js +1 -1
- package/server/lib/walkthrough/model-settings.js +11 -15
- package/server/lib/walkthrough/model-settings.test.js +1 -1
- package/server/lib/walkthrough/prompt.js +3 -1
- package/server/lib/walkthrough/pull-request.test.js +5 -5
- package/server/lib/walkthrough/reproduce-2607.test.js +2 -2
- package/server/lib/walkthrough/routes.test.js +26 -5
- package/server/lib/walkthrough/sources.js +23 -2
- package/server/lib/walkthrough/store.js +3 -3
- package/server/lib/walkthrough/store.test.js +1 -1
- package/server/opencode-proxy.test.js +2 -2
- package/server/proxy-headers.js +1 -1
- package/server/sse-routes.test.js +4 -4
- package/README.md.bak +0 -250
- package/bin/lib/DOCUMENTATION.md.bak +0 -163
- package/dist/assets/CommitSection-SGBCAW0w.js +0 -1
- package/dist/assets/DiffView-DluiRSla.js +0 -4
- package/dist/assets/FilesView-Bdq8ppva.js +0 -17
- package/dist/assets/GitView-Znn7VJcP.js +0 -6
- package/dist/assets/LinearIssuesView-jP-hXyac.js +0 -1
- package/dist/assets/MarkdownImageGallery-ClOvuZxC.js +0 -1
- package/dist/assets/MarkdownRendererImpl-Dt9mjTRN.js +0 -12
- package/dist/assets/OnboardingScreen-D2DG7K6b.js +0 -1
- package/dist/assets/PierreDiffViewer-B7m3f0d7.js +0 -157
- package/dist/assets/PlanView-B0F3BtPZ.js +0 -5
- package/dist/assets/SettingsView-BBW-S-gh.js +0 -28
- package/dist/assets/ToolPartDiffPreview-CUNe28U1.js +0 -8
- package/dist/assets/WalkthroughView-egQgxel3.js +0 -4
- package/dist/assets/contextPanelEmbeddedChat-C3fjDbdg.js +0 -1
- package/dist/assets/de-C1P6X5Rs.js +0 -9
- package/dist/assets/es-CDgaF1_t.js +0 -15
- package/dist/assets/fr-Bgxqvvz_.js +0 -13
- package/dist/assets/index-C57jT0Sa.css +0 -1
- package/dist/assets/index-DW_E3rnL.js +0 -7
- package/dist/assets/ja-BDRrEcNP.js +0 -15
- package/dist/assets/ko-Ba8sQV79.js +0 -15
- package/dist/assets/main-Bv5Pkngh.js +0 -928
- package/dist/assets/markdownCore-CZwzz5gw.js +0 -3
- package/dist/assets/patchFileDiff-yNfWXN4E.js +0 -10
- package/dist/assets/pl-Dioz29BI.js +0 -15
- package/dist/assets/pt-BR-DIs6ElwV.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-BcKLHXKe.js +0 -2
- package/dist/assets/renderMobileApp-D9yQJy0L.js +0 -25
- package/dist/assets/runtimeEndpointReset-CjhvxLH0.js +0 -16
- package/dist/assets/scheduledTasksApi-DLW0mkQK.js +0 -1
- package/dist/assets/tr-DFnMlPxY.js +0 -6
- package/dist/assets/uk-ZrCNjM7Y.js +0 -15
- package/dist/assets/useAppFontEffects-CjrntdL0.js +0 -7310
- package/dist/assets/useInlineCommentController-CDEuSlPH.js +0 -2
- package/dist/assets/useRootScrollLock-BHYPkrLv.js +0 -1
- package/dist/assets/vendor-ghostty-web-CrxinGGH.js +0 -13
- package/dist/assets/vendor-zod-BLfBSgBh.js +0 -39
- package/dist/assets/zh-CN-iv_AV4EC.js +0 -15
- package/dist/assets/zh-TW-CzEUk38j.js +0 -15
- package/server/index.d.ts.bak +0 -39
- package/server/lib/agent-tool/DOCUMENTATION.md.bak +0 -119
- package/server/lib/browser-control/DOCUMENTATION.md.bak +0 -50
- package/server/lib/context-obligatory/DOCUMENTATION.md.bak +0 -19
- package/server/lib/dev-tunnel/DOCUMENTATION.md.bak +0 -57
- package/server/lib/dictation/DOCUMENTATION.md.bak +0 -100
- package/server/lib/event-stream/DOCUMENTATION.md.bak +0 -62
- package/server/lib/fs/DOCUMENTATION.md.bak +0 -47
- package/server/lib/git/DOCUMENTATION.md.bak +0 -193
- package/server/lib/github/DOCUMENTATION.md.bak +0 -183
- package/server/lib/linear/DOCUMENTATION.md.bak +0 -97
- package/server/lib/linear/OAUTH-BROKER-HANDOFF.md.bak +0 -91
- package/server/lib/markdown-image-grants/DOCUMENTATION.md.bak +0 -40
- package/server/lib/notifications/APNS.md.bak +0 -135
- package/server/lib/notifications/DOCUMENTATION.md.bak +0 -138
- package/server/lib/openchamber-control/DOCUMENTATION.md.bak +0 -64
- package/server/lib/opencode/DOCUMENTATION.md.bak +0 -431
- package/server/lib/opencode/managed-process-registry.d.ts.bak +0 -13
- package/server/lib/opencode/provider-env-aliases.d.ts.bak +0 -3
- package/server/lib/permission-auto-accept/DOCUMENTATION.md.bak +0 -34
- package/server/lib/project-context/DOCUMENTATION.md.bak +0 -160
- package/server/lib/quota/DOCUMENTATION.md.bak +0 -123
- package/server/lib/relay/DOCUMENTATION.md.bak +0 -100
- package/server/lib/scheduled-tasks/DOCUMENTATION.md.bak +0 -182
- package/server/lib/session-assist/DOCUMENTATION.md.bak +0 -66
- package/server/lib/session-goal/DOCUMENTATION.md.bak +0 -213
- package/server/lib/session-knowledge/DOCUMENTATION.md.bak +0 -89
- package/server/lib/skills-catalog/DOCUMENTATION.md.bak +0 -151
- package/server/lib/small-model/DOCUMENTATION.md.bak +0 -212
- package/server/lib/system-prompt/DOCUMENTATION.md.bak +0 -36
- package/server/lib/terminal/DOCUMENTATION.md.bak +0 -50
- package/server/lib/text/DOCUMENTATION.md.bak +0 -35
- package/server/lib/tts/DOCUMENTATION.md.bak +0 -147
- package/server/lib/tunnels/DOCUMENTATION.md.bak +0 -21
- package/server/lib/ui-auth/DOCUMENTATION.md.bak +0 -44
- package/server/lib/walkthrough/DOCUMENTATION.md.bak +0 -411
|
@@ -4,6 +4,8 @@ import os from 'node:os';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { afterAll, afterEach, describe, expect, it, vi } from 'vitest';
|
|
6
6
|
import simpleGit from 'simple-git';
|
|
7
|
+
import { loadSourceSections, parseSource, sourceKey } from '../walkthrough/sources.js';
|
|
8
|
+
import { registerGitRoutes } from './routes.js';
|
|
7
9
|
|
|
8
10
|
import {
|
|
9
11
|
checkoutBranch,
|
|
@@ -12,7 +14,12 @@ import {
|
|
|
12
14
|
createWorktree,
|
|
13
15
|
getWorktreeBootstrapStatus,
|
|
14
16
|
getBranches,
|
|
17
|
+
getUnpushedBranchCounts,
|
|
15
18
|
getRangeDiff,
|
|
19
|
+
getBranchBase,
|
|
20
|
+
getCommitDiff,
|
|
21
|
+
getCommitFiles,
|
|
22
|
+
getLog,
|
|
16
23
|
getStatus,
|
|
17
24
|
getWorktrees,
|
|
18
25
|
isGitRepository,
|
|
@@ -28,6 +35,7 @@ import {
|
|
|
28
35
|
unstageFiles,
|
|
29
36
|
applyHunk,
|
|
30
37
|
getDiff,
|
|
38
|
+
getUntrackedDiffs,
|
|
31
39
|
getFileDiff,
|
|
32
40
|
validateWorktreeCreate,
|
|
33
41
|
parseBranchCreationSource,
|
|
@@ -54,6 +62,14 @@ const runGit = (cwd, args) =>
|
|
|
54
62
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
55
63
|
});
|
|
56
64
|
|
|
65
|
+
const readBranchConfig = (cwd, branch, key) => {
|
|
66
|
+
try {
|
|
67
|
+
return runGit(cwd, ['config', '--get', `branch.${branch}.${key}`]).trim();
|
|
68
|
+
} catch {
|
|
69
|
+
return '';
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
57
73
|
/**
|
|
58
74
|
* A repository on `next` whose only remote publishes `defaultBranch` and has it
|
|
59
75
|
* recorded as that remote's HEAD — the shape of every repository whose default
|
|
@@ -174,28 +190,49 @@ describe.runIf(canRunGit())('setLocalIdentity', () => {
|
|
|
174
190
|
"ssh -i '/tmp/test key' -o IdentitiesOnly=yes"
|
|
175
191
|
);
|
|
176
192
|
});
|
|
193
|
+
|
|
194
|
+
it('configures the stored credential helper for token auth with the targeted simple-git opt-in', async () => {
|
|
195
|
+
const { tmpDir } = await createTempRepo();
|
|
196
|
+
|
|
197
|
+
await setLocalIdentity(tmpDir, {
|
|
198
|
+
userName: 'Token User',
|
|
199
|
+
userEmail: 'token@example.com',
|
|
200
|
+
authType: 'token',
|
|
201
|
+
host: 'github.com',
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
expect(runGit(tmpDir, ['config', '--local', '--get', 'credential.helper']).trim()).toBe('store');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('clears the stored credential helper when switching to SSH auth', async () => {
|
|
208
|
+
const { tmpDir } = await createTempRepo();
|
|
209
|
+
|
|
210
|
+
await setLocalIdentity(tmpDir, {
|
|
211
|
+
userName: 'Token User',
|
|
212
|
+
userEmail: 'token@example.com',
|
|
213
|
+
authType: 'token',
|
|
214
|
+
host: 'github.com',
|
|
215
|
+
});
|
|
216
|
+
await setLocalIdentity(tmpDir, {
|
|
217
|
+
userName: 'SSH User',
|
|
218
|
+
userEmail: 'ssh@example.com',
|
|
219
|
+
authType: 'ssh',
|
|
220
|
+
sshKey: '/tmp/test key',
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
expect(runGit(tmpDir, ['config', '--local', '--get', 'core.sshCommand']).trim()).toBe(
|
|
224
|
+
"ssh -i '/tmp/test key' -o IdentitiesOnly=yes"
|
|
225
|
+
);
|
|
226
|
+
expect(() => runGit(tmpDir, ['config', '--local', '--get', 'credential.helper'])).toThrow();
|
|
227
|
+
});
|
|
177
228
|
});
|
|
178
229
|
|
|
179
230
|
// ---------------------------------------------------------------------------
|
|
180
231
|
// applyHunk (per-hunk stage / unstage / discard)
|
|
181
232
|
// ---------------------------------------------------------------------------
|
|
182
233
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const lines = patch.split(/\r?\n/);
|
|
186
|
-
const headerEnd = lines.findIndex((line) => /^@@\s/.test(line));
|
|
187
|
-
if (headerEnd === -1) return [];
|
|
188
|
-
const header = lines.slice(0, headerEnd);
|
|
189
|
-
const hunks = [];
|
|
190
|
-
for (let i = headerEnd; i < lines.length; i += 1) {
|
|
191
|
-
const line = lines[i];
|
|
192
|
-
if (/^@@\s/.test(line)) hunks.push([...header, line]);
|
|
193
|
-
else if (hunks.length > 0) hunks[hunks.length - 1].push(line);
|
|
194
|
-
}
|
|
195
|
-
return hunks.map((hunk) => hunk.join('\n'))
|
|
196
|
-
.filter((hunk) => hunk.trim().length > 0)
|
|
197
|
-
.map((hunk) => (hunk.endsWith('\n') ? hunk : `${hunk}\n`));
|
|
198
|
-
};
|
|
234
|
+
// Exercise the actual client splitter against the server apply boundary.
|
|
235
|
+
import { splitPatchIntoHunks as splitHunks } from '../../../../ui/src/lib/diff/patchFileDiff.ts';
|
|
199
236
|
|
|
200
237
|
const writeFile = (repo, name, contents) =>
|
|
201
238
|
fs.promises.writeFile(path.join(repo, name), contents, 'utf8');
|
|
@@ -211,6 +248,77 @@ const readWorking = (repo) => fs.promises.readFile(path.join(repo, 'file.txt'),
|
|
|
211
248
|
const readStaged = async (git) => (await git.raw(['show', ':file.txt'])).replace(/\r\n/g, '\n');
|
|
212
249
|
|
|
213
250
|
describe('applyHunk', () => {
|
|
251
|
+
it('stages successive hunks and never discards a stale staged or committed patch', async () => {
|
|
252
|
+
if (!canRunGit()) return;
|
|
253
|
+
const { tmpDir, git } = await createTempRepo();
|
|
254
|
+
const original = Array.from({ length: 60 }, (_, index) => `line${index}`);
|
|
255
|
+
const changed = [...original];
|
|
256
|
+
changed[1] = 'FIRST'; changed[25] = 'SECOND'; changed[50] = 'THIRD';
|
|
257
|
+
await writeFile(tmpDir, 'file.txt', original.join('\n') + '\n');
|
|
258
|
+
await git.add('file.txt'); await git.commit('Initial');
|
|
259
|
+
await writeFile(tmpDir, 'file.txt', changed.join('\n') + '\n');
|
|
260
|
+
const historical = splitHunks(await getDiff(tmpDir, { path: 'file.txt' }));
|
|
261
|
+
expect(historical).toHaveLength(3);
|
|
262
|
+
await applyHunk(tmpDir, 'file.txt', { patch: historical[0], action: 'stage' });
|
|
263
|
+
const remaining = splitHunks(await getDiff(tmpDir, { path: 'file.txt' }));
|
|
264
|
+
expect(remaining).toHaveLength(2);
|
|
265
|
+
await applyHunk(tmpDir, 'file.txt', { patch: remaining[0], action: 'stage' });
|
|
266
|
+
const stalePath = path.join(tmpDir, 'stale.patch');
|
|
267
|
+
await fs.promises.writeFile(stalePath, historical[0]);
|
|
268
|
+
// Git's reverse applicability check accepts it, but it is no longer an
|
|
269
|
+
// unstaged hunk. The server must reject it before touching the working file.
|
|
270
|
+
await git.raw(['apply', '--reverse', '--check', stalePath]);
|
|
271
|
+
await expect(applyHunk(tmpDir, 'file.txt', { patch: historical[0], action: 'discard' })).rejects.toThrow('refresh and try again');
|
|
272
|
+
expect(await readWorking(tmpDir)).toBe(changed.join('\n') + '\n');
|
|
273
|
+
const last = splitHunks(await getDiff(tmpDir, { path: 'file.txt' }));
|
|
274
|
+
expect(last).toHaveLength(1);
|
|
275
|
+
await applyHunk(tmpDir, 'file.txt', { patch: last[0], action: 'discard' });
|
|
276
|
+
changed[50] = original[50];
|
|
277
|
+
expect(await readWorking(tmpDir)).toBe(changed.join('\n') + '\n');
|
|
278
|
+
expect(await readStaged(git)).toBe(changed.join('\n') + '\n');
|
|
279
|
+
const staged = splitHunks(await getDiff(tmpDir, { path: 'file.txt', staged: true }));
|
|
280
|
+
await applyHunk(tmpDir, 'file.txt', { patch: staged[0], action: 'unstage' });
|
|
281
|
+
expect(await readWorking(tmpDir)).toBe(changed.join('\n') + '\n');
|
|
282
|
+
await git.add('file.txt'); await git.commit('Committed changes');
|
|
283
|
+
await expect(applyHunk(tmpDir, 'file.txt', { patch: historical[0], action: 'discard' })).rejects.toThrow('refresh and try again');
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it.each(['crlf', 'mixed'])('preserves %s file bytes through stage, unstage and discard', async (endings) => {
|
|
287
|
+
if (!canRunGit()) return;
|
|
288
|
+
const { tmpDir, git } = await createTempRepo();
|
|
289
|
+
await git.addConfig('core.autocrlf', 'false');
|
|
290
|
+
const serialize = (first, last) => Array.from({ length: 30 }, (_, index) => {
|
|
291
|
+
const text = index === 0 ? first : index === 29 ? last : `line${index}`;
|
|
292
|
+
return text + (endings === 'crlf' || index % 2 === 0 ? '\r\n' : '\n');
|
|
293
|
+
}).join('');
|
|
294
|
+
const original = serialize('first', 'last');
|
|
295
|
+
const edited = serialize('FIRST', 'LAST');
|
|
296
|
+
await writeFile(tmpDir, 'file.txt', original);
|
|
297
|
+
await git.add('file.txt'); await git.commit('Initial');
|
|
298
|
+
await writeFile(tmpDir, 'file.txt', edited);
|
|
299
|
+
const hunks = splitHunks(await getDiff(tmpDir, { path: 'file.txt' }));
|
|
300
|
+
await applyHunk(tmpDir, 'file.txt', { patch: hunks[0], action: 'stage' });
|
|
301
|
+
expect(await git.raw(['show', ':file.txt'])).toBe(serialize('FIRST', 'last'));
|
|
302
|
+
const staged = splitHunks(await getDiff(tmpDir, { path: 'file.txt', staged: true }));
|
|
303
|
+
await applyHunk(tmpDir, 'file.txt', { patch: staged[0], action: 'unstage' });
|
|
304
|
+
expect(await git.raw(['show', ':file.txt'])).toBe(original);
|
|
305
|
+
const working = splitHunks(await getDiff(tmpDir, { path: 'file.txt' }));
|
|
306
|
+
await applyHunk(tmpDir, 'file.txt', { patch: working[0], action: 'discard' });
|
|
307
|
+
expect(await fs.promises.readFile(path.join(tmpDir, 'file.txt'), 'utf8')).toBe(serialize('first', 'LAST'));
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('rejects extra files hidden before the requested patch', async () => {
|
|
311
|
+
if (!canRunGit()) return;
|
|
312
|
+
const { tmpDir, git } = await createTempRepo();
|
|
313
|
+
for (const name of ['file.txt', 'other.txt']) await writeFile(tmpDir, name, ORIGINAL_FILE);
|
|
314
|
+
await git.add('.'); await git.commit('Initial');
|
|
315
|
+
for (const name of ['file.txt', 'other.txt']) await writeFile(tmpDir, name, EDITED_FILE);
|
|
316
|
+
const other = splitHunks(await getDiff(tmpDir, { path: 'other.txt' }))[0];
|
|
317
|
+
const requested = splitHunks(await getDiff(tmpDir, { path: 'file.txt' }))[0];
|
|
318
|
+
await expect(applyHunk(tmpDir, 'file.txt', { patch: requested + other, action: 'stage' })).rejects.toThrow('refresh and try again');
|
|
319
|
+
expect(await git.raw(['diff', '--cached'])).toBe('');
|
|
320
|
+
});
|
|
321
|
+
|
|
214
322
|
it('rejects an invalid action or a patch without a hunk header', async () => {
|
|
215
323
|
const { tmpDir } = await createTempRepo();
|
|
216
324
|
await expect(applyHunk(tmpDir, 'file.txt', { patch: '@@ -1 +1 @@\n a\n', action: 'bogus' })).rejects.toThrow(
|
|
@@ -293,10 +401,9 @@ describe('applyHunk', () => {
|
|
|
293
401
|
);
|
|
294
402
|
});
|
|
295
403
|
|
|
296
|
-
it('accepts hunk patches for
|
|
404
|
+
it.each(['file name.txt', 'зміни.txt'])('accepts hunk patches for %s', async (filePath) => {
|
|
297
405
|
if (!canRunGit()) return;
|
|
298
406
|
const { tmpDir, git } = await createTempRepo();
|
|
299
|
-
const filePath = 'file name.txt';
|
|
300
407
|
await writeFile(tmpDir, filePath, ORIGINAL_FILE);
|
|
301
408
|
await git.add(filePath);
|
|
302
409
|
await git.commit('Initial');
|
|
@@ -313,6 +420,71 @@ describe('applyHunk', () => {
|
|
|
313
420
|
});
|
|
314
421
|
});
|
|
315
422
|
|
|
423
|
+
describe.runIf(canRunGit())('untracked diffs', () => {
|
|
424
|
+
it.each(['false', 'warn'])('returns only the patch with core.safecrlf=%s', async (safecrlf) => {
|
|
425
|
+
const { tmpDir, git } = await createTempRepo();
|
|
426
|
+
await git.addConfig('core.autocrlf', 'true');
|
|
427
|
+
await git.addConfig('core.safecrlf', safecrlf);
|
|
428
|
+
fs.writeFileSync(path.join(tmpDir, 'new file.txt'), 'first\nsecond\n');
|
|
429
|
+
|
|
430
|
+
// Confirm this fixture produces a real diff exit, including stderr in the warning case.
|
|
431
|
+
let expectedPatch;
|
|
432
|
+
try {
|
|
433
|
+
runGit(tmpDir, ['diff', '--no-color', '--full-index', '--no-index', '--', '/dev/null', 'new file.txt']);
|
|
434
|
+
throw new Error('Expected git diff to exit with differences');
|
|
435
|
+
} catch (error) {
|
|
436
|
+
expect(error.status).toBe(1);
|
|
437
|
+
expectedPatch = error.stdout;
|
|
438
|
+
if (safecrlf === 'warn') {
|
|
439
|
+
expect(error.stderr).toContain('LF will be replaced by CRLF');
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const diff = await getDiff(tmpDir, { path: 'new file.txt' });
|
|
444
|
+
expect(diff).toBe(expectedPatch);
|
|
445
|
+
expect(diff).toContain('+first\n+second\n');
|
|
446
|
+
expect(diff).not.toContain('warning:');
|
|
447
|
+
expect(await getUntrackedDiffs(tmpDir, ['new file.txt'])).toEqual([diff]);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it('accepts an empty untracked file without a process error', async () => {
|
|
451
|
+
const { tmpDir } = await createTempRepo();
|
|
452
|
+
fs.writeFileSync(path.join(tmpDir, 'empty.txt'), '');
|
|
453
|
+
const diff = await getDiff(tmpDir, { path: 'empty.txt' });
|
|
454
|
+
expect(diff).toContain('new file mode 100644');
|
|
455
|
+
expect(diff).not.toContain('@@');
|
|
456
|
+
expect(await getUntrackedDiffs(tmpDir, ['empty.txt'])).toEqual([diff]);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('rejects fatal conversion errors while preserving other batch entries', async () => {
|
|
460
|
+
const { tmpDir } = await createTempRepo();
|
|
461
|
+
runGit(tmpDir, ['config', 'diff.broken.textconv', 'false']);
|
|
462
|
+
fs.writeFileSync(path.join(tmpDir, '.gitattributes'), 'bad.txt diff=broken\n');
|
|
463
|
+
fs.writeFileSync(path.join(tmpDir, 'first.safe'), 'first\n');
|
|
464
|
+
fs.writeFileSync(path.join(tmpDir, 'bad.txt'), 'bad\n');
|
|
465
|
+
fs.writeFileSync(path.join(tmpDir, 'last.safe'), 'last\n');
|
|
466
|
+
|
|
467
|
+
await expect(getDiff(tmpDir, { path: 'bad.txt' })).rejects.toThrow('unable to read files to diff');
|
|
468
|
+
const diffs = await getUntrackedDiffs(tmpDir, ['first.safe', 'bad.txt', 'last.safe'], { concurrency: 1 });
|
|
469
|
+
expect(diffs).toHaveLength(3);
|
|
470
|
+
expect(diffs[0]).toContain('+first\n');
|
|
471
|
+
expect(diffs[1]).toBe('');
|
|
472
|
+
expect(diffs[2]).toContain('+last\n');
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('rejects truncated patches when the process output exceeds the buffer limit', async () => {
|
|
476
|
+
const { tmpDir } = await createTempRepo();
|
|
477
|
+
fs.writeFileSync(path.join(tmpDir, 'large.txt'), 'x'.repeat(21 * 1024 * 1024) + '\n');
|
|
478
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
479
|
+
try {
|
|
480
|
+
await expect(getDiff(tmpDir, { path: 'large.txt' })).rejects.toThrow('maxBuffer');
|
|
481
|
+
expect(await getUntrackedDiffs(tmpDir, ['large.txt'])).toEqual(['']);
|
|
482
|
+
} finally {
|
|
483
|
+
errorSpy.mockRestore();
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
|
|
316
488
|
describe('symlink diffs', () => {
|
|
317
489
|
it('treats an untracked directory symlink as a link in patch and split diffs', async () => {
|
|
318
490
|
if (!canRunGit() || process.platform === 'win32') return;
|
|
@@ -508,6 +680,24 @@ describe('getWorktrees', () => {
|
|
|
508
680
|
expect(Array.isArray(result)).toBe(true);
|
|
509
681
|
expect(warnSpy).not.toHaveBeenCalled();
|
|
510
682
|
});
|
|
683
|
+
it('flags a worktree whose directory was deleted outside git as prunable', async () => {
|
|
684
|
+
const repo = createTempDir();
|
|
685
|
+
runGit(repo, ['init', '-b', 'main']);
|
|
686
|
+
runGit(repo, ['config', 'user.email', 'test@example.com']);
|
|
687
|
+
runGit(repo, ['config', 'user.name', 'Test User']);
|
|
688
|
+
runGit(repo, ['commit', '--allow-empty', '-m', 'init']);
|
|
689
|
+
const worktreePath = path.join(createTempDir(), 'feature');
|
|
690
|
+
runGit(repo, ['worktree', 'add', worktreePath, '-b', 'feature']);
|
|
691
|
+
|
|
692
|
+
const before = await getWorktrees(repo);
|
|
693
|
+
expect(before.find((entry) => entry.branch === 'feature')).toMatchObject({ prunable: false });
|
|
694
|
+
|
|
695
|
+
fs.rmSync(worktreePath, { recursive: true, force: true });
|
|
696
|
+
|
|
697
|
+
const after = await getWorktrees(repo);
|
|
698
|
+
expect(after.find((entry) => entry.branch === 'feature')).toMatchObject({ path: expect.any(String), prunable: true });
|
|
699
|
+
expect(after.find((entry) => entry.branch === 'main')).toMatchObject({ prunable: false });
|
|
700
|
+
});
|
|
511
701
|
});
|
|
512
702
|
|
|
513
703
|
// ---------------------------------------------------------------------------
|
|
@@ -622,7 +812,7 @@ describe('createWorktree', () => {
|
|
|
622
812
|
const created = await createWorktree(repo, {
|
|
623
813
|
mode: 'new',
|
|
624
814
|
worktreeName: 'hook-test',
|
|
625
|
-
branchName: '
|
|
815
|
+
branchName: 'aiworker/hook-test',
|
|
626
816
|
returnAfterDirectoryCreated: true,
|
|
627
817
|
});
|
|
628
818
|
|
|
@@ -674,7 +864,7 @@ describe('createWorktree', () => {
|
|
|
674
864
|
const created = await createWorktree(repo, {
|
|
675
865
|
mode: 'new',
|
|
676
866
|
worktreeName: 'hook-skip-test',
|
|
677
|
-
branchName: '
|
|
867
|
+
branchName: 'aiworker/hook-skip-test',
|
|
678
868
|
returnAfterDirectoryCreated: true,
|
|
679
869
|
});
|
|
680
870
|
|
|
@@ -717,7 +907,7 @@ describe('createWorktree', () => {
|
|
|
717
907
|
const created = await createWorktree(repo, {
|
|
718
908
|
mode: 'new',
|
|
719
909
|
worktreeName: 'hook-fail-test',
|
|
720
|
-
branchName: '
|
|
910
|
+
branchName: 'aiworker/hook-fail-test',
|
|
721
911
|
returnAfterDirectoryCreated: true,
|
|
722
912
|
});
|
|
723
913
|
|
|
@@ -854,6 +1044,135 @@ describe('createWorktree', () => {
|
|
|
854
1044
|
}
|
|
855
1045
|
}
|
|
856
1046
|
});
|
|
1047
|
+
|
|
1048
|
+
it('does not auto-track the remote start ref when creating a new branch from it', async () => {
|
|
1049
|
+
if (!canRunGit()) return;
|
|
1050
|
+
|
|
1051
|
+
const previousXdgDataHome = process.env.XDG_DATA_HOME;
|
|
1052
|
+
const dataHome = createTempDir();
|
|
1053
|
+
process.env.XDG_DATA_HOME = dataHome;
|
|
1054
|
+
|
|
1055
|
+
try {
|
|
1056
|
+
const { repository } = createRepositoryWithRemote({ defaultBranch: 'main' });
|
|
1057
|
+
|
|
1058
|
+
const created = await createWorktree(repository, {
|
|
1059
|
+
mode: 'new',
|
|
1060
|
+
branchName: 'aiworker/feature',
|
|
1061
|
+
worktreeName: 'feature-wt',
|
|
1062
|
+
startRef: 'remotes/origin/main',
|
|
1063
|
+
setUpstream: true,
|
|
1064
|
+
upstreamRemote: 'origin',
|
|
1065
|
+
upstreamBranch: 'aiworker/feature',
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
expect(created.branch).toBe('aiworker/feature');
|
|
1069
|
+
|
|
1070
|
+
await expect.poll(
|
|
1071
|
+
() => getWorktreeBootstrapStatus(created.path).then((status) => status.status === 'ready' || status.status === 'failed'),
|
|
1072
|
+
{ timeout: 5_000 }
|
|
1073
|
+
).toBe(true);
|
|
1074
|
+
|
|
1075
|
+
expect(readBranchConfig(created.path, 'aiworker/feature', 'remote')).toBe('');
|
|
1076
|
+
expect(readBranchConfig(created.path, 'aiworker/feature', 'merge')).toBe('');
|
|
1077
|
+
} finally {
|
|
1078
|
+
if (previousXdgDataHome === undefined) {
|
|
1079
|
+
delete process.env.XDG_DATA_HOME;
|
|
1080
|
+
} else {
|
|
1081
|
+
process.env.XDG_DATA_HOME = previousXdgDataHome;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}, 30_000);
|
|
1085
|
+
|
|
1086
|
+
it('falls back to the remote start ref for upstream tracking when no explicit keys are given', async () => {
|
|
1087
|
+
if (!canRunGit()) return;
|
|
1088
|
+
|
|
1089
|
+
const previousXdgDataHome = process.env.XDG_DATA_HOME;
|
|
1090
|
+
const dataHome = createTempDir();
|
|
1091
|
+
process.env.XDG_DATA_HOME = dataHome;
|
|
1092
|
+
|
|
1093
|
+
try {
|
|
1094
|
+
const { repository } = createRepositoryWithRemote({ defaultBranch: 'main' });
|
|
1095
|
+
|
|
1096
|
+
const created = await createWorktree(repository, {
|
|
1097
|
+
mode: 'new',
|
|
1098
|
+
branchName: 'aiworker/fallback-wt',
|
|
1099
|
+
worktreeName: 'fallback-wt',
|
|
1100
|
+
startRef: 'remotes/origin/main',
|
|
1101
|
+
setUpstream: true,
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
await expect.poll(
|
|
1105
|
+
() => readBranchConfig(created.path, 'aiworker/fallback-wt', 'merge'),
|
|
1106
|
+
{ timeout: 5_000 }
|
|
1107
|
+
).toBe('refs/heads/main');
|
|
1108
|
+
expect(readBranchConfig(created.path, 'aiworker/fallback-wt', 'remote')).toBe('origin');
|
|
1109
|
+
} finally {
|
|
1110
|
+
if (previousXdgDataHome === undefined) {
|
|
1111
|
+
delete process.env.XDG_DATA_HOME;
|
|
1112
|
+
} else {
|
|
1113
|
+
process.env.XDG_DATA_HOME = previousXdgDataHome;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}, 30_000);
|
|
1117
|
+
|
|
1118
|
+
it('falls back to the tracked local branch when the source fetch fails', async () => {
|
|
1119
|
+
if (!canRunGit()) return;
|
|
1120
|
+
|
|
1121
|
+
const previousXdgDataHome = process.env.XDG_DATA_HOME;
|
|
1122
|
+
const dataHome = createTempDir();
|
|
1123
|
+
process.env.XDG_DATA_HOME = dataHome;
|
|
1124
|
+
|
|
1125
|
+
try {
|
|
1126
|
+
const { repository } = createRepositoryWithRemote({ defaultBranch: 'main' });
|
|
1127
|
+
runGit(repository, ['branch', '--set-upstream-to=origin/main', 'next']);
|
|
1128
|
+
runGit(repository, ['remote', 'set-url', 'origin', '/nonexistent/openchamber-unreachable.git']);
|
|
1129
|
+
|
|
1130
|
+
const created = await createWorktree(repository, {
|
|
1131
|
+
mode: 'new',
|
|
1132
|
+
branchName: 'aiworker/stale-ref-wt',
|
|
1133
|
+
worktreeName: 'stale-ref-wt',
|
|
1134
|
+
startRef: 'remotes/origin/main',
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
expect(created.branch).toBe('aiworker/stale-ref-wt');
|
|
1138
|
+
expect(created.sourceFetchFailed).toBe(true);
|
|
1139
|
+
const expectedHead = runGit(repository, ['rev-parse', 'next']).trim();
|
|
1140
|
+
expect(runGit(created.path, ['rev-parse', 'HEAD']).trim()).toBe(expectedHead);
|
|
1141
|
+
} finally {
|
|
1142
|
+
if (previousXdgDataHome === undefined) {
|
|
1143
|
+
delete process.env.XDG_DATA_HOME;
|
|
1144
|
+
} else {
|
|
1145
|
+
process.env.XDG_DATA_HOME = previousXdgDataHome;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
}, 30_000);
|
|
1149
|
+
|
|
1150
|
+
it('rejects creation from a remote start ref that was never fetched and cannot be fetched', async () => {
|
|
1151
|
+
if (!canRunGit()) return;
|
|
1152
|
+
|
|
1153
|
+
const previousXdgDataHome = process.env.XDG_DATA_HOME;
|
|
1154
|
+
const dataHome = createTempDir();
|
|
1155
|
+
process.env.XDG_DATA_HOME = dataHome;
|
|
1156
|
+
|
|
1157
|
+
try {
|
|
1158
|
+
const { repository } = createRepositoryWithRemote({ defaultBranch: 'main' });
|
|
1159
|
+
runGit(repository, ['update-ref', '-d', 'refs/remotes/origin/main']);
|
|
1160
|
+
runGit(repository, ['remote', 'set-url', 'origin', '/nonexistent/openchamber-unreachable.git']);
|
|
1161
|
+
|
|
1162
|
+
await expect(createWorktree(repository, {
|
|
1163
|
+
mode: 'new',
|
|
1164
|
+
branchName: 'aiworker/never-fetched-wt',
|
|
1165
|
+
worktreeName: 'never-fetched-wt',
|
|
1166
|
+
startRef: 'remotes/origin/main',
|
|
1167
|
+
})).rejects.toThrow(/does not appear to be a git repository|Could not read from remote repository/i);
|
|
1168
|
+
} finally {
|
|
1169
|
+
if (previousXdgDataHome === undefined) {
|
|
1170
|
+
delete process.env.XDG_DATA_HOME;
|
|
1171
|
+
} else {
|
|
1172
|
+
process.env.XDG_DATA_HOME = previousXdgDataHome;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}, 30_000);
|
|
857
1176
|
});
|
|
858
1177
|
|
|
859
1178
|
// ---------------------------------------------------------------------------
|
|
@@ -1498,18 +1817,264 @@ describe.runIf(canRunGit())('getBranches', () => {
|
|
|
1498
1817
|
});
|
|
1499
1818
|
});
|
|
1500
1819
|
|
|
1820
|
+
describe.runIf(canRunGit())('getUnpushedBranchCounts', () => {
|
|
1821
|
+
it('counts only commits ahead of a locally known upstream', async () => {
|
|
1822
|
+
const { repository } = createRepositoryWithRemote();
|
|
1823
|
+
runGit(repository, ['branch', '--set-upstream-to=origin/react', 'next']);
|
|
1824
|
+
fs.writeFileSync(path.join(repository, 'ahead.txt'), 'ahead\n');
|
|
1825
|
+
runGit(repository, ['add', 'ahead.txt']);
|
|
1826
|
+
runGit(repository, ['commit', '-m', 'ahead']);
|
|
1827
|
+
runGit(repository, ['checkout', '-b', 'no-upstream']);
|
|
1828
|
+
|
|
1829
|
+
await expect(getUnpushedBranchCounts(repository, ['next', 'no-upstream', 'remotes/origin/react'])).resolves.toEqual({
|
|
1830
|
+
counts: { next: 1 },
|
|
1831
|
+
});
|
|
1832
|
+
});
|
|
1833
|
+
});
|
|
1834
|
+
|
|
1835
|
+
describe.runIf(canRunGit())('commit comparisons', () => {
|
|
1836
|
+
it('shows only the selected commit and gives walkthrough the identical patch', async () => {
|
|
1837
|
+
const { repository } = createRepositoryWithRemote();
|
|
1838
|
+
fs.writeFileSync(path.join(repository, 'README.md'), 'selected version\n');
|
|
1839
|
+
runGit(repository, ['add', '.']);
|
|
1840
|
+
runGit(repository, ['commit', '-m', 'selected']);
|
|
1841
|
+
const hash = runGit(repository, ['rev-parse', 'HEAD']).trim();
|
|
1842
|
+
fs.writeFileSync(path.join(repository, 'README.md'), 'later version\n');
|
|
1843
|
+
runGit(repository, ['add', '.']);
|
|
1844
|
+
runGit(repository, ['commit', '-m', 'later']);
|
|
1845
|
+
fs.writeFileSync(path.join(repository, 'README.md'), 'uncommitted version\n');
|
|
1846
|
+
const patch = await getCommitDiff(repository, { hash, path: 'README.md' });
|
|
1847
|
+
expect(patch).toContain('+selected version');
|
|
1848
|
+
expect(patch).not.toContain('later version');
|
|
1849
|
+
expect(patch).not.toContain('uncommitted version');
|
|
1850
|
+
expect((await getCommitFiles(repository, hash)).files).toEqual([
|
|
1851
|
+
{ path: 'README.md', insertions: 1, deletions: 1, isBinary: false, changeType: 'M' },
|
|
1852
|
+
]);
|
|
1853
|
+
const source = parseSource({ kind: 'commit', hash });
|
|
1854
|
+
expect(sourceKey(source)).toBe(`commit:${hash}`);
|
|
1855
|
+
expect((await loadSourceSections(repository, source)).sections).toEqual([{ scope: 'commit', patch }]);
|
|
1856
|
+
const routes = new Map();
|
|
1857
|
+
registerGitRoutes({
|
|
1858
|
+
get: (url, handler) => routes.set(url, handler), post() {}, put() {}, delete() {},
|
|
1859
|
+
});
|
|
1860
|
+
let response;
|
|
1861
|
+
await routes.get('/api/git/commit-diff')(
|
|
1862
|
+
{ query: { directory: repository, hash, path: 'README.md' } },
|
|
1863
|
+
{ json: (body) => { response = body; }, status: (code) => { throw new Error(`Unexpected status ${code}`); } },
|
|
1864
|
+
);
|
|
1865
|
+
expect(response).toEqual({ diff: patch });
|
|
1866
|
+
});
|
|
1867
|
+
|
|
1868
|
+
it('handles root and empty commits and rejects invalid hashes', async () => {
|
|
1869
|
+
const { repository } = createRepositoryWithRemote();
|
|
1870
|
+
const root = runGit(repository, ['rev-parse', 'HEAD']).trim();
|
|
1871
|
+
expect(await getCommitDiff(repository, { hash: root })).toContain('+# Test');
|
|
1872
|
+
expect((await getCommitFiles(repository, root)).files[0].changeType).toBe('A');
|
|
1873
|
+
runGit(repository, ['commit', '--allow-empty', '-m', 'empty']);
|
|
1874
|
+
const empty = runGit(repository, ['rev-parse', 'HEAD']).trim();
|
|
1875
|
+
expect(await getCommitDiff(repository, { hash: empty })).toBe('');
|
|
1876
|
+
expect(await getCommitFiles(repository, empty)).toEqual({ files: [] });
|
|
1877
|
+
expect(() => parseSource({ kind: 'commit', hash: 'HEAD' })).toThrow();
|
|
1878
|
+
expect(() => parseSource({ kind: 'commit', hash: [root] })).toThrow();
|
|
1879
|
+
await expect(getCommitDiff(repository, { hash: 'HEAD' })).rejects.toThrow();
|
|
1880
|
+
await expect(getCommitFiles(repository, '0'.repeat(40))).rejects.toThrow();
|
|
1881
|
+
});
|
|
1882
|
+
|
|
1883
|
+
it('keeps rename paths and original contents together, including whitespace in names', async () => {
|
|
1884
|
+
const { repository } = createRepositoryWithRemote();
|
|
1885
|
+
const destination = ' new\nname.md';
|
|
1886
|
+
runGit(repository, ['mv', 'README.md', destination]);
|
|
1887
|
+
runGit(repository, ['commit', '-m', 'rename']);
|
|
1888
|
+
const hash = runGit(repository, ['rev-parse', 'HEAD']).trim();
|
|
1889
|
+
const { files } = await getCommitFiles(repository, hash);
|
|
1890
|
+
expect(files).toEqual([{ path: destination, previousPath: 'README.md', changeType: 'R', insertions: 0, deletions: 0, isBinary: false }]);
|
|
1891
|
+
const patch = await getCommitDiff(repository, { hash, path: destination, previousPath: files[0].previousPath });
|
|
1892
|
+
expect(patch).toContain('rename from README.md');
|
|
1893
|
+
expect(patch).toContain('similarity index 100%');
|
|
1894
|
+
});
|
|
1895
|
+
|
|
1896
|
+
it('compares a merge commit against its first parent', async () => {
|
|
1897
|
+
const { repository } = createRepositoryWithRemote();
|
|
1898
|
+
runGit(repository, ['checkout', '-b', 'side']);
|
|
1899
|
+
fs.writeFileSync(path.join(repository, 'side.txt'), 'side\n');
|
|
1900
|
+
runGit(repository, ['add', '.']);
|
|
1901
|
+
runGit(repository, ['commit', '-m', 'side']);
|
|
1902
|
+
runGit(repository, ['checkout', 'next']);
|
|
1903
|
+
fs.writeFileSync(path.join(repository, 'main.txt'), 'main\n');
|
|
1904
|
+
runGit(repository, ['add', '.']);
|
|
1905
|
+
runGit(repository, ['commit', '-m', 'main']);
|
|
1906
|
+
runGit(repository, ['merge', '--no-ff', 'side', '-m', 'merge']);
|
|
1907
|
+
const hash = runGit(repository, ['rev-parse', 'HEAD']).trim();
|
|
1908
|
+
expect((await getCommitFiles(repository, hash)).files.map((file) => file.path)).toEqual(['side.txt']);
|
|
1909
|
+
const patch = await getCommitDiff(repository, { hash });
|
|
1910
|
+
expect(patch).toContain('+side');
|
|
1911
|
+
expect(patch).not.toContain('main.txt');
|
|
1912
|
+
});
|
|
1913
|
+
|
|
1914
|
+
it('limits current-branch history to 50 commits without including another branch', async () => {
|
|
1915
|
+
const { repository } = createRepositoryWithRemote();
|
|
1916
|
+
runGit(repository, ['checkout', '-b', 'other']);
|
|
1917
|
+
runGit(repository, ['commit', '--allow-empty', '-m', 'other branch only']);
|
|
1918
|
+
runGit(repository, ['checkout', 'next']);
|
|
1919
|
+
for (let index = 0; index < 51; index += 1) runGit(repository, ['commit', '--allow-empty', '-m', `current ${index}`]);
|
|
1920
|
+
const history = await getLog(repository, { maxCount: 50, to: 'refs/heads/next' });
|
|
1921
|
+
expect(history.all).toHaveLength(50);
|
|
1922
|
+
expect(history.all[0].message).toBe('current 50');
|
|
1923
|
+
expect(history.all.some((commit) => commit.message === 'other branch only')).toBe(false);
|
|
1924
|
+
});
|
|
1925
|
+
});
|
|
1926
|
+
|
|
1501
1927
|
describe.runIf(canRunGit())('getRangeDiff', () => {
|
|
1502
|
-
it('
|
|
1928
|
+
it('loads a committed deletion that no longer exists in HEAD or the working tree', async () => {
|
|
1929
|
+
const { repository } = createRepositoryWithRemote();
|
|
1930
|
+
runGit(repository, ['rm', 'README.md']);
|
|
1931
|
+
runGit(repository, ['commit', '-m', 'delete file']);
|
|
1932
|
+
const diff = await getRangeDiff(repository, { base: 'origin/react', head: 'next', path: 'README.md', includeWorkingTree: true });
|
|
1933
|
+
expect(diff).toContain('deleted file mode');
|
|
1934
|
+
expect(diff).toContain('-# Test');
|
|
1935
|
+
});
|
|
1936
|
+
|
|
1937
|
+
it('carries the working-tree option through the actual HTTP route handlers', async () => {
|
|
1938
|
+
const { repository } = createRepositoryWithRemote();
|
|
1939
|
+
fs.writeFileSync(path.join(repository, 'local.txt'), 'current local work\n');
|
|
1940
|
+
const routes = new Map();
|
|
1941
|
+
registerGitRoutes({
|
|
1942
|
+
get: (url, handler) => routes.set(url, handler),
|
|
1943
|
+
post() {},
|
|
1944
|
+
put() {},
|
|
1945
|
+
delete() {},
|
|
1946
|
+
});
|
|
1947
|
+
const query = { directory: repository, base: 'origin/react', head: 'next', includeWorkingTree: 'true' };
|
|
1948
|
+
for (const endpoint of ['range-diff', 'range-files']) {
|
|
1949
|
+
let status = 200;
|
|
1950
|
+
let body;
|
|
1951
|
+
const response = {
|
|
1952
|
+
status(value) { status = value; return this; },
|
|
1953
|
+
json(value) { body = value; },
|
|
1954
|
+
};
|
|
1955
|
+
await routes.get(`/api/git/${endpoint}`)({ query }, response);
|
|
1956
|
+
expect(status).toBe(200);
|
|
1957
|
+
if (endpoint === 'range-diff') expect(body.diff).toContain('+current local work');
|
|
1958
|
+
else expect(body.files).toEqual([{ path: 'local.txt', status: 'A' }]);
|
|
1959
|
+
}
|
|
1960
|
+
});
|
|
1961
|
+
|
|
1962
|
+
it('asks for a new base after restacking and compares against the selected parent', async () => {
|
|
1963
|
+
const { repository } = createRepositoryWithRemote();
|
|
1964
|
+
runGit(repository, ['checkout', '-b', 'child', 'origin/react']);
|
|
1965
|
+
fs.writeFileSync(path.join(repository, 'child.txt'), 'child\n');
|
|
1966
|
+
runGit(repository, ['add', '.']);
|
|
1967
|
+
runGit(repository, ['commit', '-m', 'child']);
|
|
1968
|
+
expect(await getBranchBase(repository, 'child')).toEqual({ base: 'origin/react' });
|
|
1969
|
+
runGit(repository, ['checkout', '-b', 'parent', 'origin/react']);
|
|
1970
|
+
fs.writeFileSync(path.join(repository, 'parent.txt'), 'parent\n');
|
|
1971
|
+
runGit(repository, ['add', '.']);
|
|
1972
|
+
runGit(repository, ['commit', '-m', 'parent']);
|
|
1973
|
+
runGit(repository, ['checkout', 'child']);
|
|
1974
|
+
runGit(repository, ['rebase', 'parent']);
|
|
1975
|
+
expect(await getBranchBase(repository, 'child')).toEqual({ base: null });
|
|
1976
|
+
fs.writeFileSync(path.join(repository, 'child.txt'), 'current child\n');
|
|
1977
|
+
const options = { base: 'refs/heads/parent', head: 'child', includeWorkingTree: true };
|
|
1978
|
+
expect(await getRangeFiles(repository, options)).toEqual([{ path: 'child.txt', status: 'A' }]);
|
|
1979
|
+
const diff = await getRangeDiff(repository, options);
|
|
1980
|
+
expect(diff).toContain('+current child');
|
|
1981
|
+
expect(diff).not.toContain('parent.txt');
|
|
1982
|
+
});
|
|
1983
|
+
|
|
1984
|
+
it('combines committed, staged, unstaged and untracked work without changing the real index', async () => {
|
|
1985
|
+
const { repository } = createRepositoryWithRemote();
|
|
1986
|
+
fs.writeFileSync(path.join(repository, 'README.md'), '# Committed\n');
|
|
1987
|
+
runGit(repository, ['add', 'README.md']);
|
|
1988
|
+
runGit(repository, ['commit', '-m', 'branch change']);
|
|
1989
|
+
fs.writeFileSync(path.join(repository, 'README.md'), '# Staged\n');
|
|
1990
|
+
fs.writeFileSync(path.join(repository, 'staged.txt'), 'staged only\n');
|
|
1991
|
+
runGit(repository, ['add', '.']);
|
|
1992
|
+
fs.writeFileSync(path.join(repository, 'README.md'), '# Current\n');
|
|
1993
|
+
fs.writeFileSync(path.join(repository, 'untracked.txt'), 'new local file\n');
|
|
1994
|
+
fs.writeFileSync(path.join(repository, ' leading space.txt'), 'space path\n');
|
|
1995
|
+
const indexBefore = fs.readFileSync(path.join(repository, '.git/index'));
|
|
1996
|
+
const options = { base: 'origin/react', head: 'next', includeWorkingTree: true };
|
|
1997
|
+
|
|
1998
|
+
const diff = await getRangeDiff(repository, options);
|
|
1999
|
+
expect(diff).toContain('-# Test');
|
|
2000
|
+
expect(diff).toContain('+# Current');
|
|
2001
|
+
expect(diff).not.toContain('+# Staged');
|
|
2002
|
+
expect(diff).not.toContain('+# Committed');
|
|
2003
|
+
expect(diff).toContain('+new local file');
|
|
2004
|
+
expect(diff).toContain('+staged only');
|
|
2005
|
+
expect(await getRangeFiles(repository, options)).toEqual(expect.arrayContaining([
|
|
2006
|
+
{ path: 'README.md', status: 'M' },
|
|
2007
|
+
{ path: 'staged.txt', status: 'A' },
|
|
2008
|
+
{ path: 'untracked.txt', status: 'A' },
|
|
2009
|
+
{ path: ' leading space.txt', status: 'A' },
|
|
2010
|
+
]));
|
|
2011
|
+
const { sections } = await loadSourceSections(repository, { kind: 'branch', baseRef: options.base, headRef: options.head });
|
|
2012
|
+
expect(sections).toEqual([{ scope: 'branch', patch: diff }]);
|
|
2013
|
+
expect(fs.readFileSync(path.join(repository, '.git/index'))).toEqual(indexBefore);
|
|
2014
|
+
|
|
2015
|
+
const committed = await getRangeDiff(repository, { base: options.base, head: options.head });
|
|
2016
|
+
expect(committed).toContain('+# Committed');
|
|
2017
|
+
expect(committed).not.toContain('+new local file');
|
|
2018
|
+
fs.writeFileSync(path.join(repository, 'README.md'), '# Latest\n');
|
|
2019
|
+
expect(await getRangeDiff(repository, { ...options, path: 'README.md' })).toContain('+# Latest');
|
|
2020
|
+
});
|
|
2021
|
+
|
|
2022
|
+
it('reports the final file after a staged deletion is recreated, and omits undone branch changes', async () => {
|
|
2023
|
+
const { repository } = createRepositoryWithRemote();
|
|
2024
|
+
runGit(repository, ['rm', 'README.md']);
|
|
2025
|
+
fs.writeFileSync(path.join(repository, 'README.md'), '# Recreated\n');
|
|
2026
|
+
const options = { base: 'origin/react', head: 'next', includeWorkingTree: true };
|
|
2027
|
+
expect(await getRangeFiles(repository, options)).toEqual([{ path: 'README.md', status: 'M' }]);
|
|
2028
|
+
const diff = await getRangeDiff(repository, options);
|
|
2029
|
+
expect(diff).toContain('-# Test');
|
|
2030
|
+
expect(diff).toContain('+# Recreated');
|
|
2031
|
+
expect(diff.match(/diff --git/g)).toHaveLength(1);
|
|
2032
|
+
fs.writeFileSync(path.join(repository, 'README.md'), '# Test\n');
|
|
2033
|
+
expect(await getRangeFiles(repository, options)).toEqual([]);
|
|
2034
|
+
expect(await getRangeDiff(repository, options)).toBe('');
|
|
2035
|
+
});
|
|
2036
|
+
|
|
2037
|
+
it('keeps local and remote bases distinct and rejects a different checked-out branch', async () => {
|
|
2038
|
+
const { repository } = createRepositoryWithRemote();
|
|
2039
|
+
runGit(repository, ['branch', 'react']);
|
|
2040
|
+
fs.writeFileSync(path.join(repository, 'parent.txt'), 'parent work\n');
|
|
2041
|
+
runGit(repository, ['add', '.']);
|
|
2042
|
+
runGit(repository, ['commit', '-m', 'parent work']);
|
|
2043
|
+
runGit(repository, ['branch', '-f', 'react', 'HEAD']);
|
|
2044
|
+
fs.writeFileSync(path.join(repository, 'child.txt'), 'child work\n');
|
|
2045
|
+
const options = { head: 'next', includeWorkingTree: true };
|
|
2046
|
+
const local = await getRangeDiff(repository, { ...options, base: 'react' });
|
|
2047
|
+
const remote = await getRangeDiff(repository, { ...options, base: 'origin/react' });
|
|
2048
|
+
expect(local).not.toContain('parent.txt');
|
|
2049
|
+
expect(remote).toContain('parent.txt');
|
|
2050
|
+
expect(local).toContain('child.txt');
|
|
2051
|
+
expect(await getRangeFiles(repository, { ...options, base: 'react' })).toEqual([{ path: 'child.txt', status: 'A' }]);
|
|
2052
|
+
runGit(repository, ['checkout', 'react']);
|
|
2053
|
+
await expect(getRangeDiff(repository, { ...options, base: 'origin/react' })).rejects.toThrow(/checked-out branch/);
|
|
2054
|
+
});
|
|
2055
|
+
|
|
2056
|
+
it('includes untracked symlinks as links without reading their targets', async () => {
|
|
2057
|
+
const { repository } = createRepositoryWithRemote();
|
|
2058
|
+
const outside = path.join(createTempDir(), 'outside.txt');
|
|
2059
|
+
fs.writeFileSync(outside, 'must not be in a diff\n');
|
|
2060
|
+
fs.symlinkSync(outside, path.join(repository, 'link.txt'));
|
|
2061
|
+
const diff = await getRangeDiff(repository, { base: 'origin/react', head: 'next', includeWorkingTree: true });
|
|
2062
|
+
expect(diff).toContain('new file mode 120000');
|
|
2063
|
+
expect(diff).toContain(outside);
|
|
2064
|
+
expect(diff).not.toContain('must not be in a diff');
|
|
2065
|
+
});
|
|
2066
|
+
|
|
2067
|
+
it('uses an explicitly selected base on a remote other than origin', async () => {
|
|
1503
2068
|
const { repository } = createRepositoryWithRemote({ remoteName: 'upstream', defaultBranch: 'react' });
|
|
1504
|
-
//
|
|
1505
|
-
// bare name, so an unqualified `react...next` fails with "ambiguous argument".
|
|
2069
|
+
// The selected remote ref must work without a local branch of that name.
|
|
1506
2070
|
fs.writeFileSync(path.join(repository, 'feature.txt'), 'work\n');
|
|
1507
2071
|
runGit(repository, ['add', 'feature.txt']);
|
|
1508
2072
|
runGit(repository, ['commit', '-m', 'feature']);
|
|
1509
2073
|
|
|
1510
|
-
const diff = await getRangeDiff(repository, { base: 'react', head: 'next' });
|
|
2074
|
+
const diff = await getRangeDiff(repository, { base: 'upstream/react', head: 'next' });
|
|
1511
2075
|
|
|
1512
2076
|
expect(diff).toContain('feature.txt');
|
|
2077
|
+
await expect(getRangeDiff(repository, { base: 'react', head: 'next' })).rejects.toThrow(/is not available locally/);
|
|
1513
2078
|
});
|
|
1514
2079
|
|
|
1515
2080
|
it('names an unfetched remote-only ref instead of failing with git\'s ambiguous argument (#2735)', async () => {
|
|
@@ -1522,6 +2087,9 @@ describe.runIf(canRunGit())('getRangeDiff', () => {
|
|
|
1522
2087
|
});
|
|
1523
2088
|
|
|
1524
2089
|
describe('parseBranchCreationSource', () => {
|
|
2090
|
+
it('does not reuse the creation base after a rebase', () => {
|
|
2091
|
+
expect(parseBranchCreationSource('rebase (finish): refs/heads/feature onto abc123\nbranch: Created from main')).toBeNull();
|
|
2092
|
+
});
|
|
1525
2093
|
it('returns the source ref from the oldest creation entry', () => {
|
|
1526
2094
|
// Reflog lists newest entries first; creation is the last line.
|
|
1527
2095
|
const reflog = [
|
|
@@ -1568,7 +2136,7 @@ describe.runIf(canRunGit())('getRangeFiles', () => {
|
|
|
1568
2136
|
runGit(repository, ['add', 'added.txt', 'README.md']);
|
|
1569
2137
|
runGit(repository, ['commit', '-m', 'changes']);
|
|
1570
2138
|
|
|
1571
|
-
const files = await getRangeFiles(repository, { base: 'react', head: 'next' });
|
|
2139
|
+
const files = await getRangeFiles(repository, { base: 'origin/react', head: 'next' });
|
|
1572
2140
|
|
|
1573
2141
|
expect(files).toEqual(expect.arrayContaining([
|
|
1574
2142
|
{ path: 'added.txt', status: 'A' },
|
|
@@ -1590,7 +2158,7 @@ describe.runIf(canRunGit())('getRangeFiles', () => {
|
|
|
1590
2158
|
runGit(repository, ['add', '-A']);
|
|
1591
2159
|
runGit(repository, ['commit', '-m', 'rename']);
|
|
1592
2160
|
|
|
1593
|
-
const files = await getRangeFiles(repository, { base: 'react', head: 'next' });
|
|
2161
|
+
const files = await getRangeFiles(repository, { base: 'origin/react', head: 'next' });
|
|
1594
2162
|
|
|
1595
2163
|
const renameEntry = files.find((file) => file.status === 'R');
|
|
1596
2164
|
expect(renameEntry).toBeDefined();
|
|
@@ -1612,7 +2180,7 @@ describe.runIf(canRunGit())('getRangeFiles', () => {
|
|
|
1612
2180
|
runGit(repository, ['add', '-A']);
|
|
1613
2181
|
runGit(repository, ['commit', '-m', 'copy']);
|
|
1614
2182
|
|
|
1615
|
-
const files = await getRangeFiles(repository, { base: 'react', head: 'next' });
|
|
2183
|
+
const files = await getRangeFiles(repository, { base: 'origin/react', head: 'next' });
|
|
1616
2184
|
|
|
1617
2185
|
const copyEntry = files.find((file) => file.status === 'C');
|
|
1618
2186
|
expect(copyEntry).toBeDefined();
|